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

ChangeSet@stripped, 2007-09-12 22:11:47+02:00, istruewing@stripped +20 -0
  Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
              corrupts a MERGE table
  
  Not to be pushed. This is a partial implementation of a new
  MERGE table open approach. For intermediate review only.
  
  This is part 2 (open_ltable(), reopen_tables()). I did not change
  reopen_name_locked_table(). It works similar to reopen_table(),
  but does not copy many important elements from the old table.
  It is unclear which MERGE relevant elements need to be copied.
  The only test I found for it showed that there was something to
  implement for CREATE...SELECT. So I put it into
  create_table_from_items() behind reopen_name_locked_table().
  
  Fixes of first patch became necessary because the part 2
  changes allowed more tests to run, which revealed new problems:
  
  - Added elements 'parent' and 'children_attached' to TABLE.
    'parent' is required to identify MERGE children when the
    TABLE_LIST object is no longer accessible (e.g. within a
    LOCK TABLES ... UNLOCK TABLES sequence). This is required
    to avoid tampering with children locks and to forward lock
    changes and table refreshs to the parent. Since all this is
    relevant in reopen situations, this change may indeed belong
    to this patch instead of part 1.
    'children_attached' enables the server to keep track of the
    MERGE table state. So it can avoid to perform inappropriate
    handler calls.
  - Temporary table handling is new.
  - Respect thd->open_options in attach. Cached MERGE parent may
    be used by other threads or thd->open_options change between
    statements.
  - Handle empty MERGE union by always have children_attached= true.
    This avoids detach/[re-]attach of non-existing children. The
    parent is usually detected by table->child_l, which is NULL
    for an empty union set. In effect the table is not treated as
    a MERGE table, but some handler methods must neverthless work
    and survive DBUG_ASSERT(children_attached) (e.g. ::info, ::close).
  
  General patch description:
  
  Changed from open_ltable() to open_and_lock_tables() in all places
  that can be relevant for MERGE tables. The latter can handle tables
  added to the list on the fly. When open_ltable() was used in a loop
  over a list of tables, the list must be temporarily terminated
  after every table for open_and_lock_tables().
  table_list->required_type is set to FRMTYPE_TABLE to avoid open of
  special tables. Handling of derived tables is suppressed.
  
  In reopen_tables() some of the tables open by a thread can be
  closed and reopened. When a MERGE child is affected, the parent
  must be closed and reopened too. Closing of the parent is forced
  before the first child is closed. Reopen happens in the order of
  thd->open_tables. MERGE parents do not attach their children
  automatically at open. This is done after all tables are reopened.
  So all children are open when attaching them.
  
  There are some more places where MERGE children need to be detached.
  
  Special lock handling like mysql_lock_abort() or mysql_lock_remove()
  need to be suppressed for MERGE children or forwarded to the parent.
  This depends on the situation. In loops over all open tables one
  suppresses child lock handling. When a single table is touched,
  forwarding is done.
  
  Bugs fixed with this patch (plus part 1):
  19627, 25038, 25700, 26377, 26379, 26867, 27660, 30275, and 30273.

  mysql-test/r/merge.result@stripped, 2007-09-12 22:11:44+02:00, istruewing@stripped +303
-16
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added test results.

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

  mysql-test/t/merge.test@stripped, 2007-09-12 22:11:44+02:00, istruewing@stripped +362 -3
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added tests. Included are tests for bugs
    8306 (moved from myisam.test), 26379, 19627, 25038, 25700, 26377,
    26867, 27660, 30275, and 30273.

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

  sql/mysql_priv.h@stripped, 2007-09-12 22:11:44+02:00, istruewing@stripped +3 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added parameter 'no_derived' to open_and_lock_tables().

  sql/slave.cc@stripped, 2007-09-12 22:11:44+02:00, istruewing@stripped +3 -1
    ug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Changed from open_ltable() to open_and_lock_tables().

  sql/sp_head.cc@stripped, 2007-09-12 22:11:44+02:00, istruewing@stripped +1 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added argument for parameter 'no_derived' to open_and_lock_tables().

  sql/sql_acl.cc@stripped, 2007-09-12 22:11:44+02:00, istruewing@stripped +1 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added argument for parameter 'no_derived' to open_and_lock_tables().

  sql/sql_base.cc@stripped, 2007-09-12 22:11:44+02:00, istruewing@stripped +345 -73
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Moved static function detach_merge_children() towards the begin
    of the file as it is used earlier now.
    detach_merge_children() has now an argument for clearing table
    references from the child TABLE_LIST objects.
    Need to detach children in close_handle_and_leave_table_as_lock().
    Need to detach children from temporary tables in
    close_thread_tables() and close_temporary().
    Prevented special lock handling of merge children (like
    mysql_lock_remove, mysql_lock_merge or mysql_lock_abort). Some of
    these calls are forwarded to the parent table now.
    Added new function reopen_merge_list() to fix MERGE children
    after reopen. Checks are done that both old and new table are of
    MERGE type and the children are the same. Copy parent and child
    table references from old table.
    Added MERGE table handling to reopen_table().
    Added (re-)attaching of children in reopen_tables().
    Forwarded lock handling to parent in close_one_data_file().
    Disabled use of open_ltable() for MERGE tables.
    Added parameter 'no_derived' to open_and_lock_tables().

  sql/sql_delete.cc@stripped, 2007-09-12 22:11:44+02:00, istruewing@stripped +1 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added argument for parameter 'no_derived' to open_and_lock_tables().

  sql/sql_insert.cc@stripped, 2007-09-12 22:11:45+02:00, istruewing@stripped +65 -16
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added argument for parameter 'no_derived' to open_and_lock_tables().
    Changed from open_ltable() to open_and_lock_tables() in
    handle_delayed_insert(). This includes initialization of thd->lex
    by start_lex().
    Added MERGE handling to create_table_from_items().
    Avoided locking of MERGE table when running CREATE...SELECT
    under LOCK TABLES. Children are already locked in this case.

  sql/sql_load.cc@stripped, 2007-09-12 22:11:45+02:00, istruewing@stripped +1 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added argument for parameter 'no_derived' to open_and_lock_tables().

  sql/sql_parse.cc@stripped, 2007-09-12 22:11:45+02:00, istruewing@stripped +10 -8
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Changed from open_ltable() to open_and_lock_tables() in
    mysql_table_dump().
    Added argument for parameter 'no_derived' to open_and_lock_tables().

  sql/sql_partition.cc@stripped, 2007-09-12 22:11:45+02:00, istruewing@stripped +1 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Fixed comment typo.

  sql/sql_table.cc@stripped, 2007-09-12 22:11:45+02:00, istruewing@stripped +35 -3
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added argument for parameter 'no_derived' to open_and_lock_tables().
    Changed from open_ltable() to open_and_lock_tables() in
    mysql_alter_table() and mysql_checksum_table().
    Initialized table_list->table in mysql_recreate_table().

  sql/sql_view.cc@stripped, 2007-09-12 22:11:45+02:00, istruewing@stripped +1 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added argument for parameter 'no_derived' to open_and_lock_tables().

  sql/table.h@stripped, 2007-09-12 22:11:45+02:00, istruewing@stripped +2 -0
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added elements 'parent' and 'children_attached' to TABLE.

  storage/myisammrg/ha_myisammrg.cc@stripped, 2007-09-12 22:11:45+02:00,
istruewing@stripped +75 -39
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added assignment of parent TABLE reference to child TABLE in
    myisammrg_attach_children_callback().
    Added a check for matching TEMPORARY type for children against
    parent.
    Added support for thd->open_options to attach_children().
    Changed child path name generation for temporary tables so that
    it does nothing special for temporary tables.

  storage/myisammrg/myrg_close.c@stripped, 2007-09-12 22:11:45+02:00, istruewing@stripped
+4 -0
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added comment.

  storage/myisammrg/myrg_open.c@stripped, 2007-09-12 22:11:45+02:00, istruewing@stripped
+18 -2
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Fixed first patch: Empty MERGE tables have children_attached true,
    rec_per_key_part needs initialization, avoid de-initializations
    for empty MERGE tables.

diff -Nrup a/mysql-test/r/merge.result b/mysql-test/r/merge.result
--- a/mysql-test/r/merge.result	2007-08-02 21:45:49 +02:00
+++ b/mysql-test/r/merge.result	2007-09-12 22:11:44 +02:00
@@ -584,9 +584,7 @@ insert into t1 values (1);
 insert into t2 values (2);
 create temporary table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
 select * from t3;
-a
-1
-2
+ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
 create temporary table t4 (a int not null);
 create temporary table t5 (a int not null);
 insert into t4 values (1);
@@ -597,6 +595,26 @@ a
 1
 2
 drop table t6, t3, t1, t2, t4, t5;
