List:Commits« Previous MessageNext Message »
From:Ingo Struewing Date:June 10 2008 6:06pm
Subject:bk commit into 6.0 tree (istruewing:1.2777) WL#4144
View as plain text  
Below is the list of changes that have just been committed into a local
6.0 repository of istruewing.  When istruewing does a push these changes
will be propagated to the main repository and, within 24 hours after the
push, to the public repository.
For information on how to access the public repository
see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html

ChangeSet@stripped, 2008-06-10 18:06:53+02:00, istruewing@stripped +15 -0
  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.

  include/my_base.h@stripped, 2008-06-10 18:06:51+02:00, istruewing@stripped +3 -2
    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-big.result@stripped, 2008-06-10 18:06:51+02:00, istruewing@stripped +0
-51
    WL#4144 - Lock MERGE engine children
    Fixed test result.

  mysql-test/r/merge.result@stripped, 2008-06-10 18:06:51+02:00, istruewing@stripped +241
-19
    WL#4144 - Lock MERGE engine children
    Fixed test result.

  mysql-test/t/merge-big.test@stripped, 2008-06-10 18:06:51+02:00, istruewing@stripped +8
-76
    WL#4144 - Lock MERGE engine children
    Repaired the test case.
    Removed tests for INSERT ... SELECT, which is disabled for MERGE.

  mysql-test/t/merge.test@stripped, 2008-06-10 18:06:51+02:00, istruewing@stripped +307
-16
    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@stripped, 2008-06-10 18:06:51+02:00, istruewing@stripped +2 -0
    WL#4144 - Lock MERGE engine children
    Added purecov comments.

  sql/ha_partition.cc@stripped, 2008-06-10 18:06:51+02:00, istruewing@stripped +44 -27
    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@stripped, 2008-06-10 18:06:51+02:00, istruewing@stripped +0 -3
    WL#4144 - Lock MERGE engine children
    Removed declarations for removed functions.

  sql/sql_base.cc@stripped, 2008-06-10 18:06:51+02:00, istruewing@stripped +204 -699
    WL#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@stripped, 2008-06-10 18:06:51+02:00, istruewing@stripped +1 -1
    WL#4144 - Lock MERGE engine children
    Changed detection of MERGE tables.

  sql/table.cc@stripped, 2008-06-10 18:06:51+02:00, istruewing@stripped +0 -18
    WL#4144 - Lock MERGE engine children
    Moved is_children_attached() method from TABLE to ha_myisammrg.

  sql/table.h@stripped, 2008-06-10 18:06:51+02:00, istruewing@stripped +0 -8
    WL#4144 - Lock MERGE engine children
    Moved all MERGE specific members from TABLE to ha_myisammrg.

  storage/myisammrg/ha_myisammrg.cc@stripped, 2008-06-10 18:06:51+02:00,
istruewing@stripped +365 -194
    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@stripped, 2008-06-10 18:06:51+02:00,
istruewing@stripped +4 -0
    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@stripped, 2008-06-10 18:06:51+02:00, istruewing@stripped
+8 -3
    WL#4144 - Lock MERGE engine children
    Changed handling of ::reset() for non-attached children.

diff -Nrup a/include/my_base.h b/include/my_base.h
--- a/include/my_base.h	2007-12-10 11:07:23 +01:00
+++ b/include/my_base.h	2008-06-10 18:06:51 +02:00
@@ -197,10 +197,11 @@ enum ha_extra_function {
   */
   HA_EXTRA_INSERT_WITH_UPDATE,
   /*
-    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
 };
 
diff -Nrup a/mysql-test/r/merge-big.result b/mysql-test/r/merge-big.result
--- a/mysql-test/r/merge-big.result	2007-11-15 20:25:41 +01:00
+++ b/mysql-test/r/merge-big.result	2008-06-10 18:06:51 +02:00
@@ -24,54 +24,3 @@ UNLOCK TABLES;
 SET SESSION debug="-d,sleep_open_and_lock_after_open";
 # connection default
 DROP TABLE t1;
-#
-# Extra tests for Bug#26379 - Combination of FLUSH TABLE and
-#                             REPAIR TABLE corrupts a MERGE table
-#
-CREATE TABLE t1 (c1 INT);
-CREATE TABLE t2 (c1 INT);
-CREATE TABLE t3 (c1 INT);
-INSERT INTO t1 VALUES (1);
-INSERT INTO t2 VALUES (2);
-INSERT INTO t3 VALUES (3);
-#
-# CREATE ... SELECT
-# try to access parent from another thread.
-#
-# connection con1
-SET SESSION debug="+d,sleep_create_select_before_lock";
-CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
-INSERT_METHOD=FIRST SELECT * FROM t3;
-# connection default
-# Now try to access the parent.
-# If 3 is in table, SELECT had to wait.
-SELECT * FROM t4 ORDER BY c1;
-c1
-1
-2
-3
-# connection con1
-SET SESSION debug="-d,sleep_create_select_before_lock";
-# connection default
-# Cleanup for next test.
-DROP TABLE t4;
-DELETE FROM t1 WHERE c1 != 1;
-#
-# CREATE ... SELECT
-# try to access child from another thread.
-#
-# connection con1
-SET SESSION debug="+d,sleep_create_select_before_lock";
-CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
-INSERT_METHOD=FIRST SELECT * FROM t3;
-# connection default
-# Now try to access a child.
-# If 3 is in table, SELECT had to wait.
-SELECT * FROM t1 ORDER BY c1;
-c1
-1
-3
-# connection con1
-SET SESSION debug="-d,sleep_create_select_before_lock";
-# connection default
-DROP TABLE t1, t2, t3, t4;
diff -Nrup a/mysql-test/r/merge.result b/mysql-test/r/merge.result
--- a/mysql-test/r/merge.result	2007-12-13 13:56:17 +01:00
+++ b/mysql-test/r/merge.result	2008-06-10 18:06:51 +02:00
@@ -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);
@@ -1223,7 +1223,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;
@@ -1295,9 +1295,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;
 #
@@ -1984,3 +1984,225 @@ test.t1	optimize	status	OK
 FLUSH TABLES m1, t1;
 UNLOCK TABLES;
 DROP TABLE t1, m1;
+End of 5.1 tests
+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;
+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
diff -Nrup a/mysql-test/t/merge-big.test b/mysql-test/t/merge-big.test
--- a/mysql-test/t/merge-big.test	2007-11-15 20:25:41 +01:00
+++ b/mysql-test/t/merge-big.test	2008-06-10 18:06:51 +02:00
@@ -3,8 +3,10 @@
 #
 # This test takes rather long time so let us run it only in --big-test mode
 --source include/big_test.inc
-# We are using some debug-only features in this test
+# We use some debug-only features in this test
 --source include/have_debug.inc
+# We use INFORMATION_SCHEMA.PROCESSLIST in this test
+--source include/not_embedded.inc
 
 --disable_warnings
 drop table if exists t1,t2,t3,t4,t5,t6;
@@ -46,6 +48,8 @@ LOCK TABLE t1 WRITE;
 --echo # connection default
 connection default;
 --echo # Let INSERT go into thr_multi_lock().
+#--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';
 --source include/wait_condition.inc
@@ -54,8 +58,10 @@ let $wait_condition= SELECT 1 FROM INFOR
 FLUSH TABLES;
 #SELECT NOW();
 --echo # Let INSERT go through open_tables() where it sleeps.
+#--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 = 'DBUG sleep';
+    WHERE ID = $con1_id AND STATE = 'Waiting for table';
 --source include/wait_condition.inc
 #SELECT NOW();
 --echo # Unlock and close table and wait for con1 to close too.
@@ -74,77 +80,3 @@ UNLOCK TABLES;
 connection default;
 DROP TABLE t1;
 
---echo #
---echo # Extra tests for Bug#26379 - Combination of FLUSH TABLE and
---echo #                             REPAIR TABLE corrupts a MERGE table
---echo #
-CREATE TABLE t1 (c1 INT);
-CREATE TABLE t2 (c1 INT);
-CREATE TABLE t3 (c1 INT);
-INSERT INTO t1 VALUES (1);
-INSERT INTO t2 VALUES (2);
-INSERT INTO t3 VALUES (3);
---echo #
---echo # CREATE ... SELECT
---echo # try to access parent from another thread.
---echo #
-#SELECT NOW();
-    --echo # connection con1
-    connect (con1,localhost,root,,);
-    let $con1_id= `SELECT CONNECTION_ID()`;
-    SET SESSION debug="+d,sleep_create_select_before_lock";
-    send CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
-         INSERT_METHOD=FIRST SELECT * FROM t3;
---echo # connection default
-connection default;
-# wait for the other query to start executing
-let $wait_condition= SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST
-    WHERE ID = $con1_id AND STATE = 'DBUG sleep';
---source include/wait_condition.inc
-#SELECT NOW();
---echo # Now try to access the parent.
---echo # If 3 is in table, SELECT had to wait.
-SELECT * FROM t4 ORDER BY c1;
-#SELECT NOW();
-    --echo # connection con1
-    connection con1;
-    reap;
-    #SELECT NOW();
-    SET SESSION debug="-d,sleep_create_select_before_lock";
-    disconnect con1;
---echo # connection default
-connection default;
---echo # Cleanup for next test.
-DROP TABLE t4;
-DELETE FROM t1 WHERE c1 != 1;
---echo #
---echo # CREATE ... SELECT
---echo # try to access child from another thread.
---echo #
-#SELECT NOW();
-    --echo # connection con1
-    connect (con1,localhost,root,,);
-    let $con1_id= `SELECT CONNECTION_ID()`;
-    SET SESSION debug="+d,sleep_create_select_before_lock";
-    send CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
-         INSERT_METHOD=FIRST SELECT * FROM t3;
---echo # connection default
-connection default;
-# wait for the other query to start executing
-let $wait_condition= SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST
-    WHERE ID = $con1_id AND STATE = 'DBUG sleep';
---source include/wait_condition.inc
-#SELECT NOW();
---echo # Now try to access a child.
---echo # If 3 is in table, SELECT had to wait.
-SELECT * FROM t1 ORDER BY c1;
-#SELECT NOW();
-    --echo # connection con1
-    connection con1;
-    reap;
-    #SELECT NOW();
-    SET SESSION debug="-d,sleep_create_select_before_lock";
-    disconnect con1;
---echo # connection default
-connection default;
-DROP TABLE t1, t2, t3, t4;
diff -Nrup a/mysql-test/t/merge.test b/mysql-test/t/merge.test
--- a/mysql-test/t/merge.test	2007-12-13 13:56:18 +01:00
+++ b/mysql-test/t/merge.test	2008-06-10 18:06:51 +02:00
@@ -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;
@@ -943,9 +943,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;
@@ -1380,4 +1380,295 @@ OPTIMIZE TABLE t1;
 FLUSH TABLES m1, t1;
 UNLOCK TABLES;
 DROP TABLE t1, m1;
+
+--echo End of 5.1 tests
+
+#
+# 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
+--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
+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;
+##
+## 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;
+#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
 
diff -Nrup a/mysys/thr_lock.c b/mysys/thr_lock.c
--- a/mysys/thr_lock.c	2007-11-15 20:25:40 +01:00
+++ b/mysys/thr_lock.c	2008-06-10 18:06:51 +02:00
@@ -608,6 +608,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))
         {
@@ -616,6 +617,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_O
           result= THR_LOCK_ABORTED;               /* Can't wait for this one */
           goto end;
         }
