List:Commits« Previous MessageNext Message »
From:Konstantin Osipov Date:June 25 2008 8:00pm
Subject:bzr commit into mysql-6.0 branch (konstantin:2675) WL#4144, WL#3726
View as plain text  
#At file:///opt/local/work/mysql-6.0-4144/

 2675 Konstantin Osipov	2008-06-25
      WL#4144 - Lock MERGE engine children
      
      Step #1: Move locking from parent to children.
      
      MERGE children are now left in the query list of tables
      after inserted there in open_tables(). So they are locked
      by lock_tables() as all other tables are.
      
      The MERGE parent does not store locks any more. It appears
      in a MYSQL_LOCK with zero lock data. This is kind of a "dummy"
      lock.
      
      All other lock handling is also done directly on the children.
      To protect against parent or child modifications during LOCK
      TABLES, the children are detached after every statement and
      attached before every statement, even under LOCK TABLES.
      
      The children table list is removed from the query list of tables
      on every detach and on close of the parent.
      
      Step #2: Move MERGE specific functionality from SQL layer
               into table handler.
      
      Functionality moved from SQL layer (mainly sql_base.cc)
      to the table handler (ha_myisammrg.cc).
      
      Unnecessary code is removed from the SQL layer.
      
      Step #3: Moved all MERGE specific members from TABLE
               to ha_myisammrg.
      
      Moved members from TABLE to ha_myisammrg.
      Renamed some mebers.
      Fixed comments.
       
      Step #4: Valgrind and coverage testing
          
      Valgrind did not uncover new problems.
      Added purecov comments.
        
      Added a new test for DATA/INDEX DIRECTORY options.
      Changed handling of ::reset() for non-attached children.
      Fixed the merge-big test.
      
      Step #5: Fixed crashes detected during review
      Changed detection when to attach/detach.
      Added new tests.
modified:
  include/my_base.h
  mysql-test/r/merge.result
  mysql-test/t/disabled.def
  mysql-test/t/merge-big.test
  mysql-test/t/merge.test
  mysys/thr_lock.c
  sql/ha_partition.cc
  sql/mysql_priv.h
  sql/sql_base.cc
  sql/sql_table.cc
  sql/table.cc
  sql/table.h
  storage/myisammrg/ha_myisammrg.cc
  storage/myisammrg/ha_myisammrg.h
  storage/myisammrg/myrg_extra.c

per-file messages:
  include/my_base.h
    WL#4144 - Lock MERGE engine children
    Added HA_EXTRA_ADD_CHILDREN_LIST and HA_EXTRA_IS_ATTACHED_CHILDREN
    for MERGE table support.
  mysql-test/r/merge.result
    WL#4144 - Lock MERGE engine children
    Fixed test result.
  mysql-test/t/disabled.def
    Enable merge.test, which now is working again (WL#4144).
  mysql-test/t/merge-big.test
    Fix the messages for wait_condition (merge with WL#3726).
  mysql-test/t/merge.test
    WL#4144 - Lock MERGE engine children
    Fixed one test to meet coding standards for tests
    (upper case keywords, engine names as in SHOW ENGINES).
    Fixed error codes.
    Added a test for DATA/INDEX DIRECTORY.
  mysys/thr_lock.c
    WL#4144 - Lock MERGE engine children
    Added purecov comments.
  sql/ha_partition.cc
    WL#4144 - Lock MERGE engine children
    Added MERGE specific extra operations to ha_partition::extra().
    Extended comments.
    Changed function comment to doxygen style.
    Fixed nomenclature: 'parameter' -> 'operation'.
  sql/mysql_priv.h
    WL#4144 - Lock MERGE engine children
    Removed declarations for removed functions.
  sql/sql_base.cc
    L#4144 - Lock MERGE engine children
    Leave the children in the query list of tables after open_tables().
    Set proper back links (prev_global).
    Attach MERGE children before and detach them after every
    statement. Even under LOCK TABLES.
    Remove children from the query list when they are detached.
    Remove lock forwarding from children to parent.
    Moved MERGE specific functions to ha_myisammrg.cc.
    Added purecov comments.
  sql/sql_table.cc
    WL#4144 - Lock MERGE engine children
    Changed detection of MERGE tables.
  sql/table.cc
    WL#4144 - Lock MERGE engine children
    Moved is_children_attached() method from TABLE to ha_myisammrg.
  sql/table.h
    WL#4144 - Lock MERGE engine children
    Moved all MERGE specific members from TABLE to ha_myisammrg.
  storage/myisammrg/ha_myisammrg.cc
    WL#4144 - Lock MERGE engine children
    Set proper back links in the child list (prev_global).
    Added a function for removal of the child list from the query list.
    Remove children from the query list when the parent is closed.
    Make parent lock handling a dummy (zero locks).
    Moved MERGE specific functionality from SQL layer to here.
    Moved all MERGE specific members from TABLE to ha_myisammrg.
    Renamed children list pointers.
    Added initialization and free for the children list mem_root.
    Fixed comments.
    Added purecov comments.
  storage/myisammrg/ha_myisammrg.h
    WL#4144 - Lock MERGE engine children
    Added method add_children_list().
    Moved all MERGE specific members from TABLE to ha_myisammrg.
    Renamed children list pointers.
    Added a mem_root for the children list.
  storage/myisammrg/myrg_extra.c
    WL#4144 - Lock MERGE engine children
    Changed handling of ::reset() for non-attached children.
=== modified file 'include/my_base.h'
--- a/include/my_base.h	2008-04-09 10:02:59 +0000
+++ b/include/my_base.h	2008-06-25 18:00:10 +0000
@@ -199,10 +199,11 @@ enum ha_extra_function {
   /* Inform handler that we will do a rename */
   HA_EXTRA_PREPARE_FOR_RENAME,
   /*
-    Orders MERGE handler to attach or detach its child tables. Used at
-    begin and end of a statement.
+    Special actions for MERGE tables.
   */
+  HA_EXTRA_ADD_CHILDREN_LIST,
   HA_EXTRA_ATTACH_CHILDREN,
+  HA_EXTRA_IS_ATTACHED_CHILDREN,
   HA_EXTRA_DETACH_CHILDREN
 };
 

=== modified file 'mysql-test/r/merge.result'
--- a/mysql-test/r/merge.result	2008-05-22 22:42:32 +0000
+++ b/mysql-test/r/merge.result	2008-06-25 18:00:10 +0000
@@ -578,23 +578,23 @@ select max(b) from t1 where a = 2;
 max(b)
 1
 drop table t3,t1,t2;
-create table t1 (a int not null);
-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);
-select * from t3;
+CREATE TABLE t1 (c1 INT NOT NULL);
+CREATE TABLE t2 (c1 INT NOT NULL);
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+CREATE TEMPORARY TABLE t3 (c1 INT NOT NULL) ENGINE=MRG_MYISAM 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
-create temporary table t4 (a int not null);
-create temporary table t5 (a int not null);
-insert into t4 values (1);
-insert into t5 values (2);
-create temporary table t6 (a int not null) ENGINE=MERGE UNION=(t4,t5);
-select * from t6;
-a
-1
-2
-drop table t6, t3, t1, t2, t4, t5;
+CREATE TEMPORARY TABLE t4 (c1 INT NOT NULL);
+CREATE TEMPORARY TABLE t5 (c1 INT NOT NULL);
+INSERT INTO t4 VALUES (4);
+INSERT INTO t5 VALUES (5);
+CREATE TEMPORARY TABLE t6 (c1 INT NOT NULL) ENGINE=MRG_MYISAM UNION=(t4,t5);
+SELECT * FROM t6;
+c1
+4
+5
+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);
@@ -1245,7 +1245,7 @@ c1
 LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE;
 ALTER TABLE t2 RENAME TO t5;
 SELECT * FROM t3 ORDER BY c1;
-ERROR HY000: Table 't3' was not locked with LOCK TABLES
+ERROR HY000: Table 't2' was not locked with LOCK TABLES
 ALTER TABLE t5 RENAME TO t2;
 ERROR HY000: Table 't5' was not locked with LOCK TABLES
 UNLOCK TABLES;
@@ -1317,9 +1317,9 @@ LOCK TABLES t1 WRITE, t2 WRITE;
 INSERT INTO t1 VALUES (1);
 DROP TABLE t1;
 SELECT * FROM t2;
-ERROR 42S02: Table 'test.t1' doesn't exist
+ERROR HY000: Table 't1' was not locked with LOCK TABLES
 SELECT * FROM t1;
-ERROR 42S02: Table 'test.t1' doesn't exist
+ERROR HY000: Table 't1' was not locked with LOCK TABLES
 UNLOCK TABLES;
 DROP TABLE t2;
 #
@@ -2058,3 +2058,250 @@ deallocate prepare stmt;
 #
 drop table t_parent;
 set @@global.table_definition_cache=@save_table_definition_cache;