+create temporary table t1 (a int not null);
+create temporary table t2 (a int not null);
+insert into t1 values (1);
+insert into t2 values (2);
+create table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
+select * from t3;
+ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
+drop table t3, t2, t1;
+create table t1 (a int not null);
+create temporary table t2 (a int not null);
+insert into t1 values (1);
+insert into t2 values (2);
+create table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
+select * from t3;
+ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
+drop table t3;
+create temporary table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
+select * from t3;
+ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
+drop table t3, t2, t1;
 CREATE TABLE t1 (
 fileset_id tinyint(3) unsigned NOT NULL default '0',
 file_code varchar(32) NOT NULL default '',
@@ -621,7 +639,7 @@ id	select_type	table	type	possible_keys	
 EXPLAIN SELECT * FROM t2 WHERE fileset_id = 2
 AND file_code BETWEEN '0000000115' AND '0000000120' LIMIT 1;
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	SIMPLE	t2	range	PRIMARY,files	PRIMARY	35	NULL	5	Using where
+1	SIMPLE	t2	ref	PRIMARY,files	files	1	const	9	Using where
 EXPLAIN SELECT * FROM t1 WHERE fileset_id = 2
 AND file_code BETWEEN '0000000115' AND '0000000120' LIMIT 1;
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
@@ -708,7 +726,7 @@ a	b	c
 1	1	0
 show index from t3;
 Table	Non_unique	Key_name	Seq_in_index	Column_name	Collation	Cardinality	Sub_part	Packed	Null	Index_type	Comment
-t3	1	a	1	a	A	NULL	NULL	NULL	YES	BTREE	
+t3	1	a	1	a	A	0	NULL	NULL	YES	BTREE	
 t3	1	a	2	b	A	NULL	NULL	NULL	YES	BTREE	
 t3	1	a	3	c	A	NULL	NULL	NULL	YES	BTREE	
 drop table t1, t2, t3;
@@ -784,7 +802,7 @@ ERROR HY000: Unable to open underlying t
 DROP TABLE t1, t2;
 CREATE TABLE t2(a INT) ENGINE=MERGE UNION=(t3);
 SELECT * FROM t2;
-ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
+ERROR 42S02: Table 'test.t3' doesn't exist
 DROP TABLE t2;
 CREATE TABLE t1(a INT, b TEXT);
 CREATE TABLE tm1(a TEXT, b INT) ENGINE=MERGE UNION=(t1);
@@ -849,21 +867,18 @@ drop table t2;
 drop table t1;
 CREATE TABLE tm1(a INT) ENGINE=MERGE UNION=(t1, t2);
 SELECT * FROM tm1;
-ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
+ERROR 42S02: Table 'test.t1' doesn't exist
 CHECK TABLE tm1;
 Table	Op	Msg_type	Msg_text
-test.tm1	check	Error	Table 'test.t1' is differently defined or of non-MyISAM type or
doesn't exist
-test.tm1	check	Error	Table 'test.t2' is differently defined or of non-MyISAM type or
doesn't exist
-test.tm1	check	Error	Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
-test.tm1	check	error	Corrupt
+test.tm1	check	Error	Table 'test.t1' doesn't exist
+test.tm1	check	status	OK
 CREATE TABLE t1(a INT);
 SELECT * FROM tm1;
-ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
+ERROR 42S02: Table 'test.t2' doesn't exist
 CHECK TABLE tm1;
 Table	Op	Msg_type	Msg_text
-test.tm1	check	Error	Table 'test.t2' is differently defined or of non-MyISAM type or
doesn't exist
-test.tm1	check	Error	Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
-test.tm1	check	error	Corrupt
+test.tm1	check	Error	Table 'test.t2' doesn't exist
+test.tm1	check	status	OK
 CREATE TABLE t2(a BLOB);
 SELECT * FROM tm1;
 ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
@@ -871,7 +886,7 @@ CHECK TABLE tm1;
 Table	Op	Msg_type	Msg_text
 test.tm1	check	Error	Table 'test.t2' is differently defined or of non-MyISAM type or
doesn't exist
 test.tm1	check	Error	Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
-test.tm1	check	error	Corrupt
+test.tm1	check	status	OK
 ALTER TABLE t2 MODIFY a INT;
 SELECT * FROM tm1;
 a
@@ -880,3 +895,275 @@ Table	Op	Msg_type	Msg_text
 test.tm1	check	status	OK
 DROP TABLE tm1, t1, t2;
 End of 5.0 tests
+create table t1 (c1 int, index(c1));
+create table t2 (c1 int, index(c1)) engine=merge union=(t1);
+insert into t1 values (1);
+flush tables;
+select * from t2;
+c1
+1
+flush tables;
+truncate table t1;
+insert into t1 values (1);
+flush tables;
+select * from t2;
+c1
+1
+truncate table t1;
+insert into t1 values (1);
+drop table t1,t2;
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE= MRG_MYISAM UNION= (t1) INSERT_METHOD= LAST;
+REPAIR TABLE t1;
+INSERT INTO t2 VALUES (1);
+Table	Op	Msg_type	Msg_text
+test.t1	repair	status	OK
+DROP TABLE t1, t2;
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE= MRG_MYISAM UNION= (t1) INSERT_METHOD= LAST;
+LOCK TABLE t1 WRITE;
+INSERT INTO t2 VALUES (1);
+REPAIR TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	repair	status	OK
+UNLOCK TABLES;
+DROP TABLE t1, t2;
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+LOCK TABLE t1 WRITE;
+INSERT INTO t1 VALUES (1);
+FLUSH TABLES;
+FLUSH TABLES;
+SELECT * FROM t1;
+c1
+UNLOCK TABLES;
+DROP TABLE t1;
+CREATE TABLE t1 (c1 INT);
+CREATE TABLE t2 (c1 INT);
+CREATE TABLE t3 (c1 INT);
+INSERT INTO t3 VALUES (1), (2);
+CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+INSERT_METHOD=LAST SELECT * FROM t3;
+SHOW CREATE TABLE t4;
+Table	Create Table
+t4	CREATE TABLE `t4` (
+  `c1` int(11) DEFAULT NULL
+) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 INSERT_METHOD=LAST UNION=(`t1`,`t2`)
+SELECT * FROM t4;
+c1
+1
+2
+DROP TABLE t4;
+LOCK TABLES t1 WRITE, t2 WRITE, t3 READ;
+CREATE TABLE t4 (c1 INT);
+DROP TABLE t4;
+CREATE TABLE t4 (c1 INT) SELECT * FROM t3;
+ERROR HY000: Table 't4' was not locked with LOCK TABLES
+CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+INSERT_METHOD=LAST SELECT * FROM t3;
+ERROR HY000: Table 't4' was not locked with LOCK TABLES
+UNLOCK TABLES;
+DROP TABLE t1, t2, t3;
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE= MyISAM;
+CREATE TABLE t3 (c1 INT) ENGINE= MRG_MYISAM UNION= (t1, t2);
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+SELECT * FROM t3;
+c1
+1
+2
+TRUNCATE TABLE t1;
+SELECT * FROM t3;
+c1
+2
+DROP TABLE t1, t2, t3;
+CREATE TABLE t1 (id INTEGER, grp TINYINT, id_rev INTEGER);
+SET @rnd_max= 2147483647;
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+set @@read_buffer_size=2*1024*1024;
+CREATE TABLE t2 SELECT * FROM t1;
+INSERT INTO t1 (id, grp, id_rev) SELECT id, grp, id_rev FROM t2;
+INSERT INTO t2 (id, grp, id_rev) SELECT id, grp, id_rev FROM t1;
+INSERT INTO t1 (id, grp, id_rev) SELECT id, grp, id_rev FROM t2;
+INSERT INTO t2 (id, grp, id_rev) SELECT id, grp, id_rev FROM t1;
+INSERT INTO t1 (id, grp, id_rev) SELECT id, grp, id_rev FROM t2;
+CREATE TABLE t3 (id INTEGER, grp TINYINT, id_rev INTEGER)
+ENGINE= MRG_MYISAM UNION= (t1, t2);
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+130
+SELECT COUNT(*) FROM t2;
+COUNT(*)
+80
+SELECT COUNT(*) FROM t3;
+COUNT(*)
+210
+SELECT COUNT(DISTINCT a1.id) FROM t3 AS a1, t3 AS a2
+WHERE a1.id = a2.id GROUP BY a2.grp;
+TRUNCATE TABLE t1;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+SELECT COUNT(*) FROM t2;
+COUNT(*)
+80
+SELECT COUNT(*) FROM t3;
+COUNT(*)
+80
+DROP TABLE t1, t2, t3;
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+INSERT INTO t2 VALUES (1);
+SELECT * FROM t2;
+c1
+1
+LOCK TABLES t2 WRITE, t1 WRITE;
+FLUSH TABLES;
+REPAIR TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	repair	status	OK
+CHECK TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	check	status	OK
+REPAIR TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	repair	status	OK
+UNLOCK TABLES;
+CHECK TABLE t1 EXTENDED;
+Table	Op	Msg_type	Msg_text
+test.t1	check	status	OK
+LOCK TABLES t2 WRITE, t1 WRITE;
+SELECT * FROM t2;
+c1
+1
+LOCK TABLES t2 WRITE, t1 WRITE;
+REPAIR TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	repair	status	OK
+CHECK TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	check	status	OK
+REPAIR TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	repair	status	OK
+UNLOCK TABLES;
+CHECK TABLE t1 EXTENDED;
+Table	Op	Msg_type	Msg_text
+test.t1	check	status	OK
+DROP TABLE t1, t2;
+CREATE TABLE t1 ( a INT ) ENGINE=MyISAM;
+CREATE TABLE m1 ( a INT ) ENGINE=MRG_MYISAM UNION=(t1);
+LOCK TABLES t1 WRITE, m1 WRITE;
+FLUSH TABLE t1;
+UNLOCK TABLES;
+DROP TABLE m1, t1;
+CREATE TABLE t1 ( a INT ) ENGINE=MyISAM;
+CREATE TABLE m1 ( a INT ) ENGINE=MRG_MYISAM UNION=(t1);
+LOCK TABLES m1 WRITE, t1 WRITE;
+FLUSH TABLE t1;
+UNLOCK TABLES;
+DROP TABLE m1, t1;
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE= MRG_MYISAM UNION= (t1) INSERT_METHOD= LAST;
+LOCK TABLE t1 WRITE;
+INSERT INTO t2 VALUES (1);
+REPAIR TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	repair	status	OK
+UNLOCK TABLES;
+DROP TABLE t1, t2;
+CREATE TABLE t1 (c1 INT, c2 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT, c2 INT) ENGINE= MyISAM;
+CREATE TABLE t3 (c1 INT, c2 INT) ENGINE= MRG_MYISAM UNION(t1, t2);
+INSERT INTO t1 VALUES (1, 1);
+INSERT INTO t2 VALUES (2, 2);
+SELECT * FROM t3;
+c1	c2
+1	1
+2	2
+ALTER TABLE t1 ENGINE= MEMORY;
+INSERT INTO t1 VALUES (0, 0);
+SELECT * FROM t3;
+ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
+DROP TABLE t1, t2, t3;
+CREATE TABLE t1 (c1 INT, KEY(c1));
+CREATE TABLE t2 (c1 INT, KEY(c1)) ENGINE=MRG_MYISAM UNION=(t1)
+INSERT_METHOD=FIRST;
+LOCK TABLE t1 WRITE, t2 WRITE;
+FLUSH TABLES t2, t1;
+OPTIMIZE TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	optimize	status	Table is already up to date
+FLUSH TABLES t1;
+UNLOCK TABLES;
+FLUSH TABLES;
+INSERT INTO t1 VALUES (1);
+LOCK TABLE t1 WRITE, t2 WRITE;
+FLUSH TABLES t2, t1;
+OPTIMIZE TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	optimize	status	OK
+FLUSH TABLES t1;
+UNLOCK TABLES;
+DROP TABLE t1, t2;
+CREATE TABLE t1 (ID INT) ENGINE=MYISAM;
+CREATE TABLE m1 (ID INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=FIRST;
+INSERT INTO t1 VALUES ();
+INSERT INTO m1 VALUES ();
+LOCK TABLE t1 WRITE, m1 WRITE;
+FLUSH TABLES m1, t1;
+OPTIMIZE TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	optimize	status	OK
+FLUSH TABLES m1, t1;
+UNLOCK TABLES;
+DROP TABLE t1, m1;
diff -Nrup a/mysql-test/r/myisam.result b/mysql-test/r/myisam.result
--- a/mysql-test/r/myisam.result	2007-05-29 14:57:33 +02:00
+++ b/mysql-test/r/myisam.result	2007-09-12 22:11:44 +02:00
@@ -606,24 +606,6 @@ select count(*) from t1 where a is null;
 count(*)
 2
 drop table t1;
-create table t1 (c1 int, index(c1));
-create table t2 (c1 int, index(c1)) engine=merge union=(t1);
-insert into t1 values (1);
-flush tables;
-select * from t2;
-c1
-1
-flush tables;
-truncate table t1;
-insert into t1 values (1);
-flush tables;
-select * from t2;
-c1
-1
-truncate table t1;
-ERROR HY000: MyISAM table 't1' is in use (most likely by a MERGE table). Try FLUSH
TABLES.
-insert into t1 values (1);
-drop table t1,t2;
 create table t1 (c1 int, c2 varchar(4) not null default '',
 key(c2(3))) default charset=utf8;
 insert into t1 values (1,'A'), (2, 'B'), (3, 'A');
diff -Nrup a/mysql-test/t/merge.test b/mysql-test/t/merge.test
--- a/mysql-test/t/merge.test	2007-06-16 13:14:06 +02:00
+++ b/mysql-test/t/merge.test	2007-09-12 22:11:44 +02:00
@@ -221,6 +221,7 @@ create table t2 (a int not null);
 insert into t1 values (1);
 insert into t2 values (2);
 create temporary table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
+--error ER_WRONG_MRG_TABLE
 select * from t3;
 create temporary table t4 (a int not null);
 create temporary table t5 (a int not null);
@@ -229,6 +230,32 @@ insert into t5 values (2);
 create temporary table t6 (a int not null) ENGINE=MERGE UNION=(t4,t5);
 select * from t6;
 drop table t6, t3, t1, t2, t4, t5;
+#
+# Bug#19627 - temporary merge table locking
+# MERGE table and its children must match in temporary type.
+# Forbid temporary merge on non-temporary children: shown above.
+# Forbid non-temporary merge on temporary children:
+create temporary table t1 (a int not null);
+create temporary table t2 (a int not null);
+insert into t1 values (1);
+insert into t2 values (2);
+create table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
+--error ER_WRONG_MRG_TABLE
+select * from t3;
+drop table t3, t2, t1;
+# Forbid children mismatch in temporary:
+create table t1 (a int not null);
+create temporary table t2 (a int not null);
+insert into t1 values (1);
+insert into t2 values (2);
+create table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
+--error ER_WRONG_MRG_TABLE
+select * from t3;
+drop table t3;
+create temporary table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
+--error ER_WRONG_MRG_TABLE
+select * from t3;
+drop table t3, t2, t1;
 
 #
 # testing merge::records_in_range and optimizer
@@ -403,7 +430,7 @@ CREATE TABLE t2(a INT) ENGINE=MERGE UNIO
 SELECT * FROM t2;
 DROP TABLE t1, t2;
 CREATE TABLE t2(a INT) ENGINE=MERGE UNION=(t3);
---error 1168
+--error ER_NO_SUCH_TABLE
 SELECT * FROM t2;
 DROP TABLE t2;
 
@@ -495,11 +522,11 @@ drop table t1;
 #             CREATE TABLE fails
 #
 CREATE TABLE tm1(a INT) ENGINE=MERGE UNION=(t1, t2);
---error 1168
+--error ER_NO_SUCH_TABLE
 SELECT * FROM tm1;
 CHECK TABLE tm1;
 CREATE TABLE t1(a INT);
---error 1168
+--error ER_NO_SUCH_TABLE
 SELECT * FROM tm1;
 CHECK TABLE tm1;
 CREATE TABLE t2(a BLOB);
@@ -512,3 +539,335 @@ CHECK TABLE tm1;
 DROP TABLE tm1, t1, t2;
 
 --echo End of 5.0 tests
+
+#
+# Bug #8306: TRUNCATE leads to index corruption 
+#
+create table t1 (c1 int, index(c1));
+create table t2 (c1 int, index(c1)) engine=merge union=(t1);
+insert into t1 values (1);
+# Close all tables.
+flush tables;
+# Open t2 and (implicitly) t1.
+select * from t2;
+# Truncate after flush works (unless another threads reopens t2 in between).
+flush tables;
+truncate table t1;
+insert into t1 values (1);
+# Close all tables.
+flush tables;
+# Open t2 and (implicitly) t1.
+select * from t2;
+# Truncate t1, wich was not recognized as open without the bugfix.
+# After fix for Bug#8306 and before fix for Bug#26379,
+# it should fail with a table-in-use error message, otherwise succeed.
+truncate table t1;
+# The insert used to fail on the crashed table.
+insert into t1 values (1);
+drop table t1,t2;
+
+#
+# Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table
+# Preparation
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+connection default;
+#
+# Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table
+# Problem #1
+# A thread trying to lock a MERGE table performed busy waiting while
+# REPAIR TABLE or a similar table administration task was ongoing on one or
+# more of its MyISAM tables.
+# To allow for observability it was necessary to enter a multi-second sleep
+# in mysql_admin_table() after remove_table_from_cache(), which comes after
+# mysql_abort_lock(). The sleep faked a long running operation. One could
+# watch a high CPU load during the sleep time.
+# The problem was that mysql_abort_lock() upgrades the write lock to
+# TL_WRITE_ONLY. This lock type persisted until the final unlock at the end
+# of the administration task. The effect of TL_WRITE_ONLY is to reject any
+# attempt to lock the table. The trying thread must close the table and wait
+# until it is no longer used. Unfortunately there is no way to detect that
+# one of the MyISAM tables of a MERGE table is in use. When trying to lock
+# the MERGE table, all MyISAM tables are locked. If one fails on
+# TL_WRITE_ONLY, all locks are aborted and wait_for_tables() is entered.
+# But this doesn't see the MERGE table as used, so it seems appropriate to
+# retry a lock...
+#
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE= MRG_MYISAM UNION= (t1) INSERT_METHOD= LAST;
+send REPAIR TABLE t1;
+    connection con1;
+    sleep 1; # let repair run into its sleep
+    INSERT INTO t2 VALUES (1);
+connection default;
+reap;
+DROP TABLE t1, t2;
+#
+# Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table
+# Problem #2
+# A thread trying to lock a MERGE table performed busy waiting until all
+# threads that did REPAIR TABLE or similar table administration tasks on
+# one or more of its MyISAM tables in LOCK TABLES segments did
+# UNLOCK TABLES.
+# The difference against problem #1 is that the busy waiting took place
+# *after* the administration task. It was terminated by UNLOCK TABLES only.
+#
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE= MRG_MYISAM UNION= (t1) INSERT_METHOD= LAST;
+LOCK TABLE t1 WRITE;
+    connection con1;
+    send INSERT INTO t2 VALUES (1);
+connection default;
+sleep 1; # Let INSERT go into thr_multi_lock().
+REPAIR TABLE t1;
+sleep 2; # con1 performs busy waiting during this sleep.
+UNLOCK TABLES;
+    connection con1;
+    reap;
+connection default;
+DROP TABLE t1, t2;
+#
+# Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table
+# Problem #3
+# Two FLUSH TABLES within a LOCK TABLES segment could invalidate the lock.
+# This did *not* require a MERGE table.
+# To increase reproducibility it was necessary to enter a sleep of 2 seconds
+# at the end of wait_for_tables() after unlock of LOCK_open. In 5.0 and 5.1
+# the sleep must be inserted in open_and_lock_tables() after open_tables()
+# instead. wait_for_tables() is not used in this case.
+# The problem was that FLUSH TABLES releases LOCK_open while having unlocked
+# and closed all tables. When this happened while a thread was in the loop in
+# mysql_lock_tables() right after wait_for_tables() and before retrying to
+# lock, the thread got the lock.  (Translate to similar code places in 5.0
+# and 5.1). And it did not notice that the table needed a refresh. So it
+# executed its statement on the table.
+# The first FLUSH TABLES kicked the INSERT out of thr_multi_lock() and let
+# it wait in wait_for_tables(). (open_table() in 5.0 and 5.1). The second
+# FLUSH TABLES must happen while the INSERT was on its way from
+# wait_for_tables() to the next call of thr_multi_lock(). This needed to be
+# supported by a sleep to make it repeatable.
+#
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+LOCK TABLE t1 WRITE;
+    connection con1;
+    send INSERT INTO t1 VALUES (1);
+connection default;
+sleep 1; # Let INSERT go into thr_multi_lock().
+FLUSH TABLES;
+sleep 1; # Let INSERT go through wait_for_tables() where it sleeps.
+FLUSH TABLES;
+# This should give no result. But it will with sleep(2) at the right place.
+SELECT * FROM t1;
+UNLOCK TABLES;
+    connection con1;
+    reap;
+connection default;
+DROP TABLE t1;
+#
+# Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table
+# Cleanup
+disconnect con1;
+disconnect con2;
+#
+# Extra test for Bug#26379.
+# Test usage of reopen_name_locked_table() in CREATE ... SELECT
+#
+CREATE TABLE t1 (c1 INT);
+CREATE TABLE t2 (c1 INT);
+CREATE TABLE t3 (c1 INT);
+INSERT INTO t3 VALUES (1), (2);
+CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+  INSERT_METHOD=LAST SELECT * FROM t3;
+SHOW CREATE TABLE t4;
+SELECT * FROM t4;
+DROP TABLE t4;
+LOCK TABLES t1 WRITE, t2 WRITE, t3 READ;
+CREATE TABLE t4 (c1 INT);
+DROP TABLE t4;
+--error ER_TABLE_NOT_LOCKED
+CREATE TABLE t4 (c1 INT) SELECT * FROM t3;
+--error ER_TABLE_NOT_LOCKED
+CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+  INSERT_METHOD=LAST SELECT * FROM t3;
+UNLOCK TABLES;
+DROP TABLE t1, t2, t3;
+
+#
+# Bug#25038 - Waiting TRUNCATE
+#
+# Show that truncate of child table after use of parent table works.
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM; 
+CREATE TABLE t2 (c1 INT) ENGINE= MyISAM; 
+CREATE TABLE t3 (c1 INT) ENGINE= MRG_MYISAM UNION= (t1, t2); 
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+SELECT * FROM t3;
+TRUNCATE TABLE t1;
+SELECT * FROM t3;
+DROP TABLE t1, t2, t3;
+#
+# Show that truncate of child table waits while parent table is used.
+# (test partly borrowed from count_distinct3.)
+CREATE TABLE t1 (id INTEGER, grp TINYINT, id_rev INTEGER);
+SET @rnd_max= 2147483647;
+let $1 = 10;
+while ($1)
+{
+  SET @rnd= RAND();
+  SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+  SET @id_rev= @rnd_max - @id;
+  SET @grp= CAST(127.0 * @rnd AS UNSIGNED); 
+  INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev); 
+  dec $1;
+}
+set @@read_buffer_size=2*1024*1024;
+CREATE TABLE t2 SELECT * FROM t1;
+INSERT INTO t1 (id, grp, id_rev) SELECT id, grp, id_rev FROM t2;
+INSERT INTO t2 (id, grp, id_rev) SELECT id, grp, id_rev FROM t1;
+INSERT INTO t1 (id, grp, id_rev) SELECT id, grp, id_rev FROM t2;
+INSERT INTO t2 (id, grp, id_rev) SELECT id, grp, id_rev FROM t1;
+INSERT INTO t1 (id, grp, id_rev) SELECT id, grp, id_rev FROM t2;
+CREATE TABLE t3 (id INTEGER, grp TINYINT, id_rev INTEGER)
+  ENGINE= MRG_MYISAM UNION= (t1, t2);
+SELECT COUNT(*) FROM t1;
+SELECT COUNT(*) FROM t2;
+SELECT COUNT(*) FROM t3;
+connect (con1,localhost,root,,);
+    # As t3 contains random numbers, results are different from test to test. 
+    # That's okay, because we test only that select doesn't yield an
+    # error. Note, that --disable_result_log doesn't suppress error output.
+    --disable_result_log
+    send SELECT COUNT(DISTINCT a1.id) FROM t3 AS a1, t3 AS a2
+      WHERE a1.id = a2.id GROUP BY a2.grp;
+connection default;
+sleep 1;
+TRUNCATE TABLE t1;
+    connection con1;
+    reap;
+    --enable_result_log
+    disconnect con1;
+connection default;
+SELECT COUNT(*) FROM t1;
+SELECT COUNT(*) FROM t2;
+SELECT COUNT(*) FROM t3;
+DROP TABLE t1, t2, t3;
+
+#
+# Bug#25700 - merge base tables get corrupted by optimize/analyze/repair table
+#
+# Using FLUSH TABLES before REPAIR.
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+INSERT INTO t2 VALUES (1);
+SELECT * FROM t2;
+LOCK TABLES t2 WRITE, t1 WRITE;
+FLUSH TABLES;
+REPAIR TABLE t1;
+CHECK TABLE t1;
+REPAIR TABLE t1;
+UNLOCK TABLES;
+CHECK TABLE t1 EXTENDED;
+#
+# Not using FLUSH TABLES before REPAIR.
+LOCK TABLES t2 WRITE, t1 WRITE;
+SELECT * FROM t2;
+LOCK TABLES t2 WRITE, t1 WRITE;
+REPAIR TABLE t1;
+CHECK TABLE t1;
+REPAIR TABLE t1;
+UNLOCK TABLES;
+CHECK TABLE t1 EXTENDED;
+DROP TABLE t1, t2;
+
+#
+# Bug#26377 - Deadlock with MERGE and FLUSH TABLE
+#
+CREATE TABLE t1 ( a INT ) ENGINE=MyISAM;
+CREATE TABLE m1 ( a INT ) ENGINE=MRG_MYISAM UNION=(t1);
+# Lock t1 first. This did always work.
+LOCK TABLES t1 WRITE, m1 WRITE;
+FLUSH TABLE t1;
+UNLOCK TABLES;
+DROP TABLE m1, t1;
+#
+CREATE TABLE t1 ( a INT ) ENGINE=MyISAM;
+CREATE TABLE m1 ( a INT ) ENGINE=MRG_MYISAM UNION=(t1);
+# Lock m1 first. This did deadlock.
+LOCK TABLES m1 WRITE, t1 WRITE;
+FLUSH TABLE t1;
+UNLOCK TABLES;
+DROP TABLE m1, t1;
+
+#
+# Bug#26867 - LOCK TABLES + REPAIR + merge table result in memory/cpu hogging
+#
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE= MRG_MYISAM UNION= (t1) INSERT_METHOD= LAST;
+LOCK TABLE t1 WRITE;
+    connect (con1,localhost,root,,);
+    send INSERT INTO t2 VALUES (1);
+connection default;
+sleep 1; # Let INSERT go into thr_multi_lock().
+REPAIR TABLE t1;
+sleep 2; # con1 performs busy waiting during this sleep.
+UNLOCK TABLES;
+    connection con1;
+    reap;
+    disconnect con1;
+connection default;
+DROP TABLE t1, t2;
+
+#
+# Bug#27660 - Falcon: merge table possible
+#
+# Normal MyISAM MERGE operation.
+CREATE TABLE t1 (c1 INT, c2 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT, c2 INT) ENGINE= MyISAM;
+CREATE TABLE t3 (c1 INT, c2 INT) ENGINE= MRG_MYISAM UNION(t1, t2);
+INSERT INTO t1 VALUES (1, 1);
+INSERT INTO t2 VALUES (2, 2);
+SELECT * FROM t3;
+# Try an unsupported engine.
+ALTER TABLE t1 ENGINE= MEMORY;
+INSERT INTO t1 VALUES (0, 0);
+# Before fixing, this succeeded, but (0, 0) was missing.
+--error 1168
+SELECT * FROM t3;
+DROP TABLE t1, t2, t3;
+
+#
+# Bug#30275 - Merge tables: flush tables or unlock tables causes server to crash
+#
+CREATE TABLE t1 (c1 INT, KEY(c1));
+CREATE TABLE t2 (c1 INT, KEY(c1)) ENGINE=MRG_MYISAM UNION=(t1)
+  INSERT_METHOD=FIRST;
+LOCK TABLE t1 WRITE, t2 WRITE;
+FLUSH TABLES t2, t1;
+OPTIMIZE TABLE t1;
+FLUSH TABLES t1;
+UNLOCK TABLES;
+#
+FLUSH TABLES;
+INSERT INTO t1 VALUES (1);
+LOCK TABLE t1 WRITE, t2 WRITE;
+FLUSH TABLES t2, t1;
+OPTIMIZE TABLE t1;
+FLUSH TABLES t1;
+UNLOCK TABLES;
+DROP TABLE t1, t2;
+
+#
+# Test derived from test program for
+# Bug#30273 - merge tables: Can't lock file (errno: 155)
+#
+CREATE TABLE t1 (ID INT) ENGINE=MYISAM;
+CREATE TABLE m1 (ID INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=FIRST;
+INSERT INTO t1 VALUES ();
+INSERT INTO m1 VALUES ();
+LOCK TABLE t1 WRITE, m1 WRITE;
+FLUSH TABLES m1, t1;
+OPTIMIZE TABLE t1;
+FLUSH TABLES m1, t1;
+UNLOCK TABLES;
+DROP TABLE t1, m1;
+
diff -Nrup a/mysql-test/t/myisam.test b/mysql-test/t/myisam.test
--- a/mysql-test/t/myisam.test	2007-08-29 14:46:27 +02:00
+++ b/mysql-test/t/myisam.test	2007-09-12 22:11:44 +02:00
@@ -576,32 +576,6 @@ select count(*) from t1 where a is null;
 drop table t1;
 
 #
-# Bug #8306: TRUNCATE leads to index corruption 
-#
-create table t1 (c1 int, index(c1));
-create table t2 (c1 int, index(c1)) engine=merge union=(t1);
-insert into t1 values (1);
-# Close all tables.
-flush tables;
-# Open t2 and (implicitly) t1.
-select * from t2;
-# Truncate after flush works (unless another threads reopens t2 in between).
-flush tables;
-truncate table t1;
-insert into t1 values (1);
-# Close all tables.
-flush tables;
-# Open t2 and (implicitly) t1.
-select * from t2;
-# Truncate t1, wich was not recognized as open without the bugfix.
-# Now, it should fail with a table-in-use error message.
---error 1105
-truncate table t1;
-# The insert used to fail on the crashed table.
-insert into t1 values (1);
-drop table t1,t2;
-
-#
 # bug9188 - Corruption Can't open file: 'table.MYI' (errno: 145)
 #
 create table t1 (c1 int, c2 varchar(4) not null default '',
diff -Nrup a/sql/mysql_priv.h b/sql/mysql_priv.h
--- a/sql/mysql_priv.h	2007-08-31 20:13:22 +02:00
+++ b/sql/mysql_priv.h	2007-09-12 22:11:44 +02:00
@@ -1142,6 +1142,8 @@ TABLE *table_cache_insert_placeholder(TH
 bool lock_table_name_if_not_cached(THD *thd, const char *db,
                                    const char *table_name, TABLE **table);
 TABLE *find_locked_table(THD *thd, const char *db,const char *table_name);
+bool reopen_merge_list(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,
@@ -1393,7 +1395,7 @@ void wait_for_condition(THD *thd, pthrea
                         pthread_cond_t *cond);
 int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags);
 int simple_open_n_lock_tables(THD *thd,TABLE_LIST *tables);
-bool open_and_lock_tables(THD *thd,TABLE_LIST *tables);
+bool open_and_lock_tables(THD *thd,TABLE_LIST *tables, bool no_derived= 0);
 bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags);
 int lock_tables(THD *thd, TABLE_LIST *tables, uint counter, bool *need_reopen);
 int decide_logging_format(THD *thd, TABLE_LIST *tables);
diff -Nrup a/sql/slave.cc b/sql/slave.cc
--- a/sql/slave.cc	2007-08-29 23:28:34 +02:00
+++ b/sql/slave.cc	2007-09-12 22:11:44 +02:00
@@ -1013,8 +1013,10 @@ static int create_table_from_dump(THD* t
     goto err;                   // mysql_parse took care of the error send
 
   thd->proc_info = "Opening master dump table";
+  tables.table= NULL; /* was set by mysql_rm_table(). */
   tables.lock_type = TL_WRITE;
-  if (!open_ltable(thd, &tables, TL_WRITE, 0))
+  tables.required_type= FRMTYPE_TABLE;
+  if (open_and_lock_tables(thd, &tables, TRUE))
   {
     sql_print_error("create_table_from_dump: could not open created table");
     goto err;
diff -Nrup a/sql/sp_head.cc b/sql/sp_head.cc
--- a/sql/sp_head.cc	2007-08-31 20:13:22 +02:00
+++ b/sql/sp_head.cc	2007-09-12 22:11:44 +02:00
@@ -2664,7 +2664,7 @@ int sp_instr::exec_open_and_lock_tables(
     and open and lock them before executing instructions core function.
   */
   if (check_table_access(thd, SELECT_ACL, tables, 0)
-      || open_and_lock_tables(thd, tables))
+      || open_and_lock_tables(thd, tables, FALSE))
     result= -1;
   else
     result= 0;
diff -Nrup a/sql/sql_acl.cc b/sql/sql_acl.cc
--- a/sql/sql_acl.cc	2007-08-13 15:11:13 +02:00
+++ b/sql/sql_acl.cc	2007-09-12 22:11:44 +02:00
@@ -2948,7 +2948,7 @@ bool mysql_table_grant(THD *thd, TABLE_L
       class LEX_COLUMN *column;
       List_iterator <LEX_COLUMN> column_iter(columns);
 
-      if (open_and_lock_tables(thd, table_list))
+      if (open_and_lock_tables(thd, table_list, FALSE))
         DBUG_RETURN(TRUE);
 
       while ((column = column_iter++))
diff -Nrup a/sql/sql_base.cc b/sql/sql_base.cc
--- a/sql/sql_base.cc	2007-09-11 19:42:00 +02:00
+++ b/sql/sql_base.cc	2007-09-12 22:11:44 +02:00
@@ -671,6 +671,58 @@ TABLE_SHARE *get_cached_table_share(cons
 }  
 
 
+/**
+  @brief Detach MERGE children from the parent.
+
+  @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.
+
+  @param[in]    table           the TABLE object of the parent
+  @param[in]    clear_refs      if to clear TABLE references
+                                this must be true when called from
+                                close_thread_tables() to enable fresh
+                                open in open_tables()
+                                it must be false when called in preparation
+                                for reopen_tables()
+*/
+
+static void detach_merge_children(TABLE *table, bool clear_refs)
+{
+  TABLE_LIST *child_l;
+  DBUG_ENTER("detach_merge_children");
+  DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
+                      table->s->table_name.str, (long) table));
+  DBUG_PRINT("myrg", ("MERGE parent table, detach children"));
+
+  /*
+    In a open_tables() loop it can happen that not all tables have their
+    children attached yet.
+  */
+  if (table->children_attached)
+  {
+    VOID(table->file->extra(HA_EXTRA_DETACH_CHILDREN));
+    table->children_attached= FALSE;
+  }
+
+  if (clear_refs)
+  {
+    /* Clear TABLE references to force new assignment at next open.*/
+    for (child_l= table->child_l; ; child_l= child_l->next_global)
+    {
+      /* Do not DBUG_ASSERT(child_l->table); open_tables might be incomplete. */
+      if (child_l->table)
+        child_l->table->parent= NULL;
+      child_l->table= NULL;
+      /* Break when this was the last child. */
+      if (&child_l->next_global == table->child_last_l)
+        break;
+    }
+  }
+  DBUG_VOID_RETURN;
+}
+
+
 /*
   Close file handle, but leave the table in the table cache
 
@@ -711,6 +763,12 @@ void close_handle_and_leave_table_as_loc
     share->tmp_table= INTERNAL_TMP_TABLE;       // for intern_close_table()
   }
 
+  /*
+    When closing a MERGE parent table, detach its children first.
+    Do not clear child table references to allow for reopen.
+  */
+  if (table->child_l)
+    detach_merge_children(table, FALSE);
   table->file->close();
   table->db_stat= 0;                            // Mark file closed
   release_table_share(table->s, RELEASE_NORMAL);
@@ -974,6 +1032,7 @@ bool close_cached_tables(THD *thd, bool 
     /* Set version for table */
     for (TABLE *table=thd->open_tables; table ; table= table->next)
       table->s->version= refresh_version;
+    /* No need to attach MERGE children here. */
   }
   if (!have_lock)
     VOID(pthread_mutex_unlock(&LOCK_open));
@@ -1054,39 +1113,6 @@ bool close_cached_connection_tables(THD 
 }
 
 
-/**
-  @brief Detach MERGE children from the parent.
-
-  @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.
-
-  @param[in]    table           the TABLE object of the parent
-*/
-
-static void detach_merge_children(TABLE *table)
-{
-  TABLE_LIST *child_l;
-  DBUG_ENTER("detach_merge_children");
-  DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
-                      table->s->table_name.str, (long) table));
-  DBUG_PRINT("myrg", ("MERGE parent table, detach children"));
-
-  VOID(table->file->extra(HA_EXTRA_DETACH_CHILDREN));
-
-  /* Clear TABLE reference to force new assignment at next open.*/
-  for (child_l= table->child_l; ; child_l= child_l->next_global)
-  {
-    /* Do not DBUG_ASSERT(child_l->table); open_tables might be incomplete. */
-    child_l->table= NULL;
-    /* Break when this was the last child. */
-    if (&child_l->next_global == table->child_last_l)
-      break;
-  }
-  DBUG_VOID_RETURN;
-}
-
-
 /*
   Mark all tables in the list which were used by current substatement
   as free for reuse.
@@ -1149,11 +1175,21 @@ static void mark_used_tables_as_free_for
 
 void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived)
 {
+  TABLE *table;
   bool found_old_table;
   prelocked_mode_type prelocked_mode= thd->prelocked_mode;
   DBUG_ENTER("close_thread_tables");
 
   /*
+    Detach MERGE children to allow new attach at next open.
+  */
+  for (table= thd->temporary_tables; table; table= table->next)
+  {
+    if (table->child_l)
+      detach_merge_children(table, TRUE);
+  }
+
+  /*
     We are assuming here that thd->derived_tables contains ONLY derived
     tables for this substatement. i.e. instead of approach which uses
     query_id matching for determining which of the derived tables belong
@@ -1166,7 +1202,7 @@ void close_thread_tables(THD *thd, bool 
   */
   if (thd->derived_tables && !skip_derived)
   {
-    TABLE *table, *next;
+    TABLE *next;
     /*
       Close all derived tables generated in queries like
       SELECT * FROM (SELECT * FROM t1)
@@ -1295,8 +1331,8 @@ bool close_thread_table(THD *thd, TABLE 
   DBUG_ENTER("close_thread_table");
   DBUG_ASSERT(table->key_read == 0);
   DBUG_ASSERT(!table->file || table->file->inited == handler::NONE);
-  DBUG_PRINT("tcache", ("table: '%s' 0x%lx", table->s->table_name.str,
-                        (long) table));
+  DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
+                        table->s->table_name.str, (long) table));
 
   *table_ptr=table->next;
   /*
@@ -1305,9 +1341,10 @@ bool close_thread_table(THD *thd, TABLE 
     In most cases they will be closed already at this point. They are
     opened after the parent and thus stacked into thd->open_tables
     before it. Hence detach must not touch the children in any way.
+    Clear child table references to force new assignment at next open.
   */
   if (table->child_l)
-    detach_merge_children(table);
+    detach_merge_children(table, TRUE);
 
   if (table->needs_reopen_or_name_lock() ||
       thd->version != refresh_version || !table->db_stat)
@@ -1741,16 +1778,19 @@ TABLE *find_temporary_table(THD *thd, TA
 bool close_temporary_table(THD *thd, TABLE_LIST *table_list)
 {
   TABLE *table;
+  DBUG_ENTER("close_temporary_table");
+  DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'",
+                          table_list->db, table_list->table_name));
 
   if (!(table= find_temporary_table(thd, table_list)))
-    return 1;
+    DBUG_RETURN(1);
   /*
     If LOCK TABLES list is not empty and contains this table,
     unlock the table and remove the table from this list.
   */
   mysql_lock_remove(thd, thd->locked_tables, table, FALSE);
   close_temporary_table(thd, table, 1, 1);
-  return 0;
+  DBUG_RETURN(0);
 }
 
 /*
@@ -1760,6 +1800,11 @@ bool close_temporary_table(THD *thd, TAB
 void close_temporary_table(THD *thd, TABLE *table,
                            bool free_share, bool delete_table)
 {
+  DBUG_ENTER("close_temporary_table");
+  DBUG_PRINT("tmptable", ("closing table: '%s'.'%s' 0x%lx  alias: '%s'",
+                          table->s->db.str, table->s->table_name.str,
+                          (long) table, table->alias));
+
   if (table->prev)
   {
     table->prev->next= table->next;
@@ -1786,6 +1831,7 @@ void close_temporary_table(THD *thd, TAB
     slave_open_temp_tables--;
   }
   close_temporary(table, free_share, delete_table);
+  DBUG_VOID_RETURN;
 }
 
 
@@ -1801,7 +1847,11 @@ void close_temporary(TABLE *table, bool 
 {
   handlerton *table_type= table->s->db_type();
   DBUG_ENTER("close_temporary");
+  DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'",
+                          table->s->db.str, table->s->table_name.str));
 
+  if (table->child_l)
+    detach_merge_children(table, FALSE);
   free_io_cache(table);
   closefrm(table, 0);
   if (delete_table)
@@ -1894,7 +1944,8 @@ void unlink_open_table(THD *thd, TABLE *
     if (list->s->table_cache_key.length == key_length &&
 	!memcmp(list->s->table_cache_key.str, key, key_length))
     {
-      if (unlock && thd->locked_tables)
+      /* Do not handle locks of MERGE children. */
+      if (unlock && thd->locked_tables && !list->parent)
         mysql_lock_remove(thd, thd->locked_tables, list, TRUE);
       VOID(hash_delete(&open_cache,(uchar*) list)); // Close table
     }
@@ -2634,6 +2685,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
   {
     /* Insert a new TABLE instance into the open cache */
     int error;
+    DBUG_PRINT("tcache", ("opening new table"));
     /* Free cache if too big */
     while (open_cache.records > table_cache_size && unused_tables)
       VOID(hash_delete(&open_cache,(uchar*) unused_tables)); /* purecov: tested */
@@ -2767,6 +2819,97 @@ TABLE *find_locked_table(THD *thd, const
 }
 
 
+/**
+  @brief Fix MERGE children after reopen.
+
+  @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
+
+  @detail Main action is to copy TABLE reference fro each member of
+    original child list to new child list. After a fresh open these
+    references are NULL. Assign the old children to the new table.
+    Some of them might also be reopened or will be reopened soon.
+
+    Other action is to verify that the table definition with respect to
+    the UNION list did not change.
+*/
+
+bool reopen_merge_list(TABLE_LIST *old_child_list, TABLE_LIST **old_last,
+                       TABLE_LIST *new_child_list, TABLE_LIST **new_last)
+{
+  bool mismatch= FALSE;
+  DBUG_ENTER("reopen_merge_list");
+  DBUG_PRINT("myrg", ("old last addr: 0x%lx  new last addr: 0x%lx",
+                      (long) old_last, (long) new_last));
+
+  for (;;)
+  {
+    DBUG_PRINT("myrg", ("old list item: 0x%lx  new list item: 0x%lx",
+                        (long) old_child_list, (long) new_child_list));
+    /* Break if one of the list is at its end. */
+    if (!old_child_list || !new_child_list)
+      break;
+    /* Old table has references to child TABLEs. */
+    DBUG_ASSERT(old_child_list->table);
+    /* New table does not yet have references to child TABLEs. */
+    DBUG_ASSERT(!new_child_list->table);
+    DBUG_PRINT("myrg", ("old table: '%s'.'%s'  new table: '%s'.'%s'",
+                        old_child_list->db, old_child_list->table_name,
+                        new_child_list->db, new_child_list->table_name));
+    /* Child db.table names must match. */
+    if (strcmp(old_child_list->table_name, new_child_list->table_name) ||
+        strcmp(old_child_list->db,         new_child_list->db))
+    {
+      mismatch= TRUE;
+      break;
+    }
+    /*
+      Copy TABLE reference. Child TABLE objects are still in place
+      though not necessarily open yet.
+    */
+    DBUG_PRINT("myrg", ("old table ref: 0x%lx  replaces new table ref: 0x%lx",
+                        (long) old_child_list->table,
+                        (long) new_child_list->table));
+    new_child_list->table= old_child_list->table;
+    /*
+      Break if one of the lists is at its last child. When the child
+      list is part of the statement list, the list may continue after
+      the last child. The test for NULL is insufficient.
+    */
+    DBUG_PRINT("myrg", ("old next addr: 0x%lx  new next addr: 0x%lx",
+                        (long) &old_child_list->next_global,
+                        (long) &new_child_list->next_global));
+    if ((old_last && (&old_child_list->next_global == old_last)) ||
+        (new_last && (&new_child_list->next_global == new_last)))
+      break;
+    /* Step both lists. */
+    old_child_list= old_child_list->next_global;
+    new_child_list= new_child_list->next_global;
+  }
+  DBUG_PRINT("myrg", ("end of list, mismatch: %d", mismatch));
+  if (!mismatch &&
+      (/* old list at end */
+       (!old_child_list || (!old_last && !old_child_list->next_global) ||
+        (old_last && (&old_child_list->next_global == old_last)))
&&
+       /* and new list not at end */
+       (new_child_list && new_child_list->next_global &&
+        (!new_last || (&new_child_list->next_global != new_last)))) ||
+      (/* new list at end */
+       (!new_child_list || (!new_last && !new_child_list->next_global) ||
+        (new_last && (&new_child_list->next_global == new_last)))
&&
+       /* and old list not at end */
+       (old_child_list && old_child_list->next_global &&
+        (!old_last || (&old_child_list->next_global != old_last)))))
+    mismatch= TRUE;
+  if (mismatch)
+    my_error(ER_TABLE_DEF_CHANGED, MYF(0));
+
+  DBUG_RETURN(mismatch);
+}
+
+
 /*
   Reopen an table because the definition has changed.
 
@@ -2792,9 +2935,12 @@ bool reopen_table(TABLE *table)
   TABLE_LIST table_list;
   THD *thd= table->in_use;
   DBUG_ENTER("reopen_table");
+  DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
+                        table->s->table_name.str, (long) table));
 
   DBUG_ASSERT(table->s->ref_count == 0);
   DBUG_ASSERT(!table->sort.io_cache);
+  DBUG_ASSERT(!table->children_attached);
 
 #ifdef EXTRA_DEBUG
   if (table->db_stat)
@@ -2835,6 +2981,17 @@ bool reopen_table(TABLE *table)
   tmp.next=		table->next;
   tmp.prev=		table->prev;
 
+  /* Preserve MERGE parent. */
+  tmp.parent=           table->parent;
+  /* Fix MERGE child list and check for unchanged union. */
+  if ((table->child_l || tmp.child_l) &&
+      reopen_merge_list(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
@@ -2856,6 +3013,7 @@ bool reopen_table(TABLE *table)
   }
   if (table->triggers)
     table->triggers->set_table(table);
+  /* No need to attach MERGE children here. */
 
   broadcast_refresh();
   error=0;
@@ -2907,7 +3065,8 @@ void close_data_files_and_morph_locks(TH
     if (!strcmp(table->s->table_name.str, table_name) &&
 	!strcmp(table->s->db.str, db))
     {
-      if (thd->locked_tables)
+      /* Do not handle locks of MERGE children. */
+      if (thd->locked_tables && !table->parent)
         mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
       table->open_placeholder= 1;
       close_handle_and_leave_table_as_lock(table);
@@ -2942,6 +3101,7 @@ bool reopen_tables(THD *thd,bool get_loc
   TABLE *table,*next,**prev;
   TABLE **tables,**tables_ptr;			// For locks
   bool error=0, not_used;
+  bool merge_table_found= FALSE;
   DBUG_ENTER("reopen_tables");
 
   if (!thd->open_tables)
@@ -2950,10 +3110,15 @@ bool reopen_tables(THD *thd,bool get_loc
   safe_mutex_assert_owner(&LOCK_open);
   if (get_locks)
   {
-    /* The ptr is checked later */
+    /*
+      The ptr is checked later
+      Do not handle locks of MERGE children.
+    */
     uint opens=0;
     for (table= thd->open_tables; table ; table=table->next)
-      opens++;
+      if (!table->parent)
+        opens++;
+    DBUG_PRINT("tcache", ("open tables to lock: %u", opens));
     tables= (TABLE**) my_alloca(sizeof(TABLE*)*opens);
   }
   else
@@ -2965,6 +3130,12 @@ bool reopen_tables(THD *thd,bool get_loc
   {
     uint db_stat=table->db_stat;
     next=table->next;
+    DBUG_PRINT("tcache", ("open table: '%s'.'%s' 0x%lx  "
+                          "parent: 0x%lx  db_stat: %u",
+                          table->s->db.str, table->s->table_name.str,
+                          (long) table, (long) table->parent, db_stat));
+    if (table->child_l && !db_stat)
+      merge_table_found= TRUE;
     if (!tables || (!db_stat && reopen_table(table)))
     {
       my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias);
@@ -2973,9 +3144,12 @@ bool reopen_tables(THD *thd,bool get_loc
     }
     else
     {
+      DBUG_PRINT("tcache", ("opened. need lock: %d",
+                            get_locks && !db_stat && !table->parent));
       *prev= table;
       prev= &table->next;
-      if (get_locks && !db_stat)
+      /* Do not handle locks of MERGE children. */
+      if (get_locks && !db_stat && !table->parent)
 	*tables_ptr++= table;			// need new lock on this
       if (in_refresh)
       {
@@ -2984,6 +3158,36 @@ bool reopen_tables(THD *thd,bool get_loc
       }
     }
   }
+  /*
+    When all tables are open again, we can re-attach MERGE children to
+    their parents. All TABLE objects are still present.
+  */
+  DBUG_PRINT("tcache", ("re-attaching MERGE tables: %d", merge_table_found));
+  if (!error && merge_table_found)
+  {
+    for (table= thd->open_tables; table; table= next)
+    {
+      next= table->next;
+      DBUG_PRINT("tcache", ("check table: '%s'.'%s' 0x%lx  next: 0x%lx",
+                            table->s->db.str, table->s->table_name.str,
+                            (long) table, (long) next));
+      /* Reattach children for MERGE tables with "closed data files" only. */
+      if (table->child_l && !table->children_attached)
+      {
+        DBUG_PRINT("tcache", ("MERGE parent, attach children"));
+        if(table->file->extra(HA_EXTRA_ATTACH_CHILDREN))
+        {
+          my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias);
+          VOID(hash_delete(&open_cache,(uchar*) table));
+          error=1;
+        }
+        else
+          table->children_attached= TRUE;
+      }
+    }
+  }
+  DBUG_PRINT("tcache", ("open tables to lock: %u",
+                        (uint) (tables_ptr - tables)));
   if (tables != tables_ptr)			// Should we get back old locks
   {
     MYSQL_LOCK *lock;
@@ -3029,6 +3233,11 @@ void close_old_data_files(THD *thd, TABL
 
   for (; table ; table=table->next)
   {
+    DBUG_PRINT("tcache", ("checking table: '%s'.'%s' 0x%lx",
+                          table->s->db.str, table->s->table_name.str,
+                          (long) table));
+    DBUG_PRINT("tcache", ("needs refresh: %d  is open: %u",
+                          table->needs_reopen_or_name_lock(), table->db_stat));
     /*
       Reopen marked for flush.
     */
@@ -3037,23 +3246,30 @@ void close_old_data_files(THD *thd, TABL
       found=1;
       if (table->db_stat)
       {
-        if (table->child_l)
-          detach_merge_children(table);
 	if (morph_locks)
 	{
           /*
-            Wake up threads waiting for table-level lock on this table
-            so they won't sneak in when we will temporarily remove our
-            lock on it. This will also give them a chance to close their
-            instances of this table.
-          */
-          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.
+            Forward lock handling to MERGE parent. But unlock parent
+            once only.
           */
-	  table->open_placeholder= 1;
+          TABLE *ulcktbl= table->parent ? table->parent : table;
+          if (ulcktbl->lock_count)
+          {
+            /*
+              Wake up threads waiting for table-level lock on this table
+              so they won't sneak in when we will temporarily remove our
+              lock on it. This will also give them a chance to close their
+              instances of this table.
+            */
+            mysql_lock_abort(thd, ulcktbl, TRUE);
+            mysql_lock_remove(thd, thd->locked_tables, ulcktbl, TRUE);
+            ulcktbl->lock_count= 0;
+            /*
+              We want to protect the table from concurrent DDL operations
+              (like RENAME TABLE) until we will re-open and re-lock it.
+            */
+            ulcktbl->open_placeholder= 1;
+          }
 	}
         close_handle_and_leave_table_as_lock(table);
       }
@@ -3193,7 +3409,9 @@ TABLE *drop_locked_tables(THD *thd,const
     if (!strcmp(table->s->table_name.str, table_name) &&
 	!strcmp(table->s->db.str, db))
     {
-      mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
+      /* If MERGE child, forward lock handling to parent. */
+      mysql_lock_remove(thd, thd->locked_tables,
+                        table->parent ? table->parent : table, TRUE);
       if (!found)
       {
         found= table;
@@ -3242,7 +3460,8 @@ void abort_locked_tables(THD *thd,const 
     if (!strcmp(table->s->table_name.str, table_name) &&
 	!strcmp(table->s->db.str, db))
     {
-      mysql_lock_abort(thd,table, TRUE);
+      /* If MERGE child, forward lock handling to parent. */
+      mysql_lock_abort(thd, table->parent ? table->parent : table, TRUE);
       break;
     }
   }
@@ -3543,11 +3762,17 @@ static int attach_merge_children(TABLE_L
     TABLE *parent= tlist->table;
     TABLE_LIST *child_l;
     DBUG_PRINT("myrg", ("MERGE parent opened, add children to list"));
+    DBUG_ASSERT(!parent->children_attached);
 
     /* Fix children.*/
     for (child_l= parent->child_l; ; child_l= child_l->next_global)
     {
-      DBUG_ASSERT(!child_l->table);
+      /*
+        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. */
@@ -3572,6 +3797,7 @@ static int attach_merge_children(TABLE_L
     TABLE *parent= tlist->parent_l->table;
     int error;
     DBUG_PRINT("myrg", ("last MERGE child opened, attach children"));
+    DBUG_ASSERT(!parent->children_attached);
 
     error= parent->file->extra(HA_EXTRA_ATTACH_CHILDREN);
 
@@ -3592,6 +3818,7 @@ static int attach_merge_children(TABLE_L
       parent->file->print_error(error, MYF(0));
       DBUG_RETURN(1);
     }
+    parent->children_attached= TRUE;
     /*
       Note that we have the cildren in the thd->open_tables list
       at this point.
@@ -3749,8 +3976,7 @@ int open_tables(THD *thd, TABLE_LIST **s
     }
     else
       DBUG_PRINT("tcache", ("referenced table: '%s'.'%s' 0x%lx",
-                            tables->table->s->db.str,
-                            tables->table->s->table_name.str,
+                            tables->db, tables->table_name,
                             (long) tables->table));
 
     if (!tables->table)
@@ -3864,10 +4090,20 @@ int open_tables(THD *thd, TABLE_LIST **s
     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 ((tables->table->child_l || tables->parent_l) &&
         !(thd->locked_tables || thd->prelocked_mode) &&
         attach_merge_children(tables))
     {
+      /*
+        Some functions determine success as (tables->table != NULL).
+        tables->table is in thd->open_tables. It won't go lost.
+      */
+      tables->table= NULL;
       result= -1;
       goto err;
     }
@@ -3984,6 +4220,15 @@ TABLE *open_ltable(THD *thd, TABLE_LIST 
 
   if (table)
   {
+    if (table->child_l)
+    {
+      /* A MERGE table must not come here. */
+      my_error(ER_WRONG_OBJECT, MYF(0), table->s->db.str,
+               table->s->table_name.str, "BASE TABLE");
+      table= 0;
+      goto end;
+    }
+
     table_list->lock_type= lock_type;
     table_list->table=	   table;
     table->grant= table_list->grant;
@@ -4001,6 +4246,8 @@ TABLE *open_ltable(THD *thd, TABLE_LIST 
 	  table= 0;
     }
   }
+
+ end:
   thd->proc_info=0;
   DBUG_RETURN(table);
 }
@@ -4051,6 +4298,7 @@ int simple_open_n_lock_tables(THD *thd, 
     open_and_lock_tables()
     thd		- thread handler
     tables	- list of tables for open&locking
+    no_derived  - if not to handle derived tables
 
   RETURN
     FALSE - ok
@@ -4060,7 +4308,7 @@ int simple_open_n_lock_tables(THD *thd, 
     The lock will automaticaly be freed by close_thread_tables()
 */
 
-bool open_and_lock_tables(THD *thd, TABLE_LIST *tables)
+bool open_and_lock_tables(THD *thd, TABLE_LIST *tables, bool no_derived)
 {
   uint counter;
   bool need_reopen;
@@ -4076,9 +4324,14 @@ bool open_and_lock_tables(THD *thd, TABL
       DBUG_RETURN(-1);
     close_tables_for_reopen(thd, &tables);
   }
-  if (mysql_handle_derived(thd->lex, &mysql_derived_prepare) ||
-      (thd->fill_derived_tables() &&
-       mysql_handle_derived(thd->lex, &mysql_derived_filling)))
+  /*
+    If this is called as a replacement for open_ltable(), we do not want
+    to handle derived tables.
+  */
+  if (!no_derived &&
+      (mysql_handle_derived(thd->lex, &mysql_derived_prepare) ||
+       (thd->fill_derived_tables() &&
+        mysql_handle_derived(thd->lex, &mysql_derived_filling))))
     DBUG_RETURN(TRUE); /* purecov: inspected */
   DBUG_RETURN(0);
 }
@@ -4392,7 +4645,14 @@ int lock_tables(THD *thd, TABLE_LIST *ta
       thd->lock= 0;
       thd->in_lock_tables=0;
 
-      for (table= tables; table != first_not_own; table= table->next_global)
+      /*
+        When open_and_lock_tables() is called for a single table out of
+        a table list, the 'next_global' chain is temporarily broken. We
+        may not find 'first_not_own' before the end of the "list".
+      */
+      for (table= tables;
+           table && table != first_not_own;
+           table= table->next_global)
       {
         if (!table->placeholder())
         {
@@ -4556,6 +4816,8 @@ TABLE *open_temporary_table(THD *thd, co
       slave_open_temp_tables++;
   }
   tmp_table->pos_in_table_list= 0;
+  DBUG_PRINT("tmptable", ("opened table: '%s'.'%s' 0x%lx", tmp_table->s->db.str,
+                          tmp_table->s->table_name.str, (long) tmp_table));
   DBUG_RETURN(tmp_table);
 }
 
@@ -7339,7 +7601,7 @@ bool remove_table_from_cache(THD *thd, c
   TABLE_SHARE *share;
   bool result= 0, signalled= 0;
   DBUG_ENTER("remove_table_from_cache");
-  DBUG_PRINT("enter", ("Table: '%s.%s'  flags: %u", db, table_name, flags));
+  DBUG_PRINT("enter", ("table: '%s'.'%s'  flags: %u", db, table_name, flags));
 
   key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1;
   for (;;)
@@ -7354,6 +7616,8 @@ bool remove_table_from_cache(THD *thd, c
                                    &state))
     {
       THD *in_use;
+      DBUG_PRINT("tcache", ("found table: '%s'.'%s' 0x%lx", table->s->db.str,
+                            table->s->table_name.str, (long) table));
 
       table->s->version=0L;		/* Free when thread is ready */
       if (!(in_use=table->in_use))
@@ -7398,7 +7662,8 @@ bool remove_table_from_cache(THD *thd, c
 	     thd_table ;
 	     thd_table= thd_table->next)
         {
-	  if (thd_table->db_stat)		// If table is open
+          /* Do not handle locks of MERGE children. */
+	  if (thd_table->db_stat && !thd_table->parent)	// If table is open
 	    signalled|= mysql_lock_abort_for_thread(thd, thd_table);
         }
       }
@@ -7601,7 +7866,9 @@ int abort_and_upgrade_lock(ALTER_PARTITI
 
   lpt->old_lock_type= lpt->table->reginfo.lock_type;
   VOID(pthread_mutex_lock(&LOCK_open));
-  mysql_lock_abort(lpt->thd, lpt->table, TRUE);
+  /* If MERGE child, forward lock handling to parent. */
+  mysql_lock_abort(lpt->thd, lpt->table->parent ? lpt->table->parent :
+                   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);
@@ -7628,7 +7895,9 @@ 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));
-  mysql_lock_downgrade_write(lpt->thd, lpt->table, lpt->old_lock_type);
+  /* 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);
 }
 
 
@@ -7701,7 +7970,8 @@ void mysql_wait_completed_table(ALTER_PA
            thd_table ;
            thd_table= thd_table->next)
       {
-        if (thd_table->db_stat)		// If table is open
+        /* Do not handle locks of MERGE children. */
+        if (thd_table->db_stat && !thd_table->parent) // If table is open
           mysql_lock_abort_for_thread(lpt->thd, thd_table);
       }
     }
@@ -7711,8 +7981,10 @@ 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, FALSE);
+  mysql_lock_abort(lpt->thd, my_table->parent ? my_table->parent : my_table,
+                   FALSE);
   VOID(pthread_mutex_unlock(&LOCK_open));
   DBUG_VOID_RETURN;
 }
diff -Nrup a/sql/sql_delete.cc b/sql/sql_delete.cc
--- a/sql/sql_delete.cc	2007-08-13 21:39:24 +02:00
+++ b/sql/sql_delete.cc	2007-09-12 22:11:44 +02:00
@@ -40,7 +40,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *
   SELECT_LEX   *select_lex= &thd->lex->select_lex;
   DBUG_ENTER("mysql_delete");
 
-  if (open_and_lock_tables(thd, table_list))
+  if (open_and_lock_tables(thd, table_list, FALSE))
     DBUG_RETURN(TRUE);
   if (!(table= table_list->table))
   {
diff -Nrup a/sql/sql_insert.cc b/sql/sql_insert.cc
--- a/sql/sql_insert.cc	2007-08-31 01:05:45 +02:00
+++ b/sql/sql_insert.cc	2007-09-12 22:11:45 +02:00
@@ -513,7 +513,7 @@ bool open_and_lock_for_insert_delayed(TH
       Open tables used for sub-selects or in stored functions, will also
       cache these functions.
     */
-    if (open_and_lock_tables(thd, table_list->next_global))
+    if (open_and_lock_tables(thd, table_list->next_global, FALSE))
     {
       end_delayed_insert(thd);
       DBUG_RETURN(TRUE);
@@ -537,7 +537,7 @@ bool open_and_lock_for_insert_delayed(TH
     Use a normal insert.
   */
   table_list->lock_type= TL_WRITE;
-  DBUG_RETURN(open_and_lock_tables(thd, table_list));
+  DBUG_RETURN(open_and_lock_tables(thd, table_list, FALSE));
 }
 
 
@@ -608,7 +608,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *t
   }
   else
   {
-    if (open_and_lock_tables(thd, table_list))
+    if (open_and_lock_tables(thd, table_list, FALSE))
       DBUG_RETURN(TRUE);
   }
 
@@ -2263,9 +2263,18 @@ pthread_handler_t handle_delayed_insert(
     strmov(thd->net.last_error,ER(thd->net.last_errno=ER_OUT_OF_RESOURCES));
     goto err;
   }
+  /*
+    Usually lex_start() is called by mysql_parse(), but we need it here
+    as this thread does not call mysql_parse().
+  */
+  lex_start(thd);
 
   /* open table */
-  if (!(di->table=open_ltable(thd, &di->table_list, TL_WRITE_DELAYED, 0)))
+  di->table_list.next_global= NULL;
+  di->table_list.lock_type= TL_WRITE_DELAYED;
+  di->table_list.required_type= FRMTYPE_TABLE;
+  if (open_and_lock_tables(thd, &di->table_list, TRUE) ||
+      !(di->table= di->table_list.table))
   {
     thd->fatal_error();				// Abort waiting inserts
     goto err;
@@ -3372,6 +3381,37 @@ static TABLE *create_table_from_items(TH
         }
         else
           table= create_table->table;
+
+        /* MERGE children need be open for select. */
+        if (table->child_l)
+        {
+          TABLE_LIST *tlist;
+
+          if (reopen_merge_list((TABLE_LIST*) create_info->merge_list.first,
+                                NULL, table->child_l, table->child_last_l) ||
+              table->file->extra(HA_EXTRA_ATTACH_CHILDREN))
+            DBUG_RETURN(NULL);
+          table->children_attached= TRUE;
+          /*
+            Need to unlock children for locking through parent.
+            If we are under LOCK TABLES, children must not be unlocked.
+          */
+          if (!thd->locked_tables)
+          {
+            *table->child_last_l= NULL;
+            for (tlist= table->child_l; tlist; tlist=tlist->next_global)
+            {
+              /*
+                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, tlist->table, TRUE);
+              mysql_lock_remove(thd, thd->lock, tlist->table, TRUE);
+            }
+          }
+        }
         VOID(pthread_mutex_unlock(&LOCK_open));
       }
       else
@@ -3396,21 +3436,30 @@ static TABLE *create_table_from_items(TH
 
   DBUG_EXECUTE_IF("sleep_create_select_before_lock", my_sleep(6000000););
 
-  table->reginfo.lock_type=TL_WRITE;
-  hooks->prelock(&table, 1);                    // Call prelock hooks
-  if (! ((*lock)= mysql_lock_tables(thd, &table, 1,
-                                    MYSQL_LOCK_IGNORE_FLUSH, &not_used)) ||
+  /*
+    If this is a MERGE table and we are under LOCK TABLES, then all
+    children must be locked already and we must neither unlock them nor
+    try to lock them again. Instead we will just rely on their
+    independent locks.
+  */
+  if (!table->child_l || !thd->locked_tables)
+  {
+    table->reginfo.lock_type= TL_WRITE;
+    hooks->prelock(&table, 1);                    // Call prelock hooks
+    if (! ((*lock)= mysql_lock_tables(thd, &table, 1,
+                                      MYSQL_LOCK_IGNORE_FLUSH, &not_used)) ||
         hooks->postlock(&table, 1))
-  {
-    if (*lock)
     {
-      mysql_unlock_tables(thd, *lock);
-      *lock= 0;
-    }
+      if (*lock)
+      {
+        mysql_unlock_tables(thd, *lock);
+        *lock= 0;
+      }
 
-    if (!create_info->table_existed)
-      drop_open_table(thd, table, create_table->db, create_table->table_name);
-    DBUG_RETURN(0);
+      if (!create_info->table_existed)
+        drop_open_table(thd, table, create_table->db, create_table->table_name);
+      DBUG_RETURN(0);
+    }
   }
   DBUG_RETURN(table);
 }
diff -Nrup a/sql/sql_load.cc b/sql/sql_load.cc
--- a/sql/sql_load.cc	2007-07-30 18:02:19 +02:00
+++ b/sql/sql_load.cc	2007-09-12 22:11:45 +02:00
@@ -146,7 +146,7 @@ bool mysql_load(THD *thd,sql_exchange *e
 	       MYF(0));
     DBUG_RETURN(TRUE);
   }
-  if (open_and_lock_tables(thd, table_list))
+  if (open_and_lock_tables(thd, table_list, FALSE))
     DBUG_RETURN(TRUE);
   if (setup_tables_and_check_access(thd, &thd->lex->select_lex.context,
                                     &thd->lex->select_lex.top_join_list,
diff -Nrup a/sql/sql_parse.cc b/sql/sql_parse.cc
--- a/sql/sql_parse.cc	2007-08-29 21:59:33 +02:00
+++ b/sql/sql_parse.cc	2007-09-12 22:11:45 +02:00
@@ -542,8 +542,10 @@ int mysql_table_dump(THD *thd, LEX_STRIN
   if (lower_case_table_names)
     my_casedn_str(files_charset_info, tbl_name);
 
-  if (!(table=open_ltable(thd, table_list, TL_READ_NO_INSERT, 0)))
+  table_list->required_type= FRMTYPE_TABLE;
+  if (open_and_lock_tables(thd, table_list, TRUE))
     DBUG_RETURN(1);
+  table= table_list->table;
 
   if (check_one_table_access(thd, SELECT_ACL, table_list))
     goto err;
@@ -1904,7 +1906,7 @@ mysql_execute_command(THD *thd)
   }
   case SQLCOM_DO:
     if (check_table_access(thd, SELECT_ACL, all_tables, 0) ||
-        open_and_lock_tables(thd, all_tables))
+        open_and_lock_tables(thd, all_tables, FALSE))
       goto error;
 
     res= mysql_do(thd, *lex->insert_list);
@@ -2237,7 +2239,7 @@ mysql_execute_command(THD *thd)
         create_table->create= TRUE;
       }
 
-      if (!(res= open_and_lock_tables(thd, lex->query_tables)))
+      if (!(res= open_and_lock_tables(thd, lex->query_tables, FALSE)))
       {
         /*
           Is table which we are changing used somewhere in other parts
@@ -2773,7 +2775,7 @@ end_with_restore_list:
       break;
     }
 
-    if (!(res= open_and_lock_tables(thd, all_tables)))
+    if (!(res= open_and_lock_tables(thd, all_tables, FALSE)))
     {
       /* Skip first table, which is the table we are inserting in */
       TABLE_LIST *second_table= first_table->next_local;
@@ -2892,7 +2894,7 @@ end_with_restore_list:
       goto error;
 
     thd->proc_info="init";
-    if ((res= open_and_lock_tables(thd, all_tables)))
+    if ((res= open_and_lock_tables(thd, all_tables, FALSE)))
       break;
 
     if ((res= mysql_multi_delete_prepare(thd)))
@@ -3024,7 +3026,7 @@ end_with_restore_list:
   {
     List<set_var_base> *lex_var_list= &lex->var_list;
     if ((check_table_access(thd, SELECT_ACL, all_tables, 0) ||
-	 open_and_lock_tables(thd, all_tables)))
+	 open_and_lock_tables(thd, all_tables, FALSE)))
       goto error;
     if (lex->one_shot_set && not_all_support_one_shot(lex_var_list))
     {
@@ -3813,7 +3815,7 @@ create_sp_error:
         required for execution.
       */
       if (check_table_access(thd, SELECT_ACL, all_tables, 0) ||
-	  open_and_lock_tables(thd, all_tables))
+	  open_and_lock_tables(thd, all_tables, FALSE))
        goto error;
 
       /*
@@ -4514,7 +4516,7 @@ static bool execute_sqlcom_select(THD *t
       param->select_limit=
         new Item_int((ulonglong) thd->variables.select_limit);
   }
-  if (!(res= open_and_lock_tables(thd, all_tables)))
+  if (!(res= open_and_lock_tables(thd, all_tables, FALSE)))
   {
     if (lex->describe)
     {
diff -Nrup a/sql/sql_partition.cc b/sql/sql_partition.cc
--- a/sql/sql_partition.cc	2007-07-19 18:05:49 +02:00
+++ b/sql/sql_partition.cc	2007-09-12 22:11:45 +02:00
@@ -4928,7 +4928,7 @@ the generated partition syntax in a corr
 
        We use the old partitioning also for the new table. We do this
        by assigning the partition_info from the table loaded in
-       open_ltable to the partition_info struct used by mysql_create_table
+       open_table to the partition_info struct used by mysql_create_table
        later in this method.
 
      Case IIb:
diff -Nrup a/sql/sql_table.cc b/sql/sql_table.cc
--- a/sql/sql_table.cc	2007-08-25 10:43:15 +02:00
+++ b/sql/sql_table.cc	2007-09-12 22:11:45 +02:00
@@ -4016,6 +4016,8 @@ static bool mysql_admin_table(THD* thd, 
     char* db = table->db;
     bool fatal_error=0;
 
+    DBUG_PRINT("admin", ("table: '%s'.'%s'", table->db, table->table_name));
+    DBUG_PRINT("admin", ("extra_open_options: %u", extra_open_options));
     strxmov(table_name, db, ".", table->table_name, NullS);
     thd->open_options|= extra_open_options;
     table->lock_type= lock_type;
@@ -4040,7 +4042,7 @@ static bool mysql_admin_table(THD* thd, 
       if (view_operator_func == NULL)
         table->required_type=FRMTYPE_TABLE;
 
-      open_and_lock_tables(thd, table);
+      open_and_lock_tables(thd, table, FALSE);
       thd->no_warnings_for_error= 0;
       table->next_global= save_next_global;
       table->next_local= save_next_local;
@@ -4048,14 +4050,18 @@ static bool mysql_admin_table(THD* thd, 
     }
     if (prepare_func)
     {
+      DBUG_PRINT("admin", ("calling prepare_func"));
       switch ((*prepare_func)(thd, table, check_opt)) {
       case  1:           // error, message written to net
         ha_autocommit_or_rollback(thd, 1);
         close_thread_tables(thd);
+        DBUG_PRINT("admin", ("simple error, admin next table"));
         continue;
       case -1:           // error, message could be written to net
+        DBUG_PRINT("admin", ("severe error, stop"));
         goto err;
       default:           // should be 0 otherwise
+        DBUG_PRINT("admin", ("prepare_func succeeded"));
         ;
       }
     }
@@ -4070,6 +4076,7 @@ static bool mysql_admin_table(THD* thd, 
     */
     if (!table->table)
     {
+      DBUG_PRINT("admin", ("open table failed"));
       if (!thd->warn_list.elements)
         push_warning(thd, MYSQL_ERROR::WARN_LEVEL_ERROR,
                      ER_CHECK_NO_SUCH_TABLE, ER(ER_CHECK_NO_SUCH_TABLE));
@@ -4160,7 +4167,9 @@ static bool mysql_admin_table(THD* thd, 
 
     }
 
+    DBUG_PRINT("admin", ("calling operator_func '%s'", operator_name));
     result_code = (table->table->file->*operator_func)(thd, check_opt);
+    DBUG_PRINT("admin", ("operator_func returned: %d", result_code));
 
 send_result:
 
@@ -5663,6 +5672,7 @@ bool mysql_alter_table(THD *thd,char *ne
                        uint order_num, ORDER *order, bool ignore)
 {
   TABLE *table, *new_table= 0, *name_lock= 0;
+  TABLE_LIST *save_next_global;
   int error= 0;
   char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN];
   char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias;
@@ -5816,8 +5826,14 @@ view_err:
     start_waiting_global_read_lock(thd);
     DBUG_RETURN(error);
   }
-  if (!(table=open_ltable(thd, table_list, TL_WRITE_ALLOW_READ, 0)))
+  save_next_global= table_list->next_global;
+  table_list->next_global= NULL;
+  table_list->lock_type= TL_WRITE_ALLOW_READ;
+  table_list->required_type= FRMTYPE_TABLE;
+  if (open_and_lock_tables(thd, table_list, TRUE))
     DBUG_RETURN(TRUE);
+  table_list->next_global= save_next_global;
+  table= table_list->table;
   table->use_all_columns();
 
   /* Check that we are not trying to rename to an existing table */
@@ -6566,6 +6582,7 @@ view_err:
       if (reopen_table(table))
         goto err_with_placeholders;
       t_table= table;
+      /* No need to attach MERGE children here. */
     }
     /* Tell the handler that a new frm file is in place. */
     if (t_table->file->create_handler_files(path, NULL, CHF_INDEX_FLAG,
@@ -6593,6 +6610,7 @@ view_err:
     thd->in_lock_tables= 0;
     if (error)
       goto err_with_placeholders;
+    /* No need to attach MERGE children here. */
   }
   VOID(pthread_mutex_unlock(&LOCK_open));
 
@@ -6944,6 +6962,12 @@ bool mysql_recreate_table(THD *thd, TABL
   Alter_info alter_info;
 
   DBUG_ENTER("mysql_recreate_table");
+  DBUG_ASSERT(!table_list->next_global);
+  /*
+    table_list->table has been closed and freed. Do not reference
+    uninitialized data. open_tables() could fail.
+  */
+  table_list->table= NULL;
 
   bzero((char*) &create_info, sizeof(create_info));
   create_info.db_type= 0;
@@ -6975,14 +6999,22 @@ bool mysql_checksum_table(THD *thd, TABL
                             Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
     DBUG_RETURN(TRUE);
 
+  /* Open one table after the other to keep lock time as short as possible. */
   for (table= tables; table; table= table->next_local)
   {
     char table_name[NAME_LEN*2+2];
     TABLE *t;
+    TABLE_LIST *save_next_global;
 
     strxmov(table_name, table->db ,".", table->table_name, NullS);
 
-    t= table->table= open_ltable(thd, table, TL_READ, 0);
+    save_next_global= table->next_global;
+    table->next_global= NULL;
+    table->lock_type= TL_READ;
+    table->required_type=FRMTYPE_TABLE;
+    open_and_lock_tables(thd, table, TRUE);
+    t= table->table;
+    table->next_global= save_next_global;
     thd->clear_error();			// these errors shouldn't get client
 
     protocol->prepare_for_resend();
diff -Nrup a/sql/sql_view.cc b/sql/sql_view.cc
--- a/sql/sql_view.cc	2007-08-28 02:33:54 +02:00
+++ b/sql/sql_view.cc	2007-09-12 22:11:45 +02:00
@@ -408,7 +408,7 @@ bool mysql_create_view(THD *thd, TABLE_L
   }
 #endif
 
-  if (open_and_lock_tables(thd, tables))
+  if (open_and_lock_tables(thd, tables, FALSE))
   {
     res= TRUE;
     goto err;
diff -Nrup a/sql/table.h b/sql/table.h
--- a/sql/table.h	2007-09-11 19:42:00 +02:00
+++ b/sql/table.h	2007-09-12 22:11:45 +02:00
@@ -454,6 +454,7 @@ struct st_table {
   struct st_table *open_next, **open_prev;	/* Link to open tables */
 #endif
   struct st_table *next, *prev;
+  struct st_table *parent;              /* Set in MERGE child tables */
   TABLE_LIST      *child_l;             /* Set in MERGE parent tables */
   TABLE_LIST      **child_last_l;       /* Set in MERGE parent tables */
 
@@ -606,6 +607,7 @@ 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 */
+  my_bool children_attached;            /* Used in MERGE parent tables */
 
   REGINFO reginfo;			/* field connections */
   MEM_ROOT mem_root;
diff -Nrup a/storage/myisammrg/ha_myisammrg.cc b/storage/myisammrg/ha_myisammrg.cc
--- a/storage/myisammrg/ha_myisammrg.cc	2007-09-11 19:42:01 +02:00
+++ b/storage/myisammrg/ha_myisammrg.cc	2007-09-12 22:11:45 +02:00
@@ -235,6 +235,7 @@ static MI_INFO *myisammrg_attach_childre
 {
   ha_myisammrg  *ha_myrg;
   TABLE         *parent;
+  TABLE         *child;
   TABLE_LIST    *child_l;
   MI_INFO       *myisam;
   DBUG_ENTER("myisammrg_attach_children_callback");
@@ -250,6 +251,9 @@ static MI_INFO *myisammrg_attach_childre
     DBUG_PRINT("myrg", ("No more children to attach"));
     DBUG_RETURN(NULL);
   }
+  child= child_l->table;
+  DBUG_PRINT("myrg", ("child table: '%s'.'%s' 0x%lx", child->s->db.str,
+                      child->s->table_name.str, (long) child));
   /*
     Prepare for next child. Used as child_l in next call to this function.
     We cannot rely on a NULL-terminated chain.
@@ -262,12 +266,23 @@ static MI_INFO *myisammrg_attach_childre
   else
     ha_myrg->next_child_attach= child_l->next_global;
 
+  /* Set parent reference. */
+  child->parent= parent;
+
+  /* If parent is temporary, children must be temporary too and vice versa. */
+  if (child->s->tmp_table != parent->s->tmp_table)
+  {
+    DBUG_PRINT("error", ("temporary table mismatch parent: %d  child: %d",
+                         parent->s->tmp_table, child->s->tmp_table));
+    my_errno= HA_ERR_WRONG_MRG_TABLE_DEF;
+  }
+
   /* Extract the MyISAM table structure pointer from the handler object. */
-  if (!(myisam= myisam_engine_handle(child_l->table->file)))
+  if (!(myisam= myisam_engine_handle(child->file)))
   {
-    DBUG_PRINT("error", ("no MyISAM handle for table: '%s' 0x%lx",
-                         child_l->table->s->table_name.str,
-                         (long) child_l->table));
+    DBUG_PRINT("error", ("no MyISAM handle for child table: '%s'.'%s' 0x%lx",
+                         child->s->db.str, child->s->table_name.str,
+                         (long) child));
     my_errno= HA_ERR_WRONG_MRG_TABLE_DEF;
   }
   DBUG_PRINT("myrg", ("MyISAM handle: 0x%lx  my_errno: %d",
@@ -296,6 +311,7 @@ int ha_myisammrg::open(const char *name,
 {
   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;
@@ -322,6 +338,14 @@ int ha_myisammrg::open(const char *name,
     attach only. Its result (recs, recinfo, keyinfo) is stored in
     ha_myisammrg for reuse with later attaches.
 
+  @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
@@ -335,11 +359,13 @@ int ha_myisammrg::attach_children(void)
   DBUG_ENTER("ha_myisammrg::attach_children");
   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));
   DBUG_ASSERT(!this->file->children_attached);
 
   next_child_attach= table->child_l;
   my_errno= 0;
-  if (myrg_attach_children(this->file, this->test_if_locked,
+  if (myrg_attach_children(this->file, this->test_if_locked |
+                           current_thd->open_options,
                            myisammrg_attach_children_callback, this))
   {
     DBUG_PRINT("error", ("my_errno %d", my_errno));
@@ -428,8 +454,11 @@ err:
 int ha_myisammrg::detach_children(void)
 {
   DBUG_ENTER("ha_myisammrg::detach_children");
-  /* Do not test children_attached. After a failed attach it may be FALSE. */
-  if (myrg_detach_children(this->file))
+  /*
+    Do not test children_attached. After a failed attach it may be FALSE.
+    In cases like ALTER TABLE the table could have been closed already.
+  */
+  if (this->file && myrg_detach_children(this->file))
   {
     DBUG_PRINT("error", ("my_errno %d", my_errno));
     DBUG_RETURN(my_errno ? my_errno : -1);
@@ -452,7 +481,11 @@ int ha_myisammrg::close(void)
 {
   int rc;
   DBUG_ENTER("ha_myisammrg::close");
-  DBUG_ASSERT(!this->file->children_attached);
+  /*
+    Children must not be attached here. Unless the MERGE table has no
+    children. In this case children_attached is always true.
+  */
+  DBUG_ASSERT(!this->file->children_attached || !this->file->tables);
   rc= myrg_close(file);
   file= 0;
   DBUG_RETURN(rc);
@@ -848,47 +881,50 @@ int ha_myisammrg::create(const char *nam
   uint dirlgt= dirname_length(name);
   DBUG_ENTER("ha_myisammrg::create");
 
+  /* 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);
+
+  /* Create child path names. */
   for (pos= table_names; tables; tables= tables->next_local)
   {
     const char *table_name;
-    TABLE *tbl= 0;
-    if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
-      tbl= find_temporary_table(thd, tables);
-    if (!tbl)
-    {
-      /*
-        Construct the path to the MyISAM table. Try to meet two conditions:
-        1.) Allow to include MyISAM tables from different databases, and
-        2.) allow for moving DATADIR around in the file system.
-        The first means that we need paths in the .MRG file. The second
-        means that we should not have absolute paths in the .MRG file.
-        The best, we can do, is to use 'mysql_data_home', which is '.'
-        in mysqld and may be an absolute path in an embedded server.
-        This means that it might not be possible to move the DATADIR of
-        an embedded server without changing the paths in the .MRG file.
-      */
-      uint length= build_table_filename(buff, sizeof(buff),
-                                        tables->db, tables->table_name, "", 0);
-      /*
-        If a MyISAM table is in the same directory as the MERGE table,
-        we use the table name without a path. This means that the
-        DATADIR can easily be moved even for an embedded server as long
-        as the MyISAM tables are from the same database as the MERGE table.
-      */
-      if ((dirname_length(buff) == dirlgt) && ! memcmp(buff, name, dirlgt))
-        table_name= tables->table_name;
-      else
-        if (! (table_name= thd->strmake(buff, length)))
-          DBUG_RETURN(HA_ERR_OUT_OF_MEM);
-    }
+
+    /*
+      Construct the path to the MyISAM table. Try to meet two conditions:
+      1.) Allow to include MyISAM tables from different databases, and
+      2.) allow for moving DATADIR around in the file system.
+      The first means that we need paths in the .MRG file. The second
+      means that we should not have absolute paths in the .MRG file.
+      The best, we can do, is to use 'mysql_data_home', which is '.'
+      in mysqld and may be an absolute path in an embedded server.
+      This means that it might not be possible to move the DATADIR of
+      an embedded server without changing the paths in the .MRG file.
+
+      Do the same even for temporary tables. MERGE children are now
+      opened through the table cache. They are opened by db.table_name,
+      not by their path name.
+    */
+    uint length= build_table_filename(buff, sizeof(buff),
+                                      tables->db, tables->table_name, "", 0);
+    /*
+      If a MyISAM table is in the same directory as the MERGE table,
+      we use the table name without a path. This means that the
+      DATADIR can easily be moved even for an embedded server as long
+      as the MyISAM tables are from the same database as the MERGE table.
+    */
+    if ((dirname_length(buff) == dirlgt) && ! memcmp(buff, name, dirlgt))
+      table_name= tables->table_name;
     else
-      table_name= tbl->s->path.str;
+      if (! (table_name= thd->strmake(buff, length)))
+        DBUG_RETURN(HA_ERR_OUT_OF_MEM);
+
     *pos++= table_name;
   }
   *pos=0;
+
+  /* Create a MERGE meta file from the table_names array. */
   DBUG_RETURN(myrg_create(fn_format(buff,name,"","",
                                     MY_RESOLVE_SYMLINKS|
                                     MY_UNPACK_FILENAME|MY_APPEND_EXT),
diff -Nrup a/storage/myisammrg/myrg_close.c b/storage/myisammrg/myrg_close.c
--- a/storage/myisammrg/myrg_close.c	2007-08-13 09:49:48 +02:00
+++ b/storage/myisammrg/myrg_close.c	2007-09-12 22:11:45 +02:00
@@ -35,6 +35,10 @@ int myrg_close(MYRG_INFO *info)
     might be true. We would close the children though they should be
     closed independently and info->rec_per_key_part is not freed.
     This should be acceptable for a panic.
+    In case of a MySQL server and no children, children_attached is
+    always true. In this case no rec_per_key_part has been allocated.
+    So it is correct to use the branch where an empty list of tables is
+    (not) closed.
   */
   if (info->children_attached)
   {
diff -Nrup a/storage/myisammrg/myrg_open.c b/storage/myisammrg/myrg_open.c
--- a/storage/myisammrg/myrg_open.c	2007-08-13 09:49:48 +02:00
+++ b/storage/myisammrg/myrg_open.c	2007-09-12 22:11:45 +02:00
@@ -281,6 +281,11 @@ MYRG_INFO *myrg_parent_open(const char *
   m_info->merge_insert_method= insert_method > 0 ? insert_method : 0;
   /* This works even if the table list is empty. */
   m_info->end_table= m_info->open_tables + child_count;
+  if (!child_count)
+  {
+    /* Do not attach/detach an empty child list. */
+    m_info->children_attached= TRUE;
+  }
 
   /* Call callback for each child. */
   dir_length= dirname_part(parent_name_buff, parent_name, &name_buff_length);
@@ -369,6 +374,7 @@ int myrg_attach_children(MYRG_INFO *m_in
   uint       key_parts;
   uint       min_keys;
   DBUG_ENTER("myrg_attach_children");
+  DBUG_PRINT("myrg", ("handle_locking: %d", handle_locking));
 
   rc= 1;
   errpos= 0;
@@ -378,6 +384,8 @@ int myrg_attach_children(MYRG_INFO *m_in
   child_nr= 0;
   while ((myisam= (*callback)(callback_param)))
   {
+    DBUG_PRINT("myrg", ("child_nr: %u  table: '%s'",
+                        child_nr, myisam->filename));
     DBUG_ASSERT(child_nr < m_info->tables);
 
     /* Special handling when the first child is attached. */
@@ -403,6 +411,9 @@ int myrg_attach_children(MYRG_INFO *m_in
     /* Check table definition match. */
     if (m_info->reclength != myisam->s->base.reclength)
     {
+      DBUG_PRINT("error", ("definition mismatch table: '%s'  repair: %d",
+                           myisam->filename,
+                           (handle_locking & HA_OPEN_FOR_REPAIR)));
       my_errno= HA_ERR_WRONG_MRG_TABLE_DEF;
       if (handle_locking & HA_OPEN_FOR_REPAIR)
       {
@@ -443,6 +454,7 @@ err:
   switch (errpos) {
   case 1:
     my_free((char*) m_info->rec_per_key_part, MYF(0));
+    m_info->rec_per_key_part= NULL;
   }
   my_errno= save_errno;
   DBUG_RETURN(1);
@@ -465,8 +477,12 @@ err:
 int myrg_detach_children(MYRG_INFO *m_info)
 {
   DBUG_ENTER("myrg_detach_children");
-  m_info->children_attached= FALSE;
-  bzero((char*) m_info->open_tables, m_info->tables * sizeof(MYRG_TABLE));
+  if (m_info->tables)
+  {
+    /* Do not attach/detach an empty child list. */
+    m_info->children_attached= FALSE;
+    bzero((char*) m_info->open_tables, m_info->tables * sizeof(MYRG_TABLE));
+  }
   m_info->records= 0;
   m_info->del= 0;
   m_info->data_file_length= 0;
Thread
bk commit into 5.1 tree (istruewing:1.2609) BUG#26379Ingo Struewing12 Sep