+        /* purecov: end */
       }
 
       /*
diff -Nrup a/sql/ha_partition.cc b/sql/ha_partition.cc
--- a/sql/ha_partition.cc	2007-12-15 15:08:03 +01:00
+++ b/sql/ha_partition.cc	2008-06-10 18:06:51 +02:00
@@ -4577,34 +4577,34 @@ void ha_partition::get_dynamic_partition
 }
 
 
-/*
-  General function to prepare handler for certain behavior
+/**
+  General function to prepare handler for certain behavior.
 
-  SYNOPSIS
-    extra()
-    operation              Operation type for extra call
-
-  RETURN VALUE
-    >0                     Error code
-    0                      Success
+  @param[in]    operation       operation to execute
+
+  @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
@@ -4643,7 +4643,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.
@@ -4662,7 +4662,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
@@ -4790,7 +4790,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
@@ -4821,7 +4821,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
@@ -4856,19 +4856,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:
@@ -4876,6 +4876,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)
@@ -4960,11 +4968,20 @@ 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:
diff -Nrup a/sql/mysql_priv.h b/sql/mysql_priv.h
--- a/sql/mysql_priv.h	2007-12-15 15:08:04 +01:00
+++ b/sql/mysql_priv.h	2008-06-10 18:06:51 +02:00
@@ -1243,9 +1243,6 @@ TABLE *table_cache_insert_placeholder(TH
 bool lock_table_name_if_not_cached(THD *thd, const char *db,
                                    const char *table_name, TABLE **table);
 TABLE *find_locked_table(THD *thd, const char *db,const char *table_name);
-void detach_merge_children(TABLE *table, bool clear_refs);
-bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last,
-                          TABLE_LIST *new_child_list, TABLE_LIST **new_last);
 bool reopen_table(TABLE *table);
 bool reopen_tables(THD *thd,bool get_locks,bool in_refresh);
 void close_data_files_and_morph_locks(THD *thd, const char *db,
diff -Nrup a/sql/sql_base.cc b/sql/sql_base.cc
--- a/sql/sql_base.cc	2007-12-15 15:08:10 +01:00
+++ b/sql/sql_base.cc	2008-06-10 18:06:51 +02:00
@@ -702,9 +702,6 @@ TABLE_SHARE *get_cached_table_share(cons
     to open the table
 
     thd->killed will be set if we run out of memory
-
-    If closing a MERGE child, the calling function has to take care for
-    closing the parent too, if necessary.
 */
 
 
@@ -733,12 +730,6 @@ void close_handle_and_leave_table_as_loc
     share->tmp_table= INTERNAL_TMP_TABLE;       // for intern_close_table()
   }
 
-  /*
-    When closing a MERGE parent or child table, detach the children first.
-    Do not clear child table references to allow for reopen.
-  */
-  if (table->child_l || table->parent)
-    detach_merge_children(table, FALSE);
   table->file->close();
   table->db_stat= 0;                            // Mark file closed
   release_table_share(table->s, RELEASE_NORMAL);
@@ -866,9 +857,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());
-
   intern_close_table(table);
   if (!table->in_use)
   {
@@ -1174,14 +1162,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);
+      VOID(table->file->extra(HA_EXTRA_DETACH_CHILDREN));
     }
   }
 }