+DROP DATABASE IF EXISTS mysql_test1;
+CREATE DATABASE mysql_test1;
+CREATE TABLE t1 ... DATA DIRECTORY=... INDEX DIRECTORY=...
+CREATE TABLE mysql_test1.t2 ... DATA DIRECTORY=... INDEX DIRECTORY=...
+CREATE TABLE m1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,mysql_test1.t2)
+INSERT_METHOD=LAST;
+INSERT INTO t1 VALUES (1);
+INSERT INTO mysql_test1.t2 VALUES (2);
+SELECT * FROM m1;
+c1
+1
+2
+DROP TABLE t1, mysql_test1.t2, m1;
+DROP DATABASE mysql_test1;
+CREATE TABLE t1 (c1 INT);
+CREATE TABLE t2 (c1 INT);
+INSERT INTO t1 (c1) VALUES (1);
+CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2) INSERT_METHOD=FIRST;
+CREATE TABLE t3 (c1 INT);
+INSERT INTO t3 (c1) VALUES (1);
+CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MAX(c1) FROM t3);
+CREATE VIEW v1 AS SELECT foo.c1 c1, f1() c2, bar.c1 c3, f1() c4
+FROM tm1 foo, tm1 bar, t3;
+SELECT * FROM v1;
+c1	c2	c3	c4
+1	1	1	1
+DROP FUNCTION f1;
+DROP VIEW v1;
+DROP TABLE tm1, t1, t2, t3;
+CREATE TEMPORARY TABLE t1 (c1 INT);
+CREATE TEMPORARY TABLE t2 (c1 INT);
+CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+INSERT_METHOD=FIRST;
+CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MAX(c1) FROM tm1);
+INSERT INTO tm1 (c1) VALUES (1);
+SELECT f1() FROM (SELECT 1) AS c1;
+f1()
+1
+DROP FUNCTION f1;
+DROP TABLE tm1, t1, t2;
+CREATE FUNCTION f1() RETURNS INT
+BEGIN
+CREATE TEMPORARY TABLE t1 (c1 INT);
+CREATE TEMPORARY TABLE t2 (c1 INT);
+CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2);
+INSERT INTO t1 (c1) VALUES (1);
+RETURN (SELECT MAX(c1) FROM tm1);
+END|
+SELECT f1() FROM (SELECT 1 UNION SELECT 1) c1;
+f1()
+1
+DROP FUNCTION f1;
+DROP TABLE tm1, t1, t2;
+CREATE TEMPORARY TABLE t1 (c1 INT);
+INSERT INTO t1 (c1) VALUES (1);
+CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1);
+CREATE FUNCTION f1() RETURNS INT
+BEGIN
+CREATE TEMPORARY TABLE t2 (c1 INT);
+ALTER TEMPORARY TABLE tm1 UNION=(t1,t2);
+INSERT INTO t2 (c1) VALUES (2);
+RETURN (SELECT MAX(c1) FROM tm1);
+END|
+ERROR 0A000: ALTER VIEW is not allowed in stored procedures
+DROP TABLE tm1, t1;
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+INSERT INTO tm1 VALUES (1);
+SELECT * FROM tm1;
+c1
+1
+DROP TABLE tm1, t1;
+CREATE FUNCTION f1() RETURNS INT
+BEGIN
+INSERT INTO tm1 VALUES (1);
+RETURN (SELECT MAX(c1) FROM tm1);
+END|
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+SELECT f1();
+f1()
+1
+DROP FUNCTION f1;
+DROP TABLE tm1, t1;
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+LOCK TABLE tm1 WRITE;
+INSERT INTO tm1 VALUES (1);
+SELECT * FROM tm1;
+c1
+1
+UNLOCK TABLES;
+DROP TABLE tm1, t1;
+CREATE FUNCTION f1() RETURNS INT
+BEGIN
+INSERT INTO tm1 VALUES (1);
+RETURN (SELECT MAX(c1) FROM tm1);
+END|
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+LOCK TABLE tm1 WRITE;
+SELECT f1();
+f1()
+1
+UNLOCK TABLES;
+DROP FUNCTION f1;
+DROP TABLE tm1, t1;
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+CREATE TRIGGER t2_ai AFTER INSERT ON t2
+FOR EACH ROW INSERT INTO tm1 VALUES(11);
+LOCK TABLE t2 WRITE;
+INSERT INTO t2 VALUES (2);
+SELECT * FROM tm1;
+c1
+11
+SELECT * FROM t2;
+c1
+2
+UNLOCK TABLES;
+DROP TRIGGER t2_ai;
+DROP TABLE tm1, t1, t2;
+CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
+INSERT_METHOD=LAST;
+INSERT INTO tm1 VALUES (1);
+SELECT * FROM tm1;
+c1
+1
+DROP TABLE tm1, t1;
+CREATE FUNCTION f1() RETURNS INT
+BEGIN
+INSERT INTO tm1 VALUES (1);
+RETURN (SELECT MAX(c1) FROM tm1);
+END|
+CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
+INSERT_METHOD=LAST;
+SELECT f1();
+f1()
+1
+DROP FUNCTION f1;
+DROP TABLE tm1, t1;
+CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
+INSERT_METHOD=LAST;
+CREATE TABLE t9 (c1 INT) ENGINE=MyISAM;
+LOCK TABLE t9 WRITE;
+INSERT INTO tm1 VALUES (1);
+SELECT * FROM tm1;
+c1
+1
+UNLOCK TABLES;
+DROP TABLE tm1, t1, t9;
+CREATE FUNCTION f1() RETURNS INT
+BEGIN
+INSERT INTO tm1 VALUES (1);
+RETURN (SELECT MAX(c1) FROM tm1);
+END|
+CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
+INSERT_METHOD=LAST;
+CREATE TABLE t9 (c1 INT) ENGINE=MyISAM;
+LOCK TABLE t9 WRITE;
+SELECT f1();
+f1()
+1
+UNLOCK TABLES;
+DROP FUNCTION f1;
+DROP TABLE tm1, t1, t9;
+CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
+INSERT_METHOD=LAST;
+CREATE TABLE t2 (c1 INT) ENGINE=MyISAM;
+CREATE TRIGGER t2_ai AFTER INSERT ON t2
+FOR EACH ROW INSERT INTO tm1 VALUES(11);
+LOCK TABLE t2 WRITE;
+INSERT INTO t2 VALUES (2);
+SELECT * FROM tm1;
+c1
+11
+SELECT * FROM t2;
+c1
+2
+UNLOCK TABLES;
+DROP TRIGGER t2_ai;
+DROP TABLE tm1, t1, t2;
+#
+# Don't select MERGE child when trying to get prelocked table.
+#
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
+INSERT_METHOD=LAST;
+CREATE TRIGGER tm1_ai AFTER INSERT ON tm1
+FOR EACH ROW INSERT INTO t1 VALUES(11);
+LOCK TABLE tm1 WRITE, t1 WRITE;
+INSERT INTO tm1 VALUES (1);
+SELECT * FROM tm1;
+c1
+1
+11
+UNLOCK TABLES;
+LOCK TABLE t1 WRITE, tm1 WRITE;
+INSERT INTO tm1 VALUES (1);
+SELECT * FROM tm1;
+c1
+1
+11
+1
+11
+UNLOCK TABLES;
+DROP TRIGGER tm1_ai;
+DROP TABLE tm1, t1;
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE t3 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE t4 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE t5 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2,t3,t4,t5)
+INSERT_METHOD=LAST;
+CREATE TRIGGER t2_au AFTER UPDATE ON t2
+FOR EACH ROW INSERT INTO t3 VALUES(33);
+CREATE FUNCTION f1() RETURNS INT
+RETURN (SELECT MAX(c1) FROM t4);
+LOCK TABLE tm1 WRITE, t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE, t5 WRITE;
+INSERT INTO t1 VALUES(1);
+INSERT INTO t2 VALUES(2);
+INSERT INTO t3 VALUES(3);
+INSERT INTO t4 VALUES(4);
+INSERT INTO t5 VALUES(5);
+UPDATE t2, tm1 SET t2.c1=f1();
+FLUSH TABLES;
+FLUSH TABLES;
+UNLOCK TABLES;
+SELECT * FROM tm1;
+c1
+1
+4
+3
+33
+4
+5
+DROP TRIGGER t2_au;
+DROP FUNCTION f1;
+DROP TABLE tm1, t1, t2, t3, t4, t5;
+End of 6.0 tests

=== modified file 'mysql-test/t/disabled.def'
--- a/mysql-test/t/disabled.def	2008-05-27 10:46:20 +0000
+++ b/mysql-test/t/disabled.def	2008-06-25 18:00:10 +0000
@@ -47,4 +47,3 @@ sort_buffer_size_basic_32   : Bug#36875 
 key_buffer_size_basic_32    : Bug#36876 main.key_buffer_size_basic_32 fails on some
systems
 max_heap_table_size_basic_32 : Bug#36877 main.max_heap_table_size_basic_32 fails on some
systems
 tmp_table_size_basic_32     : Bug#36878 main.tmp_table_size_basic_32 fails on some
systems
-merge                       : Temporarily broken by WL#3726

=== modified file 'mysql-test/t/merge-big.test'
--- a/mysql-test/t/merge-big.test	2008-02-07 11:04:19 +0000
+++ b/mysql-test/t/merge-big.test	2008-06-25 18:00:10 +0000
@@ -51,7 +51,7 @@ connection default;
 #--sleep 8
 #SELECT ID,STATE,INFO FROM INFORMATION_SCHEMA.PROCESSLIST;
 let $wait_condition= SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST
-    WHERE ID = $con1_id AND STATE = 'Locked';
+    WHERE ID = $con1_id AND STATE = 'Table lock';
 --source include/wait_condition.inc
 #SELECT NOW();
 --echo # Kick INSERT out of thr_multi_lock().
@@ -61,7 +61,7 @@ FLUSH TABLES;
 #--sleep 8
 #SELECT ID,STATE,INFO FROM INFORMATION_SCHEMA.PROCESSLIST;
 let $wait_condition= SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST
-    WHERE ID = $con1_id AND STATE = 'Waiting for table';
+    WHERE ID = $con1_id AND STATE = 'Table lock';
 --source include/wait_condition.inc
 #SELECT NOW();
 --echo # Unlock and close table and wait for con1 to close too.

=== modified file 'mysql-test/t/merge.test'
--- a/mysql-test/t/merge.test	2008-05-22 22:42:32 +0000
+++ b/mysql-test/t/merge.test	2008-06-25 18:00:10 +0000
@@ -216,20 +216,20 @@ drop table t3,t1,t2;
 #
 # temporary merge tables
 #
-create table t1 (a int not null);
-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);
+CREATE TABLE t1 (c1 INT NOT NULL);
+CREATE TABLE t2 (c1 INT NOT NULL);
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+CREATE TEMPORARY TABLE t3 (c1 INT NOT NULL) ENGINE=MRG_MYISAM 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);
-insert into t4 values (1);
-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;
+SELECT * FROM t3;
+CREATE TEMPORARY TABLE t4 (c1 INT NOT NULL);
+CREATE TEMPORARY TABLE t5 (c1 INT NOT NULL);
+INSERT INTO t4 VALUES (4);
+INSERT INTO t5 VALUES (5);
+CREATE TEMPORARY TABLE t6 (c1 INT NOT NULL) ENGINE=MRG_MYISAM 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.
@@ -556,7 +556,7 @@ CREATE TABLE t1(a INT);
 SELECT * FROM tm1;
 CHECK TABLE tm1;
 CREATE TABLE t2(a BLOB);
---error 1168
+--error ER_WRONG_MRG_TABLE
 SELECT * FROM tm1;
 CHECK TABLE tm1;
 ALTER TABLE t2 MODIFY a INT;
@@ -956,9 +956,9 @@ CREATE TABLE t2 (c1 INT, INDEX(c1)) ENGI
 LOCK TABLES t1 WRITE, t2 WRITE;
 INSERT INTO t1 VALUES (1);
 DROP TABLE t1;
---error ER_NO_SUCH_TABLE
+--error ER_TABLE_NOT_LOCKED
 SELECT * FROM t2;
---error ER_NO_SUCH_TABLE
+--error ER_TABLE_NOT_LOCKED
 SELECT * FROM t1;
 UNLOCK TABLES;
 DROP TABLE t2;
@@ -1394,6 +1394,7 @@ FLUSH TABLES m1, t1;
 UNLOCK TABLES;
 DROP TABLE t1, m1;
 
+
 #
 # Bug#36006 - Optimizer does table scan for select count(*)
 #
@@ -1471,3 +1472,296 @@ while ($1)
 --enable_query_log
 drop table t_parent;
 set @@global.table_definition_cache=@save_table_definition_cache;
+
+#
+# WL#4144 - Lock MERGE engine children
+#
+# Test DATA/INDEX DIRECTORY
+#
+--disable_warnings
+DROP DATABASE IF EXISTS mysql_test1;
+--enable_warnings
+CREATE DATABASE mysql_test1;
+--disable_query_log
+# data/index directory don't work in HAVE_purify builds. Disable
+# build-dependent warnings.
+--disable_warnings
+--echo CREATE TABLE t1 ... DATA DIRECTORY=... INDEX DIRECTORY=...
+eval CREATE TABLE t1 (c1 INT)
+  DATA DIRECTORY='$MYSQLTEST_VARDIR/tmp'
+  INDEX DIRECTORY='$MYSQLTEST_VARDIR/tmp';
+--echo CREATE TABLE mysql_test1.t2 ... DATA DIRECTORY=... INDEX DIRECTORY=...
+eval CREATE TABLE mysql_test1.t2 (c1 INT)
+  DATA DIRECTORY='$MYSQLTEST_VARDIR/tmp'
+  INDEX DIRECTORY='$MYSQLTEST_VARDIR/tmp';
+--enable_query_log
+--enable_warnings
+CREATE TABLE m1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,mysql_test1.t2)
+  INSERT_METHOD=LAST;
+INSERT INTO t1 VALUES (1);
+INSERT INTO mysql_test1.t2 VALUES (2);
+SELECT * FROM m1;
+#--copy_file $MYSQLTEST_VARDIR/master-data/test/m1.MRG /tmp/mysql-test-m1.MRG
+DROP TABLE t1, mysql_test1.t2, m1;
+DROP DATABASE mysql_test1;
+#
+# Review detected Crash #1. Detaching main tables while in sub statement.
+#
+CREATE TABLE t1 (c1 INT);
+CREATE TABLE t2 (c1 INT);
+INSERT INTO t1 (c1) VALUES (1);
+CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2) INSERT_METHOD=FIRST;
+CREATE TABLE t3 (c1 INT);
+INSERT INTO t3 (c1) VALUES (1);
+CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MAX(c1) FROM t3);
+CREATE VIEW v1 AS SELECT foo.c1 c1, f1() c2, bar.c1 c3, f1() c4
+                    FROM tm1 foo, tm1 bar, t3;
+SELECT * FROM v1;
+DROP FUNCTION f1;
+DROP VIEW v1;
+DROP TABLE tm1, t1, t2, t3;
+#
+# Review detected Crash #2. Trying to attach temporary table twice.
+#
+CREATE TEMPORARY TABLE t1 (c1 INT);
+CREATE TEMPORARY TABLE t2 (c1 INT);
+CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+  INSERT_METHOD=FIRST;
+CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MAX(c1) FROM tm1);
+INSERT INTO tm1 (c1) VALUES (1);
+SELECT f1() FROM (SELECT 1) AS c1;
+DROP FUNCTION f1;
+DROP TABLE tm1, t1, t2;
+#
+# Review suggested test. DDL in a stored function.
+#
+DELIMITER |;
+CREATE FUNCTION f1() RETURNS INT
+BEGIN
+  CREATE TEMPORARY TABLE t1 (c1 INT);
+  CREATE TEMPORARY TABLE t2 (c1 INT);
+  CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2);
+  INSERT INTO t1 (c1) VALUES (1);
+  RETURN (SELECT MAX(c1) FROM tm1);
+END|
+DELIMITER ;|
+SELECT f1() FROM (SELECT 1 UNION SELECT 1) c1;
+DROP FUNCTION f1;
+DROP TABLE tm1, t1, t2;
+#
+CREATE TEMPORARY TABLE t1 (c1 INT);
+INSERT INTO t1 (c1) VALUES (1);
+CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1);
+DELIMITER |;
+--error ER_SP_BADSTATEMENT
+CREATE FUNCTION f1() RETURNS INT
+BEGIN
+  CREATE TEMPORARY TABLE t2 (c1 INT);
+  ALTER TEMPORARY TABLE tm1 UNION=(t1,t2);
+  INSERT INTO t2 (c1) VALUES (2);
+  RETURN (SELECT MAX(c1) FROM tm1);
+END|
+DELIMITER ;|
+DROP TABLE tm1, t1;
+#
+# Base table. No LOCK TABLES, no functions/triggers.
+#
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+INSERT INTO tm1 VALUES (1);
+SELECT * FROM tm1;
+DROP TABLE tm1, t1;
+#
+# Base table. No LOCK TABLES, sub-statement that is run inside a function.
+#
+DELIMITER |;
+CREATE FUNCTION f1() RETURNS INT
+BEGIN
+  INSERT INTO tm1 VALUES (1);
+  RETURN (SELECT MAX(c1) FROM tm1);
+END|
+DELIMITER ;|
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+SELECT f1();
+DROP FUNCTION f1;
+DROP TABLE tm1, t1;
+#
+# Base table. LOCK TABLES, no functions/triggers.
+#
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+LOCK TABLE tm1 WRITE;
+INSERT INTO tm1 VALUES (1);
+SELECT * FROM tm1;
+UNLOCK TABLES;
+DROP TABLE tm1, t1;
+#
+# Base table. LOCK TABLES, sub-statement that is run inside a function.
+#
+DELIMITER |;
+CREATE FUNCTION f1() RETURNS INT
+BEGIN
+  INSERT INTO tm1 VALUES (1);
+  RETURN (SELECT MAX(c1) FROM tm1);
+END|
+DELIMITER ;|
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+LOCK TABLE tm1 WRITE;
+SELECT f1();
+UNLOCK TABLES;
+DROP FUNCTION f1;
+DROP TABLE tm1, t1;
+#
+# Base table. LOCK TABLES statement that locks a table that has a trigger
+# that inserts into a merge table, so an attempt is made to lock tables
+# of a sub-statement.
+#
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+CREATE TRIGGER t2_ai AFTER INSERT ON t2
+  FOR EACH ROW INSERT INTO tm1 VALUES(11);
+LOCK TABLE t2 WRITE;
+INSERT INTO t2 VALUES (2);
+SELECT * FROM tm1;
+SELECT * FROM t2;
+UNLOCK TABLES;
+DROP TRIGGER t2_ai;
+DROP TABLE tm1, t1, t2;
+#
+# Temporary. No LOCK TABLES, no functions/triggers.
+#
+CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
+  INSERT_METHOD=LAST;
+INSERT INTO tm1 VALUES (1);
+SELECT * FROM tm1;
+DROP TABLE tm1, t1;
+#
+# Temporary. No LOCK TABLES, sub-statement that is run inside a function.
+#
+DELIMITER |;
+CREATE FUNCTION f1() RETURNS INT
+BEGIN
+  INSERT INTO tm1 VALUES (1);
+  RETURN (SELECT MAX(c1) FROM tm1);
+END|
+DELIMITER ;|
+CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
+  INSERT_METHOD=LAST;
+SELECT f1();
+DROP FUNCTION f1;
+DROP TABLE tm1, t1;
+#
+# Temporary. LOCK TABLES, no functions/triggers.
+#
+CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
+  INSERT_METHOD=LAST;
+CREATE TABLE t9 (c1 INT) ENGINE=MyISAM;
+LOCK TABLE t9 WRITE;
+INSERT INTO tm1 VALUES (1);
+SELECT * FROM tm1;
+UNLOCK TABLES;
+DROP TABLE tm1, t1, t9;
+#
+# Temporary. LOCK TABLES, sub-statement that is run inside a function.
+#
+DELIMITER |;
+CREATE FUNCTION f1() RETURNS INT
+BEGIN
+  INSERT INTO tm1 VALUES (1);
+  RETURN (SELECT MAX(c1) FROM tm1);
+END|
+DELIMITER ;|
+CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
+  INSERT_METHOD=LAST;
+CREATE TABLE t9 (c1 INT) ENGINE=MyISAM;
+LOCK TABLE t9 WRITE;
+SELECT f1();
+UNLOCK TABLES;
+DROP FUNCTION f1;
+DROP TABLE tm1, t1, t9;
+#
+# Temporary. LOCK TABLES statement that locks a table that has a trigger
+# that inserts into a merge table, so an attempt is made to lock tables
+# of a sub-statement.
+#
+CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
+  INSERT_METHOD=LAST;
+CREATE TABLE t2 (c1 INT) ENGINE=MyISAM;
+CREATE TRIGGER t2_ai AFTER INSERT ON t2
+  FOR EACH ROW INSERT INTO tm1 VALUES(11);
+LOCK TABLE t2 WRITE;
+INSERT INTO t2 VALUES (2);
+SELECT * FROM tm1;
+SELECT * FROM t2;
+UNLOCK TABLES;
+DROP TRIGGER t2_ai;
+DROP TABLE tm1, t1, t2;
+--echo #
+--echo # Don't select MERGE child when trying to get prelocked table.
+--echo #
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
+  INSERT_METHOD=LAST;
+CREATE TRIGGER tm1_ai AFTER INSERT ON tm1
+  FOR EACH ROW INSERT INTO t1 VALUES(11);
+LOCK TABLE tm1 WRITE, t1 WRITE;
+INSERT INTO tm1 VALUES (1);
+SELECT * FROM tm1;
+UNLOCK TABLES;
+LOCK TABLE t1 WRITE, tm1 WRITE;
+INSERT INTO tm1 VALUES (1);
+SELECT * FROM tm1;
+UNLOCK TABLES;
+DROP TRIGGER tm1_ai;
+DROP TABLE tm1, t1;
+
+# Don't resurrect chopped off prelocked tables.
+# The problem is not visible by test results; only by debugging.
+#
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE t3 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE t4 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE t5 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2,t3,t4,t5)
+  INSERT_METHOD=LAST;
+CREATE TRIGGER t2_au AFTER UPDATE ON t2
+  FOR EACH ROW INSERT INTO t3 VALUES(33);
+CREATE FUNCTION f1() RETURNS INT
+  RETURN (SELECT MAX(c1) FROM t4);
+LOCK TABLE tm1 WRITE, t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE, t5 WRITE;
+INSERT INTO t1 VALUES(1);
+INSERT INTO t2 VALUES(2);
+INSERT INTO t3 VALUES(3);
+INSERT INTO t4 VALUES(4);
+INSERT INTO t5 VALUES(5);
+    connect (con1,localhost,root,,);
+    send UPDATE t2, tm1 SET t2.c1=f1();
+connection default;
+# Force reopen in other thread.
+#sleep 1;
+FLUSH TABLES;
+#sleep 1;
+FLUSH TABLES;
+#sleep 1;
+UNLOCK TABLES;
+    connection con1;
+    reap;
+    disconnect con1;
+connection default;
+SELECT * FROM tm1;
+DROP TRIGGER t2_au;
+DROP FUNCTION f1;
+DROP TABLE tm1, t1, t2, t3, t4, t5;
+
+
+
+--echo End of 6.0 tests