@@ -1290,6 +1274,22 @@ void close_thread_tables(THD *thd)
                           table->s->table_name.str, (long) table));
 #endif
 
+  /* Detach MERGE children after every statement. Even under LOCK TABLES. */
+  DBUG_PRINT("tcache", ("thd->query_id: %lu  prelocked: %d",
+                        (ulong) thd->query_id, prelocked_mode));
+  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 (table->db_stat && (!prelocked_mode ||
+                           (table->query_id == thd->query_id)))
+    {
+      DBUG_ASSERT(table->file);
+      VOID(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
@@ -1338,7 +1338,7 @@ void close_thread_tables(THD *thd)
 
     /* We are under simple LOCK TABLES so should not do anything else. */
     if (!prelocked_mode || !thd->lex->requires_prelocking())
-      DBUG_VOID_RETURN;
+      goto end;
 
     /*
       We are in prelocked mode, so we have to leave it now with doing
@@ -1348,7 +1348,7 @@ void close_thread_tables(THD *thd)
     thd->prelocked_mode= NON_PRELOCKED;
 
     if (prelocked_mode == PRELOCKED_UNDER_LOCK_TABLES)
-      DBUG_VOID_RETURN;
+      goto end;
 
     thd->lock= thd->locked_tables;
     thd->locked_tables= 0;
@@ -1387,8 +1387,6 @@ void close_thread_tables(THD *thd)
     Note that we need to hold LOCK_open while changing the
     open_tables list. Another thread may work on it.
     (See: remove_table_from_cache(), mysql_wait_completed_table())
-    Closing a MERGE child before the parent would be fatal if the
-    other thread tries to abort the MERGE lock in between.
   */
   if (thd->open_tables)
     close_open_tables(thd);
@@ -1403,6 +1401,8 @@ void close_thread_tables(THD *thd)
     thd->options&= ~(ulong) (OPTION_TABLE_LOCK);
   }
 
+ end:
+
   DBUG_VOID_RETURN;
 }
 
@@ -1420,12 +1420,6 @@ bool close_thread_table(THD *thd, TABLE 
                         table->s->table_name.str, (long) table));
 
   *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);
 
   if (table->needs_reopen_or_name_lock() ||
       thd->version != refresh_version || !table->db_stat)