=== modified file 'mysys/thr_lock.c'
--- a/mysys/thr_lock.c	2008-06-25 12:44:00 +0000
+++ b/mysys/thr_lock.c	2008-06-25 18:00:10 +0000
@@ -609,6 +609,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_O
     {
       if (lock->write.data->type == TL_WRITE_ONLY)
       {
+        /* purecov: begin tested */
         /* Allow lock owner to bypass TL_WRITE_ONLY. */
         if (!thr_lock_owner_equal(data->owner, lock->write.data->owner))
         {
@@ -617,6 +618,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_O
           result= THR_LOCK_ABORTED;               /* Can't wait for this one */
           goto end;
         }
+        /* purecov: end */
       }
 
       /*

=== modified file 'sql/ha_partition.cc'
--- a/sql/ha_partition.cc	2008-05-27 12:15:44 +0000
+++ b/sql/ha_partition.cc	2008-06-25 18:00:10 +0000
@@ -4751,34 +4751,35 @@ void ha_partition::get_dynamic_partition
 }
 
 
-/*
-  General function to prepare handler for certain behavior
+/**
+  General function to prepare handler for certain behavior.
 
-  SYNOPSIS
-    extra()
+  @param[in]    operation       operation to execute
     operation              Operation type for extra call
 
-  RETURN VALUE
-    >0                     Error code
-    0                      Success
+  @return       status
+    @retval     0               success
+    @retval     >0              error code
+
+  @detail
 
-  DESCRIPTION
   extra() is called whenever the server wishes to send a hint to
   the storage engine. The MyISAM engine implements the most hints.
 
   We divide the parameters into the following categories:
-  1) Parameters used by most handlers
-  2) Parameters used by some non-MyISAM handlers
-  3) Parameters used only by MyISAM
-  4) Parameters only used by temporary tables for query processing
-  5) Parameters only used by MyISAM internally
-  6) Parameters not used at all
-  7) Parameters only used by federated tables for query processing
-  8) Parameters only used by NDB
+  1) Operations used by most handlers
+  2) Operations used by some non-MyISAM handlers
+  3) Operations used only by MyISAM
+  4) Operations only used by temporary tables for query processing
+  5) Operations only used by MyISAM internally
+  6) Operations not used at all
+  7) Operations only used by federated tables for query processing
+  8) Operations only used by NDB
+  9) Operations only used by MERGE
 
   The partition handler need to handle category 1), 2) and 3).
 
-  1) Parameters used by most handlers
+  1) Operations used by most handlers
   -----------------------------------
   HA_EXTRA_RESET:
     This option is used by most handlers and it resets the handler state
@@ -4817,7 +4818,7 @@ void ha_partition::get_dynamic_partition
     ensure disk based tables are flushed at end of query execution.
     Currently is never used.
 
-  2) Parameters used by some non-MyISAM handlers
+  2) Operations used by some non-MyISAM handlers
   ----------------------------------------------
   HA_EXTRA_KEYREAD_PRESERVE_FIELDS:
     This is a strictly InnoDB feature that is more or less undocumented.
@@ -4836,7 +4837,7 @@ void ha_partition::get_dynamic_partition
     SQL constructs.
     Not used by MyISAM.
 
-  3) Parameters used only by MyISAM
+  3) Operations used only by MyISAM
   ---------------------------------
   HA_EXTRA_NORMAL:
     Only used in MyISAM to reset quick mode, not implemented by any other
@@ -4967,7 +4968,7 @@ void ha_partition::get_dynamic_partition
     Only used by MyISAM, called when altering table, closing tables to
     enforce a reopen of the table files.
 
-  4) Parameters only used by temporary tables for query processing
+  4) Operations only used by temporary tables for query processing
   ----------------------------------------------------------------
   HA_EXTRA_RESET_STATE:
     Same as reset() except that buffers are not released. If there is
@@ -4998,7 +4999,7 @@ void ha_partition::get_dynamic_partition
     tables used in query processing.
     Not handled by partition handler.
 
-  5) Parameters only used by MyISAM internally
+  5) Operations only used by MyISAM internally
   --------------------------------------------
   HA_EXTRA_REINIT_CACHE:
     This call reinitialises the READ CACHE described above if there is one
@@ -5033,19 +5034,19 @@ void ha_partition::get_dynamic_partition
   HA_EXTRA_CHANGE_KEY_TO_UNIQUE:
     Only used by MyISAM, never called.
 
-  6) Parameters not used at all
+  6) Operations not used at all
   -----------------------------
   HA_EXTRA_KEY_CACHE:
   HA_EXTRA_NO_KEY_CACHE:
     This parameters are no longer used and could be removed.
 
-  7) Parameters only used by federated tables for query processing
+  7) Operations only used by federated tables for query processing
   ----------------------------------------------------------------
   HA_EXTRA_INSERT_WITH_UPDATE:
     Inform handler that an "INSERT...ON DUPLICATE KEY UPDATE" will be
     executed. This condition is unset by HA_EXTRA_NO_IGNORE_DUP_KEY.
 
-  8) Parameters only used by NDB
+  8) Operations only used by NDB
   ------------------------------
   HA_EXTRA_DELETE_CANNOT_BATCH:
   HA_EXTRA_UPDATE_CANNOT_BATCH:
@@ -5053,6 +5054,14 @@ void ha_partition::get_dynamic_partition
     and should perform them immediately. This may be needed when table has 
     AFTER DELETE/UPDATE triggers which access to subject table.
     These flags are reset by the handler::extra(HA_EXTRA_RESET) call.
+
+  9) Operations only used by MERGE
+  ------------------------------
+  HA_EXTRA_ADD_CHILDREN_LIST:
+  HA_EXTRA_ATTACH_CHILDREN:
+  HA_EXTRA_IS_ATTACHED_CHILDREN:
+  HA_EXTRA_DETACH_CHILDREN:
+    Special actions for MERGE tables. Ignore.
 */
 
 int ha_partition::extra(enum ha_extra_function operation)
@@ -5138,13 +5147,22 @@ int ha_partition::extra(enum ha_extra_fu
     /* Category 7), used by federated handlers */
   case HA_EXTRA_INSERT_WITH_UPDATE:
     DBUG_RETURN(loop_extra(operation));
-    /* Category 8) Parameters only used by NDB */
+    /* Category 8) Operations only used by NDB */
   case HA_EXTRA_DELETE_CANNOT_BATCH:
   case HA_EXTRA_UPDATE_CANNOT_BATCH:
   {
     /* Currently only NDB use the *_CANNOT_BATCH */
     break;
   }
+    /* Category 9) Operations only used by MERGE */
+  case HA_EXTRA_ADD_CHILDREN_LIST:
+  case HA_EXTRA_ATTACH_CHILDREN:
+  case HA_EXTRA_IS_ATTACHED_CHILDREN:
+  case HA_EXTRA_DETACH_CHILDREN:
+  {
+    /* Special actions for MERGE tables. Ignore. */
+    break;
+  }
   default:
   {
     /* Temporary crash to discover what is wrong */

=== modified file 'sql/mysql_priv.h'
--- a/sql/mysql_priv.h	2008-06-20 13:11:20 +0000
+++ b/sql/mysql_priv.h	2008-06-25 18:00:10 +0000
@@ -1326,9 +1326,6 @@ bool tdc_open_view(THD *thd, TABLE_LIST 
 TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name);
 TABLE *find_write_locked_table(TABLE *list, const char *db,
                                const char *table_name);
-void detach_merge_children(TABLE *table, bool clear_refs);
-bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last,
-                          TABLE_LIST *new_child_list, TABLE_LIST **new_last);
 bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias,
                   uint db_stat, uint prgflag,
                   uint ha_open_flags, TABLE *outparam,

=== modified file 'sql/sql_base.cc'
--- a/sql/sql_base.cc	2008-06-25 13:03:37 +0000
+++ b/sql/sql_base.cc	2008-06-25 18:00:10 +0000
@@ -151,6 +151,11 @@ static void check_unused(void)
     I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables);
     while ((entry= it++))
     {
+      /* We must not have TABLEs in the free list that have their file closed. */
+      DBUG_ASSERT(entry->db_stat && entry->file);
+      /* Merge children should be detached from a merge parent */
+      DBUG_ASSERT(! entry->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
+
       if (entry->in_use)
       {
         DBUG_PRINT("error",("Used table is in share's list of unused tables")); /*
purecov: inspected */
@@ -359,6 +364,10 @@ static void table_def_use_table(THD *thd
   /* Add table to list of used tables for this share. */
   table->s->used_tables.push_front(table);
   table->in_use= thd;
+  /* The ex-unused table must be fully functional. */
+  DBUG_ASSERT(table->db_stat && table->file);
+  /* The children must be detached from the table. */
+  DBUG_ASSERT(! table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
 }
 
 
@@ -789,9 +798,6 @@ static void free_cache_entry(TABLE *tabl
 {
   DBUG_ENTER("free_cache_entry");
 
-  /* Assert that MERGE children are not attached before final close. */
-  DBUG_ASSERT(!table->is_children_attached());
-
   /* This should be done before releasing table share. */
   table_def_remove_table(table);
 
@@ -1111,14 +1117,10 @@ static void mark_temp_tables_as_free_for
     {
       table->query_id= 0;
       table->file->ha_reset();
-      /*
-        Detach temporary MERGE children from temporary parent to allow new
-        attach at next open. Do not do the detach, if close_thread_tables()
-        is called from a sub-statement. The temporary table might still be
-        used in the top-level statement.
-      */
-      if (table->child_l || table->parent)
-        detach_merge_children(table, TRUE);
+
+      /* Detach temporary MERGE children from temporary parent. */
+      DBUG_ASSERT(table->file);
+      table->file->extra(HA_EXTRA_DETACH_CHILDREN);
     }
   }
 }
@@ -1297,6 +1299,20 @@ void close_thread_tables(THD *thd,
                           table->s->table_name.str, table));
 #endif
 
+  /* Detach MERGE children after every statement. Even under LOCK TABLES. */
+  for (table= thd->open_tables; table; table= table->next)
+  {
+    /* Table might be in use by some outer statement. */
+    DBUG_PRINT("tcache", ("table: '%s'  query_id: %lu",
+                          table->s->table_name.str, (ulong) table->query_id));
+    if (thd->locked_tables_mode <= LTM_LOCK_TABLES ||
+        table->query_id == thd->query_id)
+    {
+      DBUG_ASSERT(table->file);
+      table->file->extra(HA_EXTRA_DETACH_CHILDREN);
+    }
+  }
+
   /*
     We are assuming here that thd->derived_tables contains ONLY derived
     tables for this substatement. i.e. instead of approach which uses
@@ -1434,12 +1450,6 @@ bool close_thread_table(THD *thd, TABLE 
   safe_mutex_assert_owner(&LOCK_open);
 
   *table_ptr=table->next;
-  /*
-    When closing a MERGE parent or child table, detach the children first.
-    Clear child table references to force new assignment at next open.
-  */
-  if (table->child_l || table->parent)
-    detach_merge_children(table, TRUE);
 
   table->mdl_lock_data= 0;
   if (table->needs_reopen() ||
@@ -1450,8 +1460,9 @@ bool close_thread_table(THD *thd, TABLE 
   }
   else
   {
-    /* Assert that MERGE children are not attached in unused_tables. */
-    DBUG_ASSERT(!table->is_children_attached());
+    /* Avoid to have MERGE tables with attached children in unused_tables. */
+    DBUG_ASSERT(table->file);
+    table->file->extra(HA_EXTRA_DETACH_CHILDREN);
 
     /* Free memory and reset for next loop */
     table->file->ha_reset();
@@ -1900,19 +1911,6 @@ void close_temporary_table(THD *thd, TAB
                           table->s->db.str, table->s->table_name.str,
                           table, table->alias));
 
-  /*
-    When closing a MERGE parent or child table, detach the children
-    first. Clear child table references as MERGE table cannot be
-    reopened after final close of one of its tables.
-
-    This is necessary here because it is sometimes called with attached
-    tables and without prior close_thread_tables(). E.g. in
-    mysql_alter_table(), mysql_rm_table_part2(), mysql_truncate(),
-    drop_open_table().
-  */
-  if (table->child_l || table->parent)
-    detach_merge_children(table, TRUE);
-
   if (table->prev)
   {
     table->prev->next= table->next;
@@ -2419,15 +2417,10 @@ bool open_table(THD *thd, TABLE_LIST *ta
                    table->s->table_name.str);
           DBUG_RETURN(TRUE);
         }
-        /*
-          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->locked_tables_mode == LTM_LOCK_TABLES ||
-             table->query_id == 0) &&
-            !table->parent)
+             table->query_id == 0))
         {
           int distance= ((int) table->reginfo.lock_type -
                          (int) table_list->lock_type);
@@ -2781,6 +2774,9 @@ bool open_table(THD *thd, TABLE_LIST *ta
   table->clear_column_bitmaps();
   table_list->table= table;
   DBUG_ASSERT(table->key_read == 0);
+  /* Tables may be reused in a sub statement. */
+  if (table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN))
+    table->file->extra(HA_EXTRA_DETACH_CHILDREN);
   DBUG_RETURN(FALSE);
 
 err_unlock:
@@ -3484,340 +3480,6 @@ recover_from_failed_open_table_attempt(T
 }
 
 
-/**
-  @brief Add list of MERGE children to a TABLE_LIST list.
-
-  @param[in]    tlist           the parent TABLE_LIST object just opened
-
-  @return status
-    @retval     0               OK
-    @retval     != 0            Error
-
-  @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.
-*/
-
-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' %p", parent->s->db.str,
-                      parent->s->table_name.str, 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);
-  /* Prevent inclusion of another MERGE table. Could make infinite recursion. */
-  if (tlist->parent_l)
-  {
-    my_error(ER_ADMIN_WRONG_MRG_TABLE, MYF(0), tlist->alias);
-    DBUG_RETURN(1);
-  }
-
-  /* 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.
-
-  @param[in]    tlist           the child TABLE_LIST object just opened
-
-  @return status
-    @retval     0               OK
-    @retval     != 0            Error
-
-  @note
-    This is called 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.
-*/
-
-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' %p", parent->s->db.str,
-                      parent->s->table_name.str, 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;
-  DBUG_PRINT("myrg", ("attached parent: '%s'.'%s' %p", parent->s->db.str,
-                      parent->s->table_name.str, parent));
-
-  /*
-    Note that we have the cildren in the thd->open_tables list at this
-    point.
-  */
-
-  DBUG_RETURN(0);
-}
-
-
-/**
-  @brief Detach MERGE children from the parent.
-
-  @note
-    Call this before the first table of a MERGE table (parent or child)
-    is closed.
-
-    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.
-
-    To avoid that we touch a closed children in any way, we must detach
-    the children from the parent when the first belonging table is
-    closed (parent or child).
-
-    All references to the children should be removed on handler level
-    and optionally on table level.
-
-  @note
-    Assure that you call it for a MERGE parent or child only.
-    Either table->child_l or table->parent must be set.
-
-  @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()
-*/
-
-void detach_merge_children(TABLE *table, bool clear_refs)
-{
-  TABLE_LIST *child_l;
-  TABLE *parent= table->child_l ? table : table->parent;
-  bool first_detach;
-  DBUG_ENTER("detach_merge_children");
-  /*
-    Either table->child_l or table->parent must be set. Parent must have
-    child_l set.
-  */
-  DBUG_ASSERT(parent && parent->child_l);
-  DBUG_PRINT("myrg", ("table: '%s'.'%s' %p  clear_refs: %d",
-                      table->s->db.str, table->s->table_name.str,
-                      table, clear_refs));
-  DBUG_PRINT("myrg", ("parent: '%s'.'%s' %p", parent->s->db.str,
-                      parent->s->table_name.str, parent));
-
-  /*
-    In a open_tables() loop it can happen that not all tables have their
-    children attached yet. Also this is called for every child and the
-    parent from close_thread_tables().
-  */
-  if ((first_detach= parent->children_attached))
-  {
-    (void) parent->file->extra(HA_EXTRA_DETACH_CHILDREN);
-    parent->children_attached= FALSE;
-    DBUG_PRINT("myrg", ("detached parent: '%s'.'%s' %p", parent->s->db.str,
-                        parent->s->table_name.str, parent));
-  }
-  else
-    DBUG_PRINT("myrg", ("parent is already detached"));
-
-  if (clear_refs)
-  {
-    /* In any case clear the own parent reference. (***) */
-    table->parent= NULL;
-
-    /*
-      On the first detach, clear all references. If this table is the
-      parent, we still may need to clear the child references. The first
-      detach might not have done this.
-    */
-    if (first_detach || (table == parent))
-    {
-      /* Clear TABLE references to force new assignment at next open. */
-      for (child_l= parent->child_l; ; child_l= child_l->next_global)
-      {
-        /*
-          Do not DBUG_ASSERT(child_l->table); open_tables might be
-          incomplete.
-
-          Clear the parent reference of the children only on the first
-          detach. The children might already be closed. They will clear
-          it themseves when this function is called for them with
-          'clear_refs' true. See above "(***)".
-        */
-        if (first_detach && child_l->table)
-          child_l->table->parent= NULL;
-
-        /* Clear the table reference to force new assignment at next open. */
-        child_l->table= NULL;
-
-        /* Break when this was the last child. */
-        if (&child_l->next_global == parent->child_last_l)
-          break;
-      }
-    }
-  }
-
-  DBUG_VOID_RETURN;
-}
-
-
-/**
-  @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.
-
-  @note
-    This function terminates the child list if the respective '*_last'
-    pointer is non-NULL. Do not call it from a place where the list is
-    embedded in another list and this would break it.
-
-    Terminating the list is required for example in the first
-    reopen_table() after open_tables(). open_tables() requires the end
-    of the list not to be terminated because other tables could follow
-    behind the child list.
-
-    If a '*_last' pointer is NULL, the respective list is assumed to be
-    NULL terminated.
-*/
-
-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: %p  new last addr: %p",
-                      old_last, new_last));
-
-  /* Terminate the lists for easier check of list end. */
-  if (old_last)
-    *old_last= NULL;
-  if (new_last)
-    *new_last= NULL;
-
-  for (;;)
-  {
-    DBUG_PRINT("myrg", ("old list item: %p  new list item: %p",
-                        old_child_list, 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))
-      break;
-    /*
-      Copy TABLE reference. Child TABLE objects are still in place
-      though not necessarily open yet.
-    */
-    DBUG_PRINT("myrg", ("old table ref: %p  replaces new table ref: %p",
-                        old_child_list->table,
-                        new_child_list->table));
-    new_child_list->table= old_child_list->table;
-    /* 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 the list pointers are not both NULL after the loop, then the
-    lists differ. If the are both identical, but not NULL, then they
-    have at least one table in common and hence the rest of the list
-    would be identical too. But in this case the loop woul run until the
-    list end, where both pointers would become NULL.
-  */
-  if (old_child_list != new_child_list)
-    mismatch= TRUE;
-  if (mismatch)
-    my_error(ER_TABLE_DEF_CHANGED, MYF(0));
-
-  DBUG_RETURN(mismatch);
-}
-
-
 /*
   Open all tables in list
 
@@ -3995,19 +3657,6 @@ int open_tables(THD *thd, TABLE_LIST **s
 
     if (error)
     {
-      /*
-        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 (action)
       {
         /*
@@ -4048,7 +3697,7 @@ int open_tables(THD *thd, TABLE_LIST **s
       }
 
       result= -1;				// Fatal error
-      break;
+      goto err;
     }
 
     /*
@@ -4135,16 +3784,15 @@ int open_tables(THD *thd, TABLE_LIST **s
       goto err;
     }
 
-    /* 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)));
-    if ((!thd->locked_tables_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))))
+    /*
+      After opening a MERGE table add the children to the query list of
+      tables, so that they are opened too.
+      Note that placeholders don't have the handler open.
+    */
+    /* MERGE tables need to access parent and child TABLE_LISTs. */
+    DBUG_ASSERT(tables->table->pos_in_table_list == tables);
+    /* Non-MERGE tables ignore this call. */
+    if (tables->table->file->extra(HA_EXTRA_ADD_CHILDREN_LIST))
     {
       result= -1;
       goto err;
@@ -4176,6 +3824,29 @@ process_view_routines:
     }
   }
 
+  /*
+    After successful open of all tables, including MERGE parents and
+    children, attach the children to their parents. At end of statement,
+    the children are detached. Attaching and detaching are always done,
+    even under LOCK TABLES.
+  */
+  for (tables= *start; tables; tables= tables->next_global)
+  {
+    TABLE *tbl= tables->table;
+
+    /* Schema tables may not have a TABLE object here. */
+    if (tbl && tbl->file->ht->db_type == DB_TYPE_MRG_MYISAM)
+    {
+      /* MERGE tables need to access parent and child TABLE_LISTs. */
+      DBUG_ASSERT(tbl->pos_in_table_list == tables);
+      if (tbl->file->extra(HA_EXTRA_ATTACH_CHILDREN))
+      {
+        result= -1;
+        goto err;
+      }
+    }
+  }
+
  err:
   thd_proc_info(thd, 0);
   free_root(&new_frm_mem, MYF(0));              // Free pre-alloced block
@@ -4185,17 +3856,6 @@ process_view_routines:
 
   if (result && tables)
   {
-    /*
-      Some functions determine success as (tables->table != NULL).
-      tables->table is in thd->open_tables. It won't go lost. If the
-      error happens on a MERGE child, clear the parents TABLE reference.
-    */
-    if (tables->parent_l)
-    {
-      if (tables->parent_l->next_global ==
tables->parent_l->table->child_l)
-        tables->parent_l->next_global=
*tables->parent_l->table->child_last_l;
-      tables->parent_l->table= NULL;
-    }
     tables->table= NULL;
   }
   DBUG_PRINT("tcache", ("returning: %d", result));
@@ -4357,7 +4017,7 @@ retry:
     */
     DBUG_ASSERT(table_list->table);
     table= table_list->table;
-    if (table->child_l)
+    if (table->file->ht->db_type == DB_TYPE_MRG_MYISAM)
     {
       /* A MERGE table must not come here. */
       /* purecov: begin tested */
@@ -4782,7 +4442,7 @@ int lock_tables(THD *thd, TABLE_LIST *ta
         and was marked as occupied during open_tables() as free for reuse.
       */
       mark_real_tables_as_free_for_reuse(first_not_own);
-      DBUG_PRINT("info",("prelocked_mode= PRELOCKED"));
+      DBUG_PRINT("info",("locked_tables_mode= LTM_PRELOCKED"));
       thd->locked_tables_mode= LTM_PRELOCKED;
     }
   }
@@ -4815,7 +4475,8 @@ int lock_tables(THD *thd, TABLE_LIST *ta
     if (thd->lex->requires_prelocking())
     {
       mark_real_tables_as_free_for_reuse(first_not_own);
-      DBUG_PRINT("info", ("thd->prelocked_mode= PRELOCKED_UNDER_LOCK_TABLES"));
+      DBUG_PRINT("info",
+                 ("thd->locked_tables_mode= LTM_PRELOCKED_UNDER_LOCK_TABLES"));
       thd->locked_tables_mode= LTM_PRELOCKED_UNDER_LOCK_TABLES;
     }
   }
@@ -8299,9 +7960,7 @@ void close_performance_schema_table(THD 
   /*
     Note that we need to hold LOCK_open while changing the
     open_tables list. Another thread may work on it.
-    (See: mysql_notify_thread_having_shared_lock())
-    Closing a MERGE child before the parent would be fatal if the
-    other thread tries to abort the MERGE lock in between.
+    (See: notify_thread_having_shared_lock())
   */
   while (thd->open_tables)
     found_old_table|= close_thread_table(thd, &thd->open_tables);

=== modified file 'sql/sql_table.cc'
--- a/sql/sql_table.cc	2008-06-19 12:39:58 +0000
+++ b/sql/sql_table.cc	2008-06-25 18:00:10 +0000
@@ -4001,7 +4001,7 @@ static int prepare_for_repair(THD *thd, 
   }
 
   /* A MERGE table must not come here. */
-  DBUG_ASSERT(!table->child_l);
+  DBUG_ASSERT(table->file->ht->db_type != DB_TYPE_MRG_MYISAM);
 
   /*
     REPAIR TABLE ... USE_FRM for temporary tables makes little sense.
@@ -6928,10 +6928,6 @@ view_err:
   }
   else
   {
-    if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
-      goto err_new_table_cleanup;
-    alter_table_manage_keys(table, table->file->indexes_are_disabled(),
-                            alter_info->keys_onoff);
     error= ha_autocommit_or_rollback(thd, 0);
     if (end_active_trans(thd))
       error= 1;

=== modified file 'sql/table.cc'
--- a/sql/table.cc	2008-05-29 12:52:56 +0000
+++ b/sql/table.cc	2008-06-25 18:00:10 +0000
@@ -4608,24 +4608,6 @@ void st_table::mark_columns_needed_for_i
 }
 
 
-/**
-  @brief Check if this is part of a MERGE table with attached children.
-
-  @return       status
-    @retval     TRUE            children are attached
-    @retval     FALSE           no MERGE part or children not attached
-
-  @detail
-    A MERGE table consists of a parent TABLE and zero or more child
-    TABLEs. Each of these TABLEs is called a part of a MERGE table.
-*/
-
-bool st_table::is_children_attached(void)
-{
-  return((child_l && children_attached) ||
-         (parent && parent->children_attached));
-}
-
 /*
   Cleanup this table for re-execution.
 

=== modified file 'sql/table.h'
--- a/sql/table.h	2008-06-19 20:51:30 +0000
+++ b/sql/table.h	2008-06-25 18:00:10 +0000
@@ -581,11 +581,6 @@ private:
 public:
   struct st_table *next, *prev;
 
-  /* For the below MERGE related members see top comment in ha_myisammrg.cc */
-  struct st_table *parent;          /* Set in MERGE child.  Ptr to parent */
-  TABLE_LIST      *child_l;         /* Set in MERGE parent. List of children */
-  TABLE_LIST      **child_last_l;   /* Set in MERGE parent. End of list */
-
   THD	*in_use;                        /* Which thread uses this */
   Field **field;			/* Pointer to fields */
 
@@ -737,8 +732,6 @@ public:
   my_bool insert_or_update;             /* Can be used by the handler */
   my_bool alias_name_used;		/* true if table_name is alias */
   my_bool get_fields_in_item_tree;      /* Signal to fix_field */
-  /* If MERGE children attached to parent. See top comment in ha_myisammrg.cc */
-  my_bool children_attached;
 
   REGINFO reginfo;			/* field connections */
   MEM_ROOT mem_root;
@@ -789,7 +782,6 @@ public:
   */
   inline bool needs_reopen()
   { return s->version != refresh_version; }
-  bool is_children_attached(void);
 };
 
 