@@ -1441,8 +1435,9 @@ bool close_thread_table(THD *thd, TABLE 
     */
     DBUG_ASSERT(!table->open_placeholder);
 
-    /* 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);
+    VOID(table->file->extra(HA_EXTRA_DETACH_CHILDREN));
 
     /* Free memory and reset for next loop */
     table->file->ha_reset();
@@ -1899,19 +1894,6 @@ void close_temporary_table(THD *thd, TAB
                           table->s->db.str, table->s->table_name.str,
                           (long) 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;
@@ -2008,7 +1990,8 @@ bool rename_temporary_table(THD* thd, TA
 static void relink_unused(TABLE *table)
 {
   /* Assert that MERGE children are not attached in unused_tables. */
-  DBUG_ASSERT(!table->is_children_attached());
+  DBUG_ASSERT(!table->db_stat || !table->file ||
+              !table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
 
   if (table != unused_tables)
   {
@@ -2025,77 +2008,6 @@ static void relink_unused(TABLE *table)
 
 
 /**
-  @brief Prepare an open merge table for close.
-
-  @param[in]     thd             thread context
-  @param[in]     table           table to prepare
-  @param[in,out] prev_pp         pointer to pointer of previous table
-
-  @detail
-    If the table is a MERGE parent, just detach the children.
-    If the table is a MERGE child, close the parent (incl. detach).
-*/
-
-static void unlink_open_merge(THD *thd, TABLE *table, TABLE ***prev_pp)
-{
-  DBUG_ENTER("unlink_open_merge");
-
-  if (table->parent)
-  {
-    /*
-      If MERGE child, close parent too. Closing includes detaching.
-
-      This is used for example in ALTER TABLE t1 RENAME TO t5 under
-      LOCK TABLES where t1 is a MERGE child:
-      CREATE TABLE t1 (c1 INT);
-      CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1);
-      LOCK TABLES t1 WRITE, t2 WRITE;
-      ALTER TABLE t1 RENAME TO t5;
-    */
-    TABLE *parent= table->parent;
-    TABLE **prv_p;
-
-    /* Find parent in open_tables list. */
-    for (prv_p= &thd->open_tables;
-         *prv_p && (*prv_p != parent);
-         prv_p= &(*prv_p)->next) {}
-    if (*prv_p)
-    {
-      /* Special treatment required if child follows parent in list. */
-      if (*prev_pp == &parent->next)
-        *prev_pp= prv_p;
-      /*
-        Remove parent from open_tables list and close it.
-        This includes detaching and hence clearing parent references.
-      */
-      close_thread_table(thd, prv_p);
-    }
-  }
-  else if (table->child_l)
-  {
-    /*
-      When closing a MERGE parent, detach the children first. It is
-      not necessary to clear the child or parent table reference of
-      this table because the TABLE is freed. But we need to clear
-      the child or parent references of the other belonging tables
-      so that they cannot be moved into the unused_tables chain with
-      these pointers set.
-
-      This is used for example in ALTER TABLE t2 RENAME TO t5 under
-      LOCK TABLES where t2 is a MERGE parent:
-      CREATE TABLE t1 (c1 INT);
-      CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1);
-      LOCK TABLES t1 WRITE, t2 WRITE;
-      ALTER TABLE t2 RENAME TO t5;
-    */
-    detach_merge_children(table, TRUE);
-  }
-
-  DBUG_VOID_RETURN;
-}
-
-
-/**
     @brief  Remove all instances of table from thread's open list and
             table cache.
 
@@ -2124,8 +2036,6 @@ void unlink_open_table(THD *thd, TABLE *
     Note that we need to hold LOCK_open while changing the
     open_tables list. Another thread may work on it.
     (See: remove_table_from_cache(), mysql_wait_completed_table())
-    Closing a MERGE child before the parent would be fatal if the
-    other thread tries to abort the MERGE lock in between.
   */
   for (prev= &thd->open_tables; *prev; )
   {
@@ -2135,11 +2045,7 @@ void unlink_open_table(THD *thd, TABLE *
 	!memcmp(list->s->table_cache_key.str, key, key_length))
     {
       if (unlock && thd->locked_tables)
-        mysql_lock_remove(thd, thd->locked_tables,
-                          list->parent ? list->parent : list, TRUE);
-
-      /* Prepare MERGE table for close. Close parent if necessary. */
-      unlink_open_merge(thd, list, &prev);
+        mysql_lock_remove(thd, thd->locked_tables, list, TRUE);
 
       /* Remove table from open_tables list. */
       *prev= list->next;
@@ -2626,7 +2532,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
 	}
 	table->query_id= thd->query_id;
 	thd->thread_specific_used= TRUE;
-        DBUG_PRINT("info",("Using temporary table"));
+        DBUG_PRINT("info",("Using temporary table: 0x%lx", (long) table));
         goto reset;
       }
     }
@@ -2657,6 +2563,9 @@ TABLE *open_table(THD *thd, TABLE_LIST *
       if (table->s->table_cache_key.length == key_length &&
 	  !memcmp(table->s->table_cache_key.str, key, key_length))
       {
+        DBUG_PRINT("tcache", ("found table '%s'.'%s' 0x%lx  query_id: %lu",
+                              table->s->db.str, table->s->table_name.str,
+                              (long) table, (ulong) table->query_id));
         if (check_if_used && table->query_id &&
             table->query_id != thd->query_id)
         {
@@ -2670,17 +2579,13 @@ TABLE *open_table(THD *thd, TABLE_LIST *
                    table->s->table_name.str);
           DBUG_RETURN(0);
         }
-        /*
-          When looking for a usable TABLE, ignore MERGE children, as they
-          belong to their parent and cannot be used explicitly.
-        */
         if (!my_strcasecmp(system_charset_info, table->alias, alias) &&
             table->query_id != thd->query_id && /* skip tables already used
*/
-            !(thd->prelocked_mode && table->query_id) &&
-            !table->parent)
+            !(thd->prelocked_mode && table->query_id))
         {
           int distance= ((int) table->reginfo.lock_type -
                          (int) table_list->lock_type);
+          DBUG_PRINT("info",("distance: %d", distance));
           /*
             Find a table that either has the exact lock type requested,
             or has the best suitable lock. In case there is no locked
@@ -2698,6 +2603,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
           {
             best_distance= distance;
             best_table= table;
+            DBUG_PRINT("info",("best table: 0x%lx", (long) table));
             if (best_distance == 0 && !check_if_used)
             {
               /*
@@ -2716,7 +2622,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
     {
       table= best_table;
       table->query_id= thd->query_id;
-      DBUG_PRINT("info",("Using locked table"));
+      DBUG_PRINT("info",("Using locked table: 0x%lx", (long) table));
       goto reset;
     }
     /*
@@ -2928,6 +2834,9 @@ TABLE *open_table(THD *thd, TABLE_LIST *
     table->prev->next=table->next;		/* Remove from unused list */
     table->next->prev=table->prev;
     table->in_use= thd;
+    /* Assert that MERGE children are not attached in unused_tables. */
+    DBUG_ASSERT(!table->db_stat || !table->file ||
+                !table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
   }
   else
   {
@@ -3048,6 +2957,9 @@ TABLE *open_table(THD *thd, TABLE_LIST *
   table_list->updatable= 1; // It is not derived table nor non-updatable VIEW
   table->clear_column_bitmaps();
   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(table);
 }
 
@@ -3078,6 +2990,11 @@ TABLE *find_locked_table(THD *thd, const
    The data file for the table is already closed and the share is released
    The table has a 'dummy' share that mainly contains database and table name.
 
+   No need to treat MERGE table specificly here. If a MERGE table is to
+   be reopend, its children are detached already. They will be
+   automatically attached at the begin of the next statement. Even under
+   LOCK TABLES.
+
  RETURN
    0  ok
    1  error. The old table object is not changed.
@@ -3097,7 +3014,6 @@ bool reopen_table(TABLE *table)
 
   DBUG_ASSERT(table->s->ref_count == 0);
   DBUG_ASSERT(!table->sort.io_cache);
-  DBUG_ASSERT(!table->children_attached);
 
 #ifdef EXTRA_DEBUG
   if (table->db_stat)
@@ -3138,17 +3054,6 @@ bool reopen_table(TABLE *table)
   tmp.next=		table->next;
   tmp.prev=		table->prev;
 
-  /* Preserve MERGE parent. */
-  tmp.parent=           table->parent;
-  /* Fix MERGE child list and check for unchanged union. */
-  if ((table->child_l || tmp.child_l) &&
-      fix_merge_after_open(table->child_l, table->child_last_l,
-                           tmp.child_l, tmp.child_last_l))
-  {
-    VOID(closefrm(&tmp, 1)); // close file, free everything
-    goto end;
-  }
-
   delete table->triggers;
   if (table->file)
     VOID(closefrm(table, 1));		// close file, free everything
@@ -3170,11 +3075,6 @@ bool reopen_table(TABLE *table)
   }
   if (table->triggers)
     table->triggers->set_table(table);
-  /*
-    Do not attach MERGE children here. The children might be reopened
-    after the parent. Attach children after reopening all tables that
-    require reopen. See for example reopen_tables().
-  */
 
   broadcast_refresh();
   error=0;
@@ -3206,6 +3106,7 @@ void close_data_files_and_morph_locks(TH
 {
   TABLE *table;
   DBUG_ENTER("close_data_files_and_morph_locks");
+  DBUG_PRINT("tcache", ("closing '%s'.'%s'", db, table_name));
 
   safe_mutex_assert_owner(&LOCK_open);
 
@@ -3229,23 +3130,11 @@ void close_data_files_and_morph_locks(TH
     if (!strcmp(table->s->table_name.str, table_name) &&
 	!strcmp(table->s->db.str, db))
     {
+      DBUG_PRINT("tcache", ("found '%s'.'%s' 0x%lx",
+                            table->s->db.str, table->s->table_name.str,
+                            (long) table));
       if (thd->locked_tables)
-      {
-        if (table->parent)
-        {
-          /*
-            If MERGE child, need to reopen parent too. This means that
-            the first child to be closed will detach all children from
-            the parent and close it. OTOH in most cases a MERGE table
-            won't have multiple children with the same db.table_name.
-          */
-          mysql_lock_remove(thd, thd->locked_tables, table->parent, TRUE);
-          table->parent->open_placeholder= 1;
-          close_handle_and_leave_table_as_lock(table->parent);
-        }
-        else
-          mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
-      }
+        mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
       table->open_placeholder= 1;
       close_handle_and_leave_table_as_lock(table);
     }
@@ -3255,62 +3144,6 @@ void close_data_files_and_morph_locks(TH
 
 
 /**
-  @brief Reattach MERGE children after reopen.
-
-  @param[in]     thd            thread context
-  @param[in,out] err_tables_p   pointer to pointer of tables in error
-
-  @return       status
-    @retval     FALSE           OK, err_tables_p unchanged
-    @retval     TRUE            Error, err_tables_p contains table(s)
-*/
-
-static bool reattach_merge(THD *thd, TABLE **err_tables_p)
-{
-  TABLE *table;
-  TABLE *next;
-  TABLE **prv_p= &thd->open_tables;
-  bool error= FALSE;
-  DBUG_ENTER("reattach_merge");
-
-  for (table= thd->open_tables; table; table= next)
-  {
-    next= table->next;
-    DBUG_PRINT("tcache", ("check table: '%s'.'%s' 0x%lx  next: 0x%lx",
-                          table->s->db.str, table->s->table_name.str,
-                          (long) table, (long) next));
-    /* Reattach children for MERGE tables with "closed data files" only. */
-    if (table->child_l && !table->children_attached)
-    {
-      DBUG_PRINT("tcache", ("MERGE parent, attach children"));
-      if(table->file->extra(HA_EXTRA_ATTACH_CHILDREN))
-      {
-        my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias);
-        error= TRUE;
-        /* Remove table from open_tables. */
-        *prv_p= next;
-        if (next)
-          prv_p= &next->next;
-        /* Stack table on error list. */
-        table->next= *err_tables_p;
-        *err_tables_p= table;
-        continue;
-      }
-      else
-      {
-        table->children_attached= TRUE;
-        DBUG_PRINT("myrg", ("attached parent: '%s'.'%s' 0x%lx",
-                            table->s->db.str,
-                            table->s->table_name.str, (long) table));
-      }
-    }
-    prv_p= &table->next;
-  }
-  DBUG_RETURN(error);
-}
-
-
-/**
     @brief Reopen all tables with closed data files.
 
     @param thd         Thread context
@@ -3334,9 +3167,7 @@ bool reopen_tables(THD *thd, bool get_lo
 {
   TABLE *table,*next,**prev;
   TABLE **tables,**tables_ptr;			// For locks
-  TABLE *err_tables= NULL;
   bool error=0, not_used;
-  bool merge_table_found= FALSE;
   const uint flags= MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN |
                     MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK |
                     MYSQL_LOCK_IGNORE_FLUSH;
@@ -3349,13 +3180,9 @@ bool reopen_tables(THD *thd, bool get_lo
   safe_mutex_assert_owner(&LOCK_open);
   if (get_locks)
   {
-    /*
-      The ptr is checked later
-      Do not handle locks of MERGE children.
-    */
+    /* The ptr is checked later. */
     uint opens=0;
     for (table= thd->open_tables; table ; table=table->next)
-      if (!table->parent)
         opens++;
     DBUG_PRINT("tcache", ("open tables to lock: %u", opens));
     tables= (TABLE**) my_alloca(sizeof(TABLE*)*opens);
@@ -3369,37 +3196,21 @@ bool reopen_tables(THD *thd, bool get_lo
   {
     uint db_stat=table->db_stat;
     next=table->next;
-    DBUG_PRINT("tcache", ("open table: '%s'.'%s' 0x%lx  "
-                          "parent: 0x%lx  db_stat: %u",
+    DBUG_PRINT("tcache", ("open table: '%s'.'%s' 0x%lx  db_stat: %u",
                           table->s->db.str, table->s->table_name.str,
-                          (long) table, (long) table->parent, db_stat));
-    if (table->child_l && !db_stat)
-      merge_table_found= TRUE;
+                          (long) table, db_stat));
     if (!tables || (!db_stat && reopen_table(table)))
     {
       my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias);
-      /*
-        If we could not allocate 'tables', we may close open tables
-        here. If a MERGE table is affected, detach the children first.
-        It is not necessary to clear the child or parent table reference
-        of this table because the TABLE is freed. But we need to clear
-        the child or parent references of the other belonging tables so
-        that they cannot be moved into the unused_tables chain with
-        these pointers set.
-      */
-      if (table->child_l || table->parent)
-        detach_merge_children(table, TRUE);
       VOID(hash_delete(&open_cache,(uchar*) table));
       error=1;
     }
     else
     {
-      DBUG_PRINT("tcache", ("opened. need lock: %d",
-                            get_locks && !db_stat && !table->parent));
+      DBUG_PRINT("tcache", ("opened. need lock: %d", get_locks && !db_stat));
       *prev= table;
       prev= &table->next;
-      /* Do not handle locks of MERGE children. */
-      if (get_locks && !db_stat && !table->parent)
+      if (get_locks && !db_stat)
 	*tables_ptr++= table;			// need new lock on this
       if (mark_share_as_old)
       {
@@ -3409,19 +3220,6 @@ bool reopen_tables(THD *thd, bool get_lo
     }
   }
   *prev=0;
-  /*
-    When all tables are open again, we can re-attach MERGE children to
-    their parents. All TABLE objects are still present.
-  */
-  DBUG_PRINT("tcache", ("re-attaching MERGE tables: %d", merge_table_found));
-  if (!error && merge_table_found && reattach_merge(thd,
&err_tables))
-  {
-    while (err_tables)
-    {
-      VOID(hash_delete(&open_cache, (uchar*) err_tables));
-      err_tables= err_tables->next;
-    }
-  }
   DBUG_PRINT("tcache", ("open tables to lock: %u",
                         (uint) (tables_ptr - tables)));
   if (tables != tables_ptr)			// Should we get back old locks
@@ -3440,6 +3238,7 @@ bool reopen_tables(THD *thd, bool get_lo
     }
     else
     {
+      /* purecov: begin inspected */
       /*
         This case should only happen if there is a bug in the reopen logic.
         Need to issue error message to have a reply for the application.
@@ -3447,6 +3246,7 @@ bool reopen_tables(THD *thd, bool get_lo
       */
       my_error(ER_LOCK_DEADLOCK, MYF(0));
       error=1;
+      /* purecov: end */
     }
   }
   if (get_locks && tables)
@@ -3496,33 +3296,13 @@ static void close_old_data_files(THD *th
 	if (morph_locks)
 	{
           /*
-            Forward lock handling to MERGE parent. But unlock parent
-            once only.
+            Wake up threads waiting for table-level lock on this table
+            so they won't sneak in when we will temporarily remove our
+            lock on it. This will also give them a chance to close their
+            instances of this table.
           */
-          TABLE *ulcktbl= table->parent ? table->parent : table;
-          if (ulcktbl->lock_count)
-          {
-            /*
-              Wake up threads waiting for table-level lock on this table
-              so they won't sneak in when we will temporarily remove our
-              lock on it. This will also give them a chance to close their
-              instances of this table.
-            */
-            mysql_lock_abort(thd, ulcktbl, TRUE);
-            mysql_lock_remove(thd, thd->locked_tables, ulcktbl, TRUE);
-            ulcktbl->lock_count= 0;
-          }
-          if ((ulcktbl != table) && ulcktbl->db_stat)
-          {
-            /*
-              Close the parent too. Note that parent can come later in
-              the list of tables. It will then be noticed as closed and
-              as a placeholder. When this happens, do not clear the
-              placeholder flag. See the branch below ("***").
-            */
-            ulcktbl->open_placeholder= 1;
-            close_handle_and_leave_table_as_lock(ulcktbl);
-          }
+          mysql_lock_abort(thd, table, TRUE);
+          mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
           /*
             We want to protect the table from concurrent DDL operations
             (like RENAME TABLE) until we will re-open and re-lock it.
@@ -3531,7 +3311,8 @@ static void close_old_data_files(THD *th
 	}
         close_handle_and_leave_table_as_lock(table);
       }
-      else if (table->open_placeholder && !morph_locks)
+      /* purecov: begin inspected */
+      else if (table->open_placeholder)
       {
         /*
           We come here only in close-for-back-off scenario. So we have to
@@ -3539,13 +3320,10 @@ static void close_old_data_files(THD *th
           in case of concurrent execution of CREATE TABLE t1 SELECT * FROM t2
           and RENAME TABLE t2 TO t1). In close-for-re-open scenario we will
           probably want to let it stay.
-
-          Note "***": We must not enter this branch if the placeholder
-          flag has been set because of a former close through a child.
-          See above the comment that refers to this note.
         */
         table->open_placeholder= 0;
       }
+      /* purecov: end */
     }
   }
   if (found)
@@ -3668,8 +3446,6 @@ TABLE *drop_locked_tables(THD *thd,const
     Note that we need to hold LOCK_open while changing the
     open_tables list. Another thread may work on it.
     (See: remove_table_from_cache(), mysql_wait_completed_table())
-    Closing a MERGE child before the parent would be fatal if the
-    other thread tries to abort the MERGE lock in between.
   */
   for (table= thd->open_tables; table ; table=next)
   {
@@ -3677,15 +3453,7 @@ TABLE *drop_locked_tables(THD *thd,const
     if (!strcmp(table->s->table_name.str, table_name) &&
 	!strcmp(table->s->db.str, db))
     {
-      /* If MERGE child, forward lock handling to parent. */
-      mysql_lock_remove(thd, thd->locked_tables,
-                        table->parent ? table->parent : table, TRUE);
-      /*
-        When closing a MERGE parent or child table, detach the children first.
-        Clear child table references in case this object is opened again.
-      */
-      if (table->child_l || table->parent)
-        detach_merge_children(table, TRUE);
+      mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
 
       if (!found)
       {
@@ -3735,8 +3503,7 @@ void abort_locked_tables(THD *thd,const 
     if (!strcmp(table->s->table_name.str, table_name) &&
 	!strcmp(table->s->db.str, db))
     {
-      /* If MERGE child, forward lock handling to parent. */
-      mysql_lock_abort(thd, table->parent ? table->parent : table, TRUE);
+      mysql_lock_abort(thd, table, TRUE);
       break;
     }
   }
@@ -4007,340 +3774,6 @@ err:
 }
 
 
-/**
-  @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' 0x%lx", parent->s->db.str,
-                      parent->s->table_name.str, (long) parent));
-
-  /* Must not call this with attached children. */
-  DBUG_ASSERT(!parent->children_attached);
-  /* Must not call this with children list in place. */
-  DBUG_ASSERT(tlist->next_global != parent->child_l);
-  /* 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' 0x%lx", parent->s->db.str,
-                      parent->s->table_name.str, (long) parent));
-
-  /* Must not call this with attached children. */
-  DBUG_ASSERT(!parent->children_attached);
-  /* Must call this with children list in place. */
-  DBUG_ASSERT(tlist->parent_l->next_global == parent->child_l);
-
-  /* Attach MyISAM tables to MERGE table. */
-  error= parent->file->extra(HA_EXTRA_ATTACH_CHILDREN);
-
-  /*
-    Remove children from the table list. Even in case of an error.
-    This should prevent tampering with them.
-  */
-  tlist->parent_l->next_global= *parent->child_last_l;
-
-  /*
-    Do not fix the last childs next_global pointer. It is needed for
-    stepping to the next table in the enclosing loop in open_tables().
-    Do not fix prev_global pointers. We did not set them.
-  */
-
-  if (error)
-  {
-    DBUG_PRINT("error", ("attaching MERGE children failed: %d", my_errno));
-    parent->file->print_error(error, MYF(0));
-    DBUG_RETURN(1);
-  }
-
-  parent->children_attached= TRUE;
-  DBUG_PRINT("myrg", ("attached parent: '%s'.'%s' 0x%lx", parent->s->db.str,
-                      parent->s->table_name.str, (long) 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' 0x%lx  clear_refs: %d",
-                      table->s->db.str, table->s->table_name.str,
-                      (long) table, clear_refs));
-  DBUG_PRINT("myrg", ("parent: '%s'.'%s' 0x%lx", parent->s->db.str,
-                      parent->s->table_name.str, (long) 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' 0x%lx", parent->s->db.str,
-                        parent->s->table_name.str, (long) 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: 0x%lx  new last addr: 0x%lx",
-                      (long) old_last, (long) 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: 0x%lx  new list item: 0x%lx",
-                        (long) old_child_list, (long) new_child_list));
-    /* Break if one of the list is at its end. */
-    if (!old_child_list || !new_child_list)
-      break;
-    /* Old table has references to child TABLEs. */
-    DBUG_ASSERT(old_child_list->table);
-    /* New table does not yet have references to child TABLEs. */
-    DBUG_ASSERT(!new_child_list->table);
-    DBUG_PRINT("myrg", ("old table: '%s'.'%s'  new table: '%s'.'%s'",
-                        old_child_list->db, old_child_list->table_name,
-                        new_child_list->db, new_child_list->table_name));
-    /* Child db.table names must match. */
-    if (strcmp(old_child_list->table_name, new_child_list->table_name) ||
-        strcmp(old_child_list->db,         new_child_list->db))
-      break;
-    /*
-      Copy TABLE reference. Child TABLE objects are still in place
-      though not necessarily open yet.
-    */
-    DBUG_PRINT("myrg", ("old table ref: 0x%lx  replaces new table ref: 0x%lx",
-                        (long) old_child_list->table,
-                        (long) new_child_list->table));
-    new_child_list->table= old_child_list->table;
-    /* 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
 
@@ -4386,6 +3819,14 @@ int open_tables(THD *thd, TABLE_LIST **s
   */
   init_sql_alloc(&new_frm_mem, 8024, 8024);
 
+#ifdef EXTRA_DEBUG
+  DBUG_PRINT("tcache", ("open_tables enter items:"));
+  for (TABLE_LIST *tlist= *start; tlist ;tlist= tlist->next_global)
+    DBUG_PRINT("tcache", ("tlist: '%s'.'%s'  item: 0x%lx", tlist->db,
+                          tlist->table_name, (long) tlist));
+  DBUG_PRINT("tcache", (":open_tables enter items"));
+#endif
+
   thd->current_tablenr= 0;
  restart:
   *counter= 0;
@@ -4408,6 +3849,13 @@ int open_tables(THD *thd, TABLE_LIST **s
     DBUG_ASSERT(thd->lex->query_tables == *start);
     sp_get_prelocking_info(thd, &need_prelocking, &first_no_prelocking);
 
+#ifdef EXTRA_DEBUG
+  DBUG_PRINT("tcache", ("open_tables restart 1 items:"));
+  for (TABLE_LIST *tlist= *start; tlist ;tlist= tlist->next_global)
+    DBUG_PRINT("tcache", ("tlist: '%s'.'%s'  item: 0x%lx", tlist->db,
+                          tlist->table_name, (long) tlist));
+  DBUG_PRINT("tcache", (":open_tables restart 1 items"));
+#endif
     if (sp_cache_routines_and_add_tables(thd, thd->lex, first_no_prelocking))
     {
       /*
@@ -4425,6 +3873,14 @@ int open_tables(THD *thd, TABLE_LIST **s
     }
   }
 
+#ifdef EXTRA_DEBUG
+  DBUG_PRINT("tcache", ("open_tables restart items:"));
+  for (TABLE_LIST *tlist= *start; tlist ;tlist= tlist->next_global)
+    DBUG_PRINT("tcache", ("tlist: '%s'.'%s'  item: 0x%lx", tlist->db,
+                          tlist->table_name, (long) tlist));
+  DBUG_PRINT("tcache", (":open_tables restart items"));
+#endif
+
   /*
     For every table in the list of tables to open, try to find or open
     a table.
@@ -4458,7 +3914,10 @@ int open_tables(THD *thd, TABLE_LIST **s
     {
       if (!mysql_schema_table(thd, thd->lex, tables))
         continue;
-      DBUG_RETURN(-1);
+      /* purecov: begin inspected */
+      result= -1;
+      goto err;
+      /* purecov: end */
     }
     (*counter)++;
 
@@ -4485,10 +3944,6 @@ int open_tables(THD *thd, TABLE_LIST **s
       else
         tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags);
     }
-    else
-      DBUG_PRINT("tcache", ("referenced table: '%s'.'%s' 0x%lx",
-                            tables->db, tables->table_name,
-                            (long) tables->table));
 
     if (!tables->table)
     {
@@ -4520,19 +3975,6 @@ int open_tables(THD *thd, TABLE_LIST **s
 	goto process_view_routines;
       }
 
-      /*
-        If in a MERGE table open, we need to remove the children list
-        from statement table list before restarting. Otherwise the list
-        will be inserted another time.
-      */
-      if (tables->parent_l)
-      {
-        TABLE_LIST *parent_l= tables->parent_l;
-        /* The parent table should be correctly open at this point. */
-        DBUG_ASSERT(parent_l->table);
-        parent_l->next_global= *parent_l->table->child_last_l;
-      }
-
       if (refresh)				// Refresh in progress
       {
         /*
@@ -4563,7 +4005,7 @@ int open_tables(THD *thd, TABLE_LIST **s
       }
 
       result= -1;				// Fatal error
-      break;
+      goto err;
     }
     else
     {
@@ -4601,22 +4043,21 @@ int open_tables(THD *thd, TABLE_LIST **s
         thd->update_lock_default : tables->lock_type;
     tables->table->grant= tables->grant;
 
-    /* Attach MERGE children if not locked already. */
-    DBUG_PRINT("tcache", ("is parent: %d  is child: %d",
-                          test(tables->table->child_l),
-                          test(tables->parent_l)));
-    DBUG_PRINT("tcache", ("in lock tables: %d  in prelock mode: %d",
-                          test(thd->locked_tables), test(thd->prelocked_mode)));
-    if (((!thd->locked_tables && !thd->prelocked_mode) ||
-         tables->table->s->tmp_table) &&
-        ((tables->table->child_l &&
-          add_merge_table_list(tables)) ||
-         (tables->parent_l &&
-          (&tables->next_global == tables->parent_l->table->child_last_l)
&&
-          attach_merge_children(tables))))
-    {
-      result= -1;
-      goto err;
+    /*
+      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.
+    */
+    if (tables->table->file)
+    {
+      /* 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;
+      }
     }
 
 process_view_routines:
@@ -4644,6 +4085,32 @@ 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.
+      Placeholders do not have a handler open.
+    */
+    if (tbl && tbl->file && (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_SET_PROC_INFO(thd, 0);
   free_root(&new_frm_mem, MYF(0));              // Free pre-alloced block
@@ -4655,12 +4122,11 @@ process_view_routines:
   {
     /*
       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.
+      For example see mysql_admin_table().
+      tables->table is in thd->open_tables. It won't go lost.
     */
-    if (tables->parent_l)
-      tables->parent_l->table= NULL;
-    tables->table= NULL;
+    for (tables= *start; tables; tables= tables->next_global)
+      tables->table= NULL;
   }
   DBUG_PRINT("tcache", ("returning: %d", result));
   DBUG_RETURN(result);
@@ -4800,7 +4266,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST 
 
   if (table)
   {
-    if (table->child_l)
+    if (table->file && (table->file->ht->db_type ==
DB_TYPE_MRG_MYISAM))
     {
       /* A MERGE table must not come here. */
       /* purecov: begin tested */
@@ -5286,6 +4752,23 @@ int lock_tables(THD *thd, TABLE_LIST *ta
 
 void close_tables_for_reopen(THD *thd, TABLE_LIST **tables)
 {
+  IF_DBUG(TABLE_LIST **save_query_tables_own_last=
+          thd->lex->query_tables_own_last);
+  DBUG_ENTER("close_tables_for_reopen");
+#ifdef EXTRA_DEBUG
+  DBUG_PRINT("tcache", ("query tables:"));
+  for (TABLE_LIST *tlist= thd->lex->query_tables; tlist;
+       tlist= tlist->next_global)
+    DBUG_PRINT("tcache", ("tlist: '%s'.'%s'  item: 0x%lx", tlist->db,
+                          tlist->table_name, (long) tlist));
+  DBUG_PRINT("tcache", (":query tables"));
+  DBUG_PRINT("tcache", ("arg tables:"));
+  for (TABLE_LIST *tlist= *tables; tlist; tlist= tlist->next_global)
+    DBUG_PRINT("tcache", ("tlist: '%s'.'%s'  item: 0x%lx", tlist->db,
+                          tlist->table_name, (long) tlist));
+  DBUG_PRINT("tcache", (":arg tables"));
+#endif
+
   /*
     If table list consists only from tables from prelocking set, table list
     for new attempt should be empty, so we have to update list's root pointer.
@@ -5293,10 +4776,42 @@ void close_tables_for_reopen(THD *thd, T
   if (thd->lex->first_not_own_table() == *tables)
     *tables= 0;
   thd->lex->chop_off_not_own_tables();
+#ifdef EXTRA_DEBUG
+  DBUG_PRINT("tcache", ("query tables:"));
+  for (TABLE_LIST *tlist= thd->lex->query_tables; tlist;
+       tlist= tlist->next_global)
+    DBUG_PRINT("tcache", ("tlist: '%s'.'%s'  item: 0x%lx", tlist->db,
+                          tlist->table_name, (long) tlist));
+  DBUG_PRINT("tcache", (":query tables"));
+  DBUG_PRINT("tcache", ("arg tables:"));
+  for (TABLE_LIST *tlist= *tables; tlist; tlist= tlist->next_global)
+    DBUG_PRINT("tcache", ("tlist: '%s'.'%s'  item: 0x%lx", tlist->db,
+                          tlist->table_name, (long) tlist));
+  DBUG_PRINT("tcache", (":arg tables"));
+#endif
+
+  DBUG_ASSERT(!save_query_tables_own_last || !*save_query_tables_own_last);
   sp_remove_not_own_routines(thd->lex);
   for (TABLE_LIST *tmp= *tables; tmp; tmp= tmp->next_global)
     tmp->table= 0;
   close_thread_tables(thd);
+
+#ifdef EXTRA_DEBUG
+  DBUG_PRINT("tcache", ("arg tables:"));
+  for (TABLE_LIST *tlist= *tables; tlist; tlist= tlist->next_global)
+    DBUG_PRINT("tcache", ("tlist: '%s'.'%s'  item: 0x%lx", tlist->db,
+                          tlist->table_name, (long) tlist));
+  DBUG_PRINT("tcache", (":arg tables"));
+  DBUG_PRINT("tcache", ("query tables:"));
+  for (TABLE_LIST *tlist= thd->lex->query_tables; tlist;
+       tlist= tlist->next_global)
+    DBUG_PRINT("tcache", ("tlist: '%s'.'%s'  item: 0x%lx", tlist->db,
+                          tlist->table_name, (long) tlist));
+  DBUG_PRINT("tcache", (":query tables"));
+#endif
+  /* Removing MERGE children list must not "resurrect" prelocked table. */
+  //DBUG_ASSERT(!save_query_tables_own_last || !*save_query_tables_own_last);
+  DBUG_VOID_RETURN;
 }
 
 
@@ -8246,15 +7761,13 @@ bool remove_table_from_cache(THD *thd, c
           Note that we need to hold LOCK_open while going through the
           list. So that the other thread cannot change it. The other
           thread must also hold LOCK_open whenever changing the
-          open_tables list. Aborting the MERGE lock after a child was
-          closed and before the parent is closed would be fatal.
+          open_tables list.
         */
         for (TABLE *thd_table= in_use->open_tables;
 	     thd_table ;
 	     thd_table= thd_table->next)
         {
-          /* Do not handle locks of MERGE children. */
-	  if (thd_table->db_stat && !thd_table->parent)	// If table is open
+	  if (thd_table->db_stat)	// If table is open
 	    signalled|= mysql_lock_abort_for_thread(thd, thd_table);
         }
       }
@@ -8457,9 +7970,7 @@ int abort_and_upgrade_lock(ALTER_PARTITI
 
   lpt->old_lock_type= lpt->table->reginfo.lock_type;
   VOID(pthread_mutex_lock(&LOCK_open));
-  /* If MERGE child, forward lock handling to parent. */
-  mysql_lock_abort(lpt->thd, lpt->table->parent ? lpt->table->parent :
-                   lpt->table, TRUE);
+  mysql_lock_abort(lpt->thd, lpt->table, TRUE);
   VOID(remove_table_from_cache(lpt->thd, lpt->db, lpt->table_name, flags));
   VOID(pthread_mutex_unlock(&LOCK_open));
   DBUG_RETURN(0);
@@ -8487,9 +7998,7 @@ void close_open_tables_and_downgrade(ALT
   remove_table_from_cache(lpt->thd, lpt->db, lpt->table_name,
                           RTFC_WAIT_OTHER_THREAD_FLAG);
   VOID(pthread_mutex_unlock(&LOCK_open));
-  /* If MERGE child, forward lock handling to parent. */
-  mysql_lock_downgrade_write(lpt->thd, lpt->table->parent ?
lpt->table->parent :
-                             lpt->table, lpt->old_lock_type);
+  mysql_lock_downgrade_write(lpt->thd, lpt->table, lpt->old_lock_type);
 }
 /* purecov: end */
 
@@ -8517,6 +8026,7 @@ void close_open_tables_and_downgrade(ALT
     table_name                    Table name
 */
 
+/* purecov: begin deadcode */
 void mysql_wait_completed_table(ALTER_PARTITION_PARAM_TYPE *lpt, TABLE *my_table)
 {
   char key[MAX_DBKEY_LENGTH];
@@ -8561,15 +8071,13 @@ void mysql_wait_completed_table(ALTER_PA
         Note that we need to hold LOCK_open while going through the
         list. So that the other thread cannot change it. The other
         thread must also hold LOCK_open whenever changing the
-        open_tables list. Aborting the MERGE lock after a child was
-        closed and before the parent is closed would be fatal.
+        open_tables list.
       */
       for (TABLE *thd_table= in_use->open_tables;
            thd_table ;
            thd_table= thd_table->next)
       {
-        /* Do not handle locks of MERGE children. */
-        if (thd_table->db_stat && !thd_table->parent) // If table is open
+        if (thd_table->db_stat) // If table is open
           mysql_lock_abort_for_thread(lpt->thd, thd_table);
       }
     }
@@ -8579,13 +8087,12 @@ void mysql_wait_completed_table(ALTER_PA
     those in use for removal after completion. Now we also need to abort
     all that are locked and are not progressing due to being locked
     by our lock. We don't upgrade our lock here.
-    If MERGE child, forward lock handling to parent.
   */
-  mysql_lock_abort(lpt->thd, my_table->parent ? my_table->parent : my_table,
-                   FALSE);
+  mysql_lock_abort(lpt->thd, my_table, FALSE);
   VOID(pthread_mutex_unlock(&LOCK_open));
   DBUG_VOID_RETURN;
 }
+/* purecov: end */
 
 
 /*
@@ -8845,8 +8352,6 @@ 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: remove_table_from_cache(), mysql_wait_completed_table())
-    Closing a MERGE child before the parent would be fatal if the
-    other thread tries to abort the MERGE lock in between.
   */
   while (thd->open_tables)
     found_old_table|= close_thread_table(thd, &thd->open_tables);
diff -Nrup a/sql/sql_table.cc b/sql/sql_table.cc
--- a/sql/sql_table.cc	2007-12-24 14:49:59 +01:00
+++ b/sql/sql_table.cc	2008-06-10 18:06:51 +02:00
@@ -3900,7 +3900,7 @@ static int prepare_for_repair(THD *thd, 
   }
 
   /* A MERGE table must not come here. */
-  DBUG_ASSERT(!table->child_l);
+  DBUG_ASSERT(!table->file || (table->file->ht->db_type !=
DB_TYPE_MRG_MYISAM));
 
   /*
     REPAIR TABLE ... USE_FRM for temporary tables makes little sense.
diff -Nrup a/sql/table.cc b/sql/table.cc
--- a/sql/table.cc	2007-12-17 11:28:18 +01:00
+++ b/sql/table.cc	2008-06-10 18:06:51 +02:00
@@ -4583,24 +4583,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.
 
diff -Nrup a/sql/table.h b/sql/table.h
--- a/sql/table.h	2007-12-14 08:19:47 +01:00
+++ b/sql/table.h	2008-06-10 18:06:51 +02:00
@@ -463,11 +463,6 @@ struct st_table {
 #endif
   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 */
 
@@ -635,8 +630,6 @@ struct st_table {
   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;
@@ -688,7 +681,6 @@ struct st_table {
   */
   inline bool needs_reopen_or_name_lock()
   { return s->version != refresh_version; }
-  bool is_children_attached(void);
 };
 
 enum enum_schema_table_state
diff -Nrup a/storage/myisammrg/ha_myisammrg.cc b/storage/myisammrg/ha_myisammrg.cc
--- a/storage/myisammrg/ha_myisammrg.cc	2007-12-13 13:56:22 +01:00
+++ b/storage/myisammrg/ha_myisammrg.cc	2008-06-10 18:06:51 +02:00
@@ -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,62 +256,204 @@ 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;
 
   /* 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.
+*/
+
+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;
+  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);
 
-  @param[in]    callback_param      data pointer as given to
-                                    myrg_attach_children()
+  /* 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;
+
+    /* Break when this was the last child. */
+    if (&child_l->next_global == this->children_last_l)
+      break;
+  }
+
+  /* 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,7 +469,7 @@ static MI_INFO *myisammrg_attach_childre
     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;
@@ -335,9 +477,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
@@ -387,59 +526,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: 0x%lx", name, (long) 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: 0x%lx", (long) 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)
@@ -454,8 +558,27 @@ int ha_myisammrg::attach_children(void)
   DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
                       table->s->table_name.str, (long) 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().
@@ -468,7 +591,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;
 
@@ -476,8 +599,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);
@@ -504,7 +627,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;
     }
@@ -535,20 +662,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;
     }
   }
@@ -561,50 +691,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)
@@ -612,10 +807,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);
@@ -857,13 +1053,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)
   {
     /*
@@ -884,6 +1090,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);
 }
 
@@ -899,24 +1106,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;
 }
 
 
@@ -924,37 +1126,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;
 }
 
@@ -1043,7 +1214,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)
diff -Nrup a/storage/myisammrg/ha_myisammrg.h b/storage/myisammrg/ha_myisammrg.h
--- a/storage/myisammrg/ha_myisammrg.h	2007-11-15 20:25:41 +01:00
+++ b/storage/myisammrg/ha_myisammrg.h	2008-06-10 18:06:51 +02:00
@@ -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 */
@@ -57,6 +60,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);
diff -Nrup a/storage/myisammrg/myrg_extra.c b/storage/myisammrg/myrg_extra.c
--- a/storage/myisammrg/myrg_extra.c	2007-11-15 20:25:41 +01:00
+++ b/storage/myisammrg/myrg_extra.c	2008-06-10 18:06:51 +02:00
@@ -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
bk commit into 6.0 tree (istruewing:1.2777) WL#4144Ingo Struewing10 Jun