=== modified file 'storage/myisammrg/ha_myisammrg.cc'
--- a/storage/myisammrg/ha_myisammrg.cc	2008-05-23 13:54:03 +0000
+++ b/storage/myisammrg/ha_myisammrg.cc	2008-06-25 18:00:10 +0000
@@ -33,40 +33,29 @@
   and hence through open_tables(). When the parent appears in the list
   of tables to open, the initial open of the handler does nothing but
   read the meta file and collect a list of TABLE_LIST objects for the
-  children. This list is attached to the parent TABLE object as
-  TABLE::child_l. The end of the children list is saved in
-  TABLE::child_last_l.
-
-  Back in open_tables(), add_merge_table_list() is called. It updates
-  each list member with the lock type and a back pointer to the parent
-  TABLE_LIST object TABLE_LIST::parent_l. The list is then inserted in
-  the list of tables to open, right behind the parent. Consequently,
-  open_tables() opens the children, one after the other. The TABLE
-  references of the TABLE_LIST objects are implicitly set to the open
-  tables. The children are opened as independent MyISAM tables, right as
-  if they are used by the SQL statement.
-
-  TABLE_LIST::parent_l is required to find the parent 1. when the last
-  child has been opened and children are to be attached, and 2. when an
-  error happens during child open and the child list must be removed
-  from the queuery list. In these cases the current child does not have
-  TABLE::parent set or does not have a TABLE at all respectively.
-
-  When the last child is open, attach_merge_children() is called. It
-  removes the list of children from the open list. Then the children are
-  "attached" to the parent. All required references between parent and
+  children. This list is attached to the handler object as
+  ha_myisammrg::children_l. The end of the children list is saved in
+  ha_myisammrg::children_last_l.
+
+  Back in open_tables(), handler::extra(HA_EXTRA_ADD_CHILDREN_LIST) is
+  called. It updates each list member with the lock type and a back
+  pointer to the parent TABLE_LIST object TABLE_LIST::parent_l. The list
+  is then inserted in the list of tables to open, right behind the
+  parent. Consequently, open_tables() opens the children, one after the
+  other. The TABLE references of the TABLE_LIST objects are implicitly
+  set to the open tables by open_table(). The children are opened as
+  independent MyISAM tables, right as if they are used by the SQL
+  statement.
+
+  When all tables from the statement query list are open,
+  handler::extra(HA_EXTRA_ATTACH_CHILDREN) is called. It "attaches" the
+  children to the parent. All required references between parent and
   children are set up.
 
   The MERGE storage engine sets up an array with references to the
   low-level MyISAM table objects (MI_INFO). It remembers the state of
   the table in MYRG_INFO::children_attached.
 
-  Every child TABLE::parent references the parent TABLE object. That way
-  TABLE objects belonging to a MERGE table can be identified.
-  TABLE::parent is required because the parent and child TABLE objects
-  can live longer than the parent TABLE_LIST object. So the path
-  child->pos_in_table_list->parent_l->table can be broken.
-
   If necessary, the compatibility of parent and children is checked.
   This check is necessary when any of the objects are reopend. This is
   detected by comparing the current table def version against the
@@ -80,14 +69,20 @@
   myisammrg_attach_children_callback() sets it ot TRUE if a table
   def version mismatches the remembered child def version.
 
-  Finally the parent TABLE::children_attached is set.
+  The children chain remains in the statement query list until the table
+  is closed or the children are detached. This is done so that the
+  children are locked by lock_tables().
+
+  At statement end the children are detached. At the next statement
+  begin the open-add-attach sequence repeats. There is no exception for
+  LOCK TABLES. The fresh establishment of the parent-child relationship
+  before every statement catches numerous cases of ALTER/FLUSH/DROP/etc
+  of parent or children during LOCK TABLES.
 
   ---
 
   On parent open the storage engine structures are allocated and initialized.
   They stay with the open table until its final close.
-
-
 */
 
 #ifdef USE_PRAGMA_IMPLEMENTATION
@@ -117,7 +112,10 @@ static handler *myisammrg_create_handler
 
 ha_myisammrg::ha_myisammrg(handlerton *hton, TABLE_SHARE *table_arg)
   :handler(hton, table_arg), file(0)
-{}
+{
+  init_sql_alloc(&children_mem_root, max(4 * sizeof(TABLE_LIST), FN_REFLEN) +
+                 ALLOC_ROOT_MIN_BLOCK_SIZE, 0);
+}
 
 
 /**
@@ -125,7 +123,9 @@ ha_myisammrg::ha_myisammrg(handlerton *h
 */
 
 ha_myisammrg::~ha_myisammrg(void)
-{}
+{
+  free_root(&children_mem_root, MYF(0));
+}
 
 
 static const char *ha_myisammrg_exts[] = {
@@ -176,45 +176,48 @@ const char *ha_myisammrg::index_type(uin
 
 
 /**
-  @brief Callback function for open of a MERGE parent table.
-
-  @detail This function adds a TABLE_LIST object for a MERGE child table
-    to a list of tables of the parent TABLE object. It is called for
-    each child table.
-
-    The list of child TABLE_LIST objects is kept in the TABLE object of
-    the parent for the whole life time of the MERGE table. It is
-    inserted in the statement list behind the MERGE parent TABLE_LIST
-    object when the MERGE table is opened. It is removed from the
-    statement list after the last child is opened.
-
-    All memeory used for the child TABLE_LIST objects and the strings
-    referred by it are taken from the parent TABLE::mem_root. Thus they
-    are all freed implicitly at the final close of the table.
-
-    TABLE::child_l -> TABLE_LIST::next_global -> TABLE_LIST::next_global
-    #                 #               ^          #               ^
-    #                 #               |          #               |
-    #                 #               +--------- TABLE_LIST::prev_global
-    #                 #                                          |
-    #           |<--- TABLE_LIST::prev_global                    |
-    #                                                            |
-    TABLE::child_last_l -----------------------------------------+
+  Callback function for open of a MERGE parent table.
 
   @param[in]    callback_param  data pointer as given to myrg_parent_open()
+                                this is used to pass the handler handle
   @param[in]    filename        file name of MyISAM table
                                 without extension.
 
   @return status
     @retval     0               OK
     @retval     != 0            Error
+
+  @detail
+
+    This function adds a TABLE_LIST object for a MERGE child table to a
+    list of tables in the parent handler object. It is called for each
+    child table.
+
+    The list of child TABLE_LIST objects is kept in the handler object
+    of the parent for the whole life time of the MERGE table. It is
+    inserted in the statement query list behind the MERGE parent
+    TABLE_LIST object when the MERGE table is opened. It is removed from
+    the statement query list at end of statement or at children detach.
+
+    All memory used for the child TABLE_LIST objects and the strings
+    referred by it are taken from the parent
+    ha_myisammrg::children_mem_root. Thus they are all freed implicitly at
+    the final close of the table.
+
+    children_l -> TABLE_LIST::next_global -> TABLE_LIST::next_global
+    #             #               ^          #               ^
+    #             #               |          #               |
+    #             #               +--------- TABLE_LIST::prev_global
+    #             #                                          |
+    #       |<--- TABLE_LIST::prev_global                    |
+    #                                                        |
+    children_last_l -----------------------------------------+
 */
 
 static int myisammrg_parent_open_callback(void *callback_param,
                                           const char *filename)
 {
-  ha_myisammrg  *ha_myrg;
-  TABLE         *parent;
+  ha_myisammrg  *ha_myrg= (ha_myisammrg*) callback_param;
   TABLE_LIST    *child_l;
   const char    *db;
   const char    *table_name;
@@ -240,11 +243,8 @@ static int myisammrg_parent_open_callbac
   dirlen-= db - dir_path; /* This is now the length of 'db'. */
   DBUG_PRINT("myrg", ("open: '%s'.'%s'", db, table_name));
 
-  ha_myrg= (ha_myisammrg*) callback_param;
-  parent= ha_myrg->table_ptr();
-
   /* Get a TABLE_LIST object. */
-  if (!(child_l= (TABLE_LIST*) alloc_root(&parent->mem_root,
+  if (!(child_l= (TABLE_LIST*) alloc_root(&ha_myrg->children_mem_root,
                                           sizeof(TABLE_LIST))))
   {
     /* purecov: begin inspected */
@@ -256,68 +256,211 @@ static int myisammrg_parent_open_callbac
 
   /* Set database (schema) name. */
   child_l->db_length= dirlen;
-  child_l->db= strmake_root(&parent->mem_root, db, dirlen);
+  child_l->db= strmake_root(&ha_myrg->children_mem_root, db, dirlen);
   /* Set table name. */
   child_l->table_name_length= strlen(table_name);
-  child_l->table_name= strmake_root(&parent->mem_root, table_name,
+  child_l->table_name= strmake_root(&ha_myrg->children_mem_root, table_name,
                                     child_l->table_name_length);
   /* Convert to lowercase if required. */
   if (lower_case_table_names && child_l->table_name_length)
+  {
+    /* purecov: begin tested */
     child_l->table_name_length= my_casedn_str(files_charset_info,
                                               child_l->table_name);
+    /* purecov: end */
+  }
   /* Set alias. */
   child_l->alias= child_l->table_name;
 
-  /*
-    FIXME: Actually we should use some other mem-root here.
-           To be fixed once Ingo pushes his patch for WL4144.
-  */
-  alloc_mdl_locks(child_l, &parent->mem_root);
-
   /* Initialize table map to 'undefined'. */
   child_l->init_child_def_version();
 
-  /* Link TABLE_LIST object into the parent list. */
-  if (!parent->child_last_l)
+  /* Link TABLE_LIST object into the children list. */
+  if (ha_myrg->children_last_l)
+    child_l->prev_global= ha_myrg->children_last_l;
+  else
   {
-    /* Initialize parent->child_last_l when handling first child. */
-    parent->child_last_l= &parent->child_l;
+    /* Initialize ha_myrg->children_last_l when handling first child. */
+    ha_myrg->children_last_l= &ha_myrg->children_l;
   }
-  *parent->child_last_l= child_l;
-  child_l->prev_global= parent->child_last_l;
-  parent->child_last_l= &child_l->next_global;
+  *ha_myrg->children_last_l= child_l;
+  ha_myrg->children_last_l= &child_l->next_global;
 
   DBUG_RETURN(0);
 }
 
 
 /**
-  @brief Callback function for attaching a MERGE child table.
+  Open a MERGE parent table, but not its children.
 
-  @detail This function retrieves the MyISAM table handle from the
-    next child table. It is called for each child table.
+  @param[in]    name            MERGE table path name
+  @param[in]    mode            read/write mode, unused
+  @param[in]    test_if_locked  open flags
+
+  @return       status
+  @retval     0               OK
+  @retval     -1              Error, my_errno gives reason
+
+  @detail
+  This function initializes the MERGE storage engine structures
+  and adds a child list of TABLE_LIST to the parent handler.
+*/
 
-  @param[in]    callback_param      data pointer as given to
-                                    myrg_attach_children()
+int ha_myisammrg::open(const char *name, int mode __attribute__((unused)),
+                       uint test_if_locked)
+{
+  DBUG_ENTER("ha_myisammrg::open");
+  DBUG_PRINT("myrg", ("name: '%s'  table: 0x%lx", name, (long) table));
+  DBUG_PRINT("myrg", ("test_if_locked: %u", test_if_locked));
+
+  /* Must not be used when table is open. */
+  DBUG_ASSERT(!this->file);
+
+  /* Save for later use. */
+  this->test_if_locked= test_if_locked;
+
+  /* In case this handler was open and closed before, free old data. */
+  free_root(&this->children_mem_root, MYF(MY_MARK_BLOCKS_FREE));
+
+  /*
+    Initialize variables that are used, modified, and/or set by
+    myisammrg_parent_open_callback().
+    'children_l' is the head of the children chain.
+    'children_last_l' points to the end of the children chain.
+    'my_errno' is set by myisammrg_parent_open_callback() in
+    case of an error.
+  */
+  children_l= NULL;
+  children_last_l= NULL;
+  my_errno= 0;
+
+  /* retrieve children table list. */
+  if (!(file= myrg_parent_open(name, myisammrg_parent_open_callback, this)))
+  {
+    /* purecov: begin inspected */
+    DBUG_PRINT("error", ("my_errno %d", my_errno));
+    DBUG_RETURN(my_errno ? my_errno : -1);
+    /* purecov: end */
+  }
+  DBUG_PRINT("myrg", ("MYRG_INFO: 0x%lx  child tables: %u",
+                      (long) file, file->tables));
+  DBUG_RETURN(0);
+}
+
+
+/**
+  Add list of MERGE children to a TABLE_LIST chain.
+
+  @return status
+    @retval     0               OK
+    @retval     != 0            Error
+
+  @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.
+*/
+
+int ha_myisammrg::add_children_list(void)
+{
+  TABLE_LIST  *parent_l= this->table->pos_in_table_list;
+  TABLE_LIST  *child_l;
+  THD         *thd= current_thd;
+  DBUG_ENTER("ha_myisammrg::add_children_list");
+  DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", this->table->s->db.str,
+                      this->table->s->table_name.str, (long) this->table));
+
+  /* Must call this with open table. */
+  DBUG_ASSERT(this->file);
+
+  /* Ignore this for empty MERGE tables (UNION=()). */
+  if (!this->file->tables)
+  {
+    DBUG_PRINT("myrg", ("empty merge table union"));
+    goto end;
+  }
+
+  /* Must not call this with attached children. */
+  DBUG_ASSERT(!this->file->children_attached);
+
+  /* Must not call this with children list in place. */
+  DBUG_ASSERT(parent_l->next_global != this->children_l);
+
+  /*
+    Prevent inclusion of another MERGE table, which could make infinite
+    recursion.
+  */
+  if (parent_l->parent_l)
+  {
+    my_error(ER_ADMIN_WRONG_MRG_TABLE, MYF(0), parent_l->alias);
+    DBUG_RETURN(1);
+  }
+
+  /* Fix children. */
+  DBUG_ASSERT(this->children_l);
+  for (child_l= this->children_l; ; child_l= child_l->next_global)
+  {
+    DBUG_ASSERT(!child_l->table);
+
+    /* Set lock type. */
+    child_l->lock_type= parent_l->lock_type;
+
+    /* Set parent reference. Used to detect MERGE in children list. */
+    child_l->parent_l= parent_l;
+
+    /* Copy select_lex. Used in unique_table() at least. */
+    child_l->select_lex= parent_l->select_lex;
+
+    child_l->mdl_lock_data= NULL; /* Safety, if alloc_mdl_locks fails. */
+
+    /* Break when this was the last child. */
+    if (&child_l->next_global == this->children_last_l)
+      break;
+  }
+
+  alloc_mdl_locks(children_l,
+                  thd->locked_tables_root ? thd->locked_tables_root :
+                  thd->mem_root);
+
+  /* Insert children into the table list. */
+  if (parent_l->next_global)
+    parent_l->next_global->prev_global= this->children_last_l;
+  *this->children_last_l= parent_l->next_global;
+  parent_l->next_global= this->children_l;
+  this->children_l->prev_global= &parent_l->next_global;
+
+end:
+  DBUG_RETURN(0);
+}
+
+
+/**
+  Callback function for attaching a MERGE child table.
+
+  @param[in]    callback_param  data pointer as given to myrg_attach_children()
+                                this is used to pass the handler handle
 
   @return       pointer to open MyISAM table structure
     @retval     !=NULL                  OK, returning pointer
     @retval     NULL, my_errno == 0     Ok, no more child tables
     @retval     NULL, my_errno != 0     error
+
+  @detail
+    This function retrieves the MyISAM table handle from the
+    next child table. It is called for each child table.
 */
 
 static MI_INFO *myisammrg_attach_children_callback(void *callback_param)
 {
-  ha_myisammrg  *ha_myrg;
-  TABLE         *parent;
+  ha_myisammrg  *ha_myrg= (ha_myisammrg*) callback_param;
+  TABLE         *parent= ha_myrg->table_ptr();
   TABLE         *child;
   TABLE_LIST    *child_l;
   MI_INFO       *myisam;
   DBUG_ENTER("myisammrg_attach_children_callback");
 
   my_errno= 0;
-  ha_myrg= (ha_myisammrg*) callback_param;
-  parent= ha_myrg->table_ptr();
 
   /* Get child list item. */
   child_l= ha_myrg->next_child_attach;
@@ -327,13 +470,11 @@ static MI_INFO *myisammrg_attach_childre
     DBUG_RETURN(NULL);
   }
   child= child_l->table;
-  DBUG_PRINT("myrg", ("child table: '%s'.'%s' %p", child->s->db.str,
-                      child->s->table_name.str, child));
   /*
     Prepare for next child. Used as child_l in next call to this function.
     We cannot rely on a NULL-terminated chain.
   */
-  if (&child_l->next_global == parent->child_last_l)
+  if (&child_l->next_global == ha_myrg->children_last_l)
   {
     DBUG_PRINT("myrg", ("attaching last child"));
     ha_myrg->next_child_attach= NULL;
@@ -341,9 +482,6 @@ static MI_INFO *myisammrg_attach_childre
   else
     ha_myrg->next_child_attach= child_l->next_global;
 
-  /* Set parent reference. */
-  child->parent= parent;
-
   /*
     Do a quick compatibility check. The table def version is set when
     the table share is created. The child def version is copied
@@ -393,59 +531,24 @@ static MI_INFO *myisammrg_attach_childre
 
 
 /**
-  @brief Open a MERGE parent table, not its children.
+  Attach children to a MERGE table.
 
-  @detail This function initializes the MERGE storage engine structures
-    and adds a child list of TABLE_LIST to the parent TABLE.
-
-  @param[in]    name            MERGE table path name
-  @param[in]    mode            read/write mode, unused
-  @param[in]    test_if_locked  open flags
-
-  @return       status
+  @return status
     @retval     0               OK
-    @retval     -1              Error, my_errno gives reason
-*/
-
-int ha_myisammrg::open(const char *name, int mode __attribute__((unused)),
-                       uint test_if_locked)
-{
-  DBUG_ENTER("ha_myisammrg::open");
-  DBUG_PRINT("myrg", ("name: '%s'  table: %p", name, table));
-  DBUG_PRINT("myrg", ("test_if_locked: %u", test_if_locked));
-
-  /* Save for later use. */
-  this->test_if_locked= test_if_locked;
-
-  /* retrieve children table list. */
-  my_errno= 0;
-  if (!(file= myrg_parent_open(name, myisammrg_parent_open_callback, this)))
-  {
-    DBUG_PRINT("error", ("my_errno %d", my_errno));
-    DBUG_RETURN(my_errno ? my_errno : -1);
-  }
-  DBUG_PRINT("myrg", ("MYRG_INFO: %p", file));
-  DBUG_RETURN(0);
-}
-
-
-/**
-  @brief Attach children to a MERGE table.
+    @retval     != 0            Error, my_errno gives reason
 
-  @detail Let the storage engine attach its children through a callback
+  @detail
+    Let the storage engine attach its children through a callback
     function. Check table definitions for consistency.
 
-  @note Special thd->open_options may be in effect. We can make use of
+  @note
+    Special thd->open_options may be in effect. We can make use of
     them in attach. I.e. we use HA_OPEN_FOR_REPAIR to report the names
     of mismatching child tables. We cannot transport these options in
     ha_myisammrg::test_if_locked because they may change after the
     parent is opened. The parent is kept open in the table cache over
     multiple statements and can be used by other threads. Open options
     can change over time.
-
-  @return status
-    @retval     0               OK
-    @retval     != 0            Error, my_errno gives reason
 */
 
 int ha_myisammrg::attach_children(void)
@@ -460,8 +563,27 @@ int ha_myisammrg::attach_children(void)
   DBUG_PRINT("myrg", ("table: '%s'.'%s' %p", table->s->db.str,
                       table->s->table_name.str, table));
   DBUG_PRINT("myrg", ("test_if_locked: %u", this->test_if_locked));
+
+  /* Must call this with open table. */
+  DBUG_ASSERT(this->file);
+
+  /*
+    A MERGE table with no children (empty union) is always seen as
+    attached internally.
+  */
+  if (!this->file->tables)
+  {
+    DBUG_PRINT("myrg", ("empty merge table union"));
+    goto end;
+  }
+  DBUG_PRINT("myrg", ("child tables: %u", this->file->tables));
+
+  /* Must not call this with attached children. */
   DBUG_ASSERT(!this->file->children_attached);
 
+  /* Must call this with children list in place. */
+  DBUG_ASSERT(this->table->pos_in_table_list->next_global ==
this->children_l);
+
   /*
     Initialize variables that are used, modified, and/or set by
     myisammrg_attach_children_callback().
@@ -474,7 +596,7 @@ int ha_myisammrg::attach_children(void)
     'my_errno' is set by myisammrg_attach_children_callback() in
     case of an error.
   */
-  next_child_attach= table->child_l;
+  next_child_attach= this->children_l;
   need_compat_check= FALSE;
   my_errno= 0;
 
@@ -482,8 +604,8 @@ int ha_myisammrg::attach_children(void)
                            current_thd->open_options,
                            myisammrg_attach_children_callback, this))
   {
-    DBUG_PRINT("error", ("my_errno %d", my_errno));
-    DBUG_RETURN(my_errno ? my_errno : -1);
+    error= my_errno;
+    goto err;
   }
   DBUG_PRINT("myrg", ("calling myrg_extrafunc"));
   myrg_extrafunc(file, query_cache_invalidate_by_MyISAM_filename_ref);
@@ -510,7 +632,11 @@ int ha_myisammrg::attach_children(void)
       DBUG_PRINT("error",("reclength: %lu  mean_rec_length: %lu",
                           table->s->reclength, stats.mean_rec_length));
       if (test_if_locked & HA_OPEN_FOR_REPAIR)
+      {
+        /* purecov: begin inspected */
         myrg_print_wrong_table(file->open_tables->table->filename);
+        /* purecov: end */
+      }
       error= HA_ERR_WRONG_MRG_TABLE_DEF;
       goto err;
     }
@@ -541,20 +667,23 @@ int ha_myisammrg::attach_children(void)
           my_free((uchar*) recinfo, MYF(0));
           goto err;
         }
+        /* purecov: begin inspected */
         myrg_print_wrong_table(u_table->table->filename);
+        /* purecov: end */
       }
     }
     my_free((uchar*) recinfo, MYF(0));
     if (error == HA_ERR_WRONG_MRG_TABLE_DEF)
-      goto err;
+      goto err; /* purecov: inspected */
 
     /* All checks passed so far. Now update child def version. */
-    for (child_l= table->child_l; ; child_l= child_l->next_global)
+    DBUG_ASSERT(this->children_l);
+    for (child_l= this->children_l; ; child_l= child_l->next_global)
     {
       child_l->set_child_def_version(
         child_l->table->s->get_table_def_version());
 
-      if (&child_l->next_global == table->child_last_l)
+      if (&child_l->next_global == this->children_last_l)
         break;
     }
   }
@@ -567,50 +696,115 @@ int ha_myisammrg::attach_children(void)
     goto err;
   }
 #endif
+
+ end:
   DBUG_RETURN(0);
 
 err:
-  myrg_detach_children(file);
+  DBUG_PRINT("error", ("attaching MERGE children failed: %d", error));
+  print_error(error, MYF(0));
+  detach_children();
   DBUG_RETURN(my_errno= error);
 }
 
 
 /**
-  @brief Detach all children from a MERGE table.
-
-  @note Detach must not touch the children in any way.
-    They may have been closed at ths point already.
-    All references to the children should be removed.
+  Detach all children from a MERGE table and from the query list of tables.
 
   @return status
     @retval     0               OK
     @retval     != 0            Error, my_errno gives reason
+
+  @note
+    Detach must not touch the child TABLE objects in any way.
+    They may have been closed at ths point already.
+    All references to the children should be removed.
 */
 
 int ha_myisammrg::detach_children(void)
 {
+  TABLE_LIST *child_l;
   DBUG_ENTER("ha_myisammrg::detach_children");
-  DBUG_ASSERT(this->file && this->file->children_attached);
+
+  /* Must call this with open table. */
+  DBUG_ASSERT(this->file);
+
+  /* A MERGE table with no children (empty union) cannot be detached. */
+  if (!this->file->tables)
+  {
+    DBUG_PRINT("myrg", ("empty merge table union"));
+    goto end;
+  }
+
+  /* Clear TABLE references. */
+  DBUG_ASSERT(this->children_l);
+  for (child_l= this->children_l; ; child_l= child_l->next_global)
+  {
+    /*
+      Do not DBUG_ASSERT(child_l->table); open_tables might be
+      incomplete.
+
+      Clear the table reference.
+    */
+    child_l->table= NULL;
+
+    /* Break when this was the last child. */
+    if (&child_l->next_global == this->children_last_l)
+      break;
+  }
+
+  /*
+    Remove children from the table list. This won't fail if called
+    twice. The list is terminated after removal.
+
+    If the parent is LEX::query_tables_own_last and pre-locked tables
+    follow (tables used by stored functions or triggers), the children
+    are inserted behind the parent and before the pre-locked tables. But
+    we do not adjust LEX::query_tables_own_last. The pre-locked tables
+    could have chopped off the list by clearing
+    *LEX::query_tables_own_last. This did also chop off the children. If
+    we would copy the reference from *this->children_last_l in this
+    case, we would put the chopped off pre-locked tables back to the
+    list. So we refrain from copying it back, if the destination has
+    been set to NULL meanwhile.
+  */
+  if (this->children_l->prev_global &&
*this->children_l->prev_global)
+    *this->children_l->prev_global= *this->children_last_l;
+  if (*this->children_last_l)
+    (*this->children_last_l)->prev_global= this->children_l->prev_global;
+
+  /* Terminate child list. So it cannot be tried to remove again. */
+  *this->children_last_l= NULL;
+  this->children_l->prev_global= NULL;
+
+  if (!this->file->children_attached)
+  {
+    DBUG_PRINT("myrg", ("merge children are already detached"));
+    goto end;
+  }
 
   if (myrg_detach_children(this->file))
   {
     /* purecov: begin inspected */
-    DBUG_PRINT("error", ("my_errno %d", my_errno));
+    print_error(my_errno, MYF(0));
     DBUG_RETURN(my_errno ? my_errno : -1);
     /* purecov: end */
   }
+
+ end:
   DBUG_RETURN(0);
 }
 
 
 /**
-  @brief Close a MERGE parent table, not its children.
-
-  @note The children are expected to be closed separately by the caller.
+  Close a MERGE parent table, but not its children.
 
   @return status
     @retval     0               OK
     @retval     != 0            Error, my_errno gives reason
+
+  @note
+    The children are expected to be closed separately by the caller.
 */
 
 int ha_myisammrg::close(void)
@@ -618,10 +812,11 @@ int ha_myisammrg::close(void)
   int rc;
   DBUG_ENTER("ha_myisammrg::close");
   /*
-    Children must not be attached here. Unless the MERGE table has no
-    children. In this case children_attached is always true.
+    There are cases where children are not explicitly detached before
+    close. detach_children() protects itself against double detach.
   */
-  DBUG_ASSERT(!this->file->children_attached || !this->file->tables);
+  detach_children();
+
   rc= myrg_close(file);
   file= 0;
   DBUG_RETURN(rc);
@@ -863,13 +1058,23 @@ int ha_myisammrg::info(uint flag)
 
 int ha_myisammrg::extra(enum ha_extra_function operation)
 {
-  if (operation == HA_EXTRA_ATTACH_CHILDREN)
+  if (operation == HA_EXTRA_ADD_CHILDREN_LIST)
+  {
+    int rc= add_children_list();
+    return(rc);
+  }
+  else if (operation == HA_EXTRA_ATTACH_CHILDREN)
   {
     int rc= attach_children();
     if (!rc)
       (void) extra(HA_EXTRA_NO_READCHECK); // Not needed in SQL
     return(rc);
   }
+  else if (operation == HA_EXTRA_IS_ATTACHED_CHILDREN)
+  {
+    /* For the upper layer pretend empty MERGE union is never attached. */
+    return(file && file->tables && file->children_attached);
+  }
   else if (operation == HA_EXTRA_DETACH_CHILDREN)
   {
     /*
@@ -890,6 +1095,7 @@ int ha_myisammrg::extra(enum ha_extra_fu
 
 int ha_myisammrg::reset(void)
 {
+  /* This is normally called with detached children. */
   return myrg_reset(file);
 }
 
@@ -905,24 +1111,19 @@ int ha_myisammrg::extra_opt(enum ha_extr
 
 int ha_myisammrg::external_lock(THD *thd, int lock_type)
 {
-  MYRG_TABLE *tmp;
-  DBUG_ASSERT(this->file->children_attached);
-  for (tmp= file->open_tables; tmp != file->end_table; tmp++)
-    tmp->table->in_use.data= thd;
-  return myrg_lock_database(file,lock_type);
+  /*
+    This can be called with no children attached. E.g. FLUSH TABLES
+    unlocks and re-locks tables under LOCK TABLES, but it does not open
+    them first. So they are detached all the time. But locking of the
+    children should work anyway because thd->open_tables is not changed
+    during FLUSH TABLES.
+  */
+  return 0;
 }
 
 uint ha_myisammrg::lock_count(void) const
 {
-  /*
-    Return the real lock count even if the children are not attached.
-    This method is used for allocating memory. If we would return 0
-    to another thread (e.g. doing FLUSH TABLE), and attach the children
-    before the other thread calls store_lock(), then we would return
-    more locks in store_lock() than we claimed by lock_count(). The
-    other tread would overrun its memory.
-  */
-  return file->tables;
+  return 0;
 }
 
 
@@ -930,37 +1131,6 @@ THR_LOCK_DATA **ha_myisammrg::store_lock
 					 THR_LOCK_DATA **to,
 					 enum thr_lock_type lock_type)
 {
-  MYRG_TABLE *open_table;
-
-  /*
-    This method can be called while another thread is attaching the
-    children. If the processor reorders instructions or write to memory,
-    'children_attached' could be set before 'open_tables' has all the
-    pointers to the children. Use of a mutex here and in
-    myrg_attach_children() forces consistent data.
-  */
-  pthread_mutex_lock(&this->file->mutex);
-
-  /*
-    When MERGE table is open, but not yet attached, other threads
-    could flush it, which means call mysql_lock_abort_for_thread()
-    on this threads TABLE. 'children_attached' is FALSE in this
-    situaton. Since the table is not locked, return no lock data.
-  */
-  if (!this->file->children_attached)
-    goto end; /* purecov: tested */
-
-  for (open_table=file->open_tables ;
-       open_table != file->end_table ;
-       open_table++)
-  {
-    *(to++)= &open_table->table->lock;
-    if (lock_type != TL_IGNORE && open_table->table->lock.type ==
TL_UNLOCK)
-      open_table->table->lock.type=lock_type;
-  }
-
- end:
-  pthread_mutex_unlock(&this->file->mutex);
   return to;
 }
 
@@ -1049,7 +1219,7 @@ int ha_myisammrg::create(const char *nam
   /* Allocate a table_names array in thread mem_root. */
   if (!(table_names= (const char**)
         thd->alloc((create_info->merge_list.elements+1) * sizeof(char*))))
-    DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+    DBUG_RETURN(HA_ERR_OUT_OF_MEM); /* purecov: inspected */
 
   /* Create child path names. */
   for (pos= table_names; tables; tables= tables->next_local)
@@ -1159,7 +1329,7 @@ bool ha_myisammrg::check_if_incompatible
 
 int ha_myisammrg::check(THD* thd, HA_CHECK_OPT* check_opt)
 {
-  return HA_ADMIN_OK;
+  return this->file->children_attached ? HA_ADMIN_OK : HA_ADMIN_CORRUPT;
 }
 
 

=== modified file 'storage/myisammrg/ha_myisammrg.h'
--- a/storage/myisammrg/ha_myisammrg.h	2008-04-25 21:45:58 +0000
+++ b/storage/myisammrg/ha_myisammrg.h	2008-06-25 18:00:10 +0000
@@ -27,6 +27,9 @@ class ha_myisammrg: public handler
   MYRG_INFO *file;
 
  public:
+  MEM_ROOT      children_mem_root;      /* mem root for children list */
+  TABLE_LIST    *children_l;            /* children list */
+  TABLE_LIST    **children_last_l;      /* children list end */
   TABLE_LIST    *next_child_attach;     /* next child to attach */
   uint          test_if_locked;         /* flags from ::open() */
   bool          need_compat_check;      /* if need compatibility check */
@@ -58,6 +61,7 @@ class ha_myisammrg: public handler
   { return ulonglong2double(stats.data_file_length) / IO_SIZE + file->tables; }
 
   int open(const char *name, int mode, uint test_if_locked);
+  int add_children_list(void);
   int attach_children(void);
   int detach_children(void);
   int close(void);

=== modified file 'storage/myisammrg/myrg_extra.c'
--- a/storage/myisammrg/myrg_extra.c	2007-11-15 19:25:43 +0000
+++ b/storage/myisammrg/myrg_extra.c	2008-06-25 18:00:10 +0000
@@ -75,12 +75,17 @@ int myrg_reset(MYRG_INFO *info)
   MYRG_TABLE *file;
   DBUG_ENTER("myrg_reset");
 
-  if (!info->children_attached)
-    DBUG_RETURN(1);
   info->cache_in_use=0;
   info->current_table=0;
   info->last_used_table= info->open_tables;
-  
+
+  /*
+    This is normally called with detached children.
+    Return OK as this is the normal case.
+  */
+  if (!info->children_attached)
+    DBUG_RETURN(0);
+
   for (file=info->open_tables ; file != info->end_table ; file++)
   {
     int error;

Thread
bzr commit into mysql-6.0 branch (konstantin:2675) WL#4144, WL#3726Konstantin Osipov25 Jun