summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorunknown <istruewing@stella.local>2007-11-15 20:25:43 +0100
committerunknown <istruewing@stella.local>2007-11-15 20:25:43 +0100
commitc8450b278d7f049e0afa49624c52e0f61b541126 (patch)
tree69cf3030a364a25215179c5f289ef3804553bfc8
parent62d3c3d3a1c7ceed7170913189dd0e4437f2d37e (diff)
downloadmariadb-git-c8450b278d7f049e0afa49624c52e0f61b541126.tar.gz
Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
corrupts a MERGE table Bug 26867 - LOCK TABLES + REPAIR + merge table result in memory/cpu hogging Bug 26377 - Deadlock with MERGE and FLUSH TABLE Bug 25038 - Waiting TRUNCATE Bug 25700 - merge base tables get corrupted by optimize/analyze/repair table Bug 30275 - Merge tables: flush tables or unlock tables causes server to crash Bug 19627 - temporary merge table locking Bug 27660 - Falcon: merge table possible Bug 30273 - merge tables: Can't lock file (errno: 155) The problems were: Bug 26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table 1. A thread trying to lock a MERGE table performs busy waiting while REPAIR TABLE or a similar table administration task is ongoing on one or more of its MyISAM tables. 2. A thread trying to lock a MERGE table performs busy waiting until all threads that did REPAIR TABLE or similar table administration tasks on one or more of its MyISAM tables in LOCK TABLES segments do UNLOCK TABLES. The difference against problem #1 is that the busy waiting takes place *after* the administration task. It is terminated by UNLOCK TABLES only. 3. Two FLUSH TABLES within a LOCK TABLES segment can invalidate the lock. This does *not* require a MERGE table. The first FLUSH TABLES can be replaced by any statement that requires other threads to reopen the table. In 5.0 and 5.1 a single FLUSH TABLES can provoke the problem. Bug 26867 - LOCK TABLES + REPAIR + merge table result in memory/cpu hogging Trying DML on a MERGE table, which has a child locked and repaired by another thread, made an infinite loop in the server. Bug 26377 - Deadlock with MERGE and FLUSH TABLE Locking a MERGE table and its children in parent-child order and flushing the child deadlocked the server. Bug 25038 - Waiting TRUNCATE Truncating a MERGE child, while the MERGE table was in use, let the truncate fail instead of waiting for the table to become free. Bug 25700 - merge base tables get corrupted by optimize/analyze/repair table Repairing a child of an open MERGE table corrupted the child. It was necessary to FLUSH the child first. Bug 30275 - Merge tables: flush tables or unlock tables causes server to crash Flushing and optimizing locked MERGE children crashed the server. Bug 19627 - temporary merge table locking Use of a temporary MERGE table with non-temporary children could corrupt the children. Temporary tables are never locked. So we do now prohibit non-temporary chidlren of a temporary MERGE table. Bug 27660 - Falcon: merge table possible It was possible to create a MERGE table with non-MyISAM children. Bug 30273 - merge tables: Can't lock file (errno: 155) This was a Windows-only bug. Table administration statements sometimes failed with "Can't lock file (errno: 155)". These bugs are fixed by a new implementation of MERGE table open. When opening a MERGE table in open_tables() we do now add the child tables to the list of tables to be opened by open_tables() (the "query_list"). The children are not opened in the handler at this stage. After opening the parent, open_tables() opens each child from the now extended query_list. When the last child is opened, we remove the children from the query_list again and attach the children to the parent. This behaves similar to the old open. However it does not open the MyISAM tables directly, but grabs them from the already open children. When closing a MERGE table in close_thread_table() we detach the children only. Closing of the children is done implicitly because they are in thd->open_tables. For more detail see the comment at the top of ha_myisammrg.cc. Changed from open_ltable() to open_and_lock_tables() in all places that can be relevant for MERGE tables. The latter can handle tables added to the list on the fly. When open_ltable() was used in a loop over a list of tables, the list must be temporarily terminated after every table for open_and_lock_tables(). table_list->required_type is set to FRMTYPE_TABLE to avoid open of special tables. Handling of derived tables is suppressed. These details are handled by the new function open_n_lock_single_table(), which has nearly the same signature as open_ltable() and can replace it in most cases. In reopen_tables() some of the tables open by a thread can be closed and reopened. When a MERGE child is affected, the parent must be closed and reopened too. Closing of the parent is forced before the first child is closed. Reopen happens in the order of thd->open_tables. MERGE parents do not attach their children automatically at open. This is done after all tables are reopened. So all children are open when attaching them. Special lock handling like mysql_lock_abort() or mysql_lock_remove() needs to be suppressed for MERGE children or forwarded to the parent. This depends on the situation. In loops over all open tables one suppresses child lock handling. When a single table is touched, forwarding is done. Behavioral changes: =================== This patch changes the behavior of temporary MERGE tables. Temporary MERGE must have temporary children. The old behavior was wrong. A temporary table is not locked. Hence even non-temporary children were not locked. See Bug 19627 - temporary merge table locking. You cannot change the union list of a non-temporary MERGE table when LOCK TABLES is in effect. The following does *not* work: CREATE TABLE m1 ... ENGINE=MRG_MYISAM ...; LOCK TABLES t1 WRITE, t2 WRITE, m1 WRITE; ALTER TABLE m1 ... UNION=(t1,t2) ...; However, you can do this with a temporary MERGE table. You cannot create a MERGE table with CREATE ... SELECT, neither as a temporary MERGE table, nor as a non-temporary MERGE table. CREATE TABLE m1 ... ENGINE=MRG_MYISAM ... SELECT ...; Gives error message: table is not BASE TABLE. include/my_base.h: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Added HA_EXTRA_ATTACH_CHILDREN and HA_EXTRA_DETACH_CHILDREN. include/myisammrg.h: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Added element 'children_attached' to MYRG_INFO. Added declarations for myrg_parent_open(), myrg_attach_children() and myrg_detach_children() for the new MERGE table open approach. mysql-test/extra/binlog_tests/blackhole.test: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Preliminarily added new error message with a comment. mysql-test/r/create.result: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Fixed test result. mysql-test/r/delayed.result: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Moved test result from here to merge.result. mysql-test/r/merge.result: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Fixed/added test result. mysql-test/r/myisam.result: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Moved test result for bug 8306 from here to merge.result. mysql-test/suite/binlog/r/binlog_stm_blackhole.result: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Fixed test result. mysql-test/t/create.test: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Fixed error number. mysql-test/t/delayed.test: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Moved test from here to merge.test. mysql-test/t/merge.test: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Fixed test for new temporary MERGE table behavior. Exchanged error numbers by symbolic codes. Added tests. Included are tests for bugs 8306 (moved from myisam.test), 26379, 19627, 25038, 25700, 26377, 26867, 27660, 30275, and 30273. Fixed changes resulting from disabled CREATE...SELECT. Integrated tests moved from delayed.test and myisam.test to here. mysql-test/t/myisam.test: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Moved test for bug 8306 from here to merge.test. mysys/thr_lock.c: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Added code to let the owner of a high priority lock (TL_WRITE_ONLY) to bypass its own lock. sql/ha_ndbcluster_binlog.cc: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Added 'thd' argument to init_tmp_table_share(). sql/handler.cc: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Added 'thd' argument to init_tmp_table_share(). sql/mysql_priv.h: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Removed declaration of check_merge_table_access(). It is now static in sql_parse.cc. Added declaration for fix_merge_after_open(). Renamed open_and_lock_tables() to open_and_lock_tables_derived() with additional parameter 'derived'. Added inline functions simple_open_n_lock_tables() and open_and_lock_tables(), which call open_and_lock_tables_derived() and add the argument for 'derived'. Added new function open_n_lock_single_table(), which can be used as an replacement for open_ltable() in most situations. Internally it calls simple_open_n_lock_tables() so hat it is appropriate for MERGE tables. Added 'thd' argument to init_tmp_table_share(). sql/slave.cc: ug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Added comment. sql/sql_base.cc: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Defined new functions add_merge_table_list(), attach_merge_children(), detach_merge_children(), and fix_merge_after_open() for the new MERGE table open approach. Added calls of the new functions to close_handle_and_leave_table_as_lock(), close_thread_tables(), close_thread_table(), unlink_open_table(), reopen_name_locked_table(), reopen_table(), drop_locked_tables(), close_temporary_table(), and open_tables() respectively. Prevented special lock handling of merge children (like mysql_lock_remove, mysql_lock_merge or mysql_lock_abort) at many places. Some of these calls are forwarded to the parent table instead. Added code to set thd->some_tables_deleted for every thread that has a table open that we are flushing. Added code for MERGE tables to unlink_open_table(). Added MERGE children to the list of unusable tables in open_table(). Added MERGE table handling to reopen_table(). Added lock handling and closing of a parent before the children in close_data_files_and_morph_locks(). Added code for re-attaching children in reopen_tables(). Added MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN to the locking flags and error reporting after mysql_lock_tables() in reopen_tables(). Added lock handling and closing of a parent before the children in close_old_data_files(). Added lock handling and detaching in drop_locked_tables(). Added code for removing the children list from the statement list to prepare for a repetition in open_tables(). Added new function open_n_lock_single_table(), which can be used as an replacement for open_ltable() in most situations. Internally it calls simple_open_n_lock_tables() so hat it is appropriate for MERGE tables. Disabled use of open_ltable() for MERGE tables. Removed function simple_open_n_lock_tables(). It is now inline declared in mysql_priv.h. Renamed open_and_lock_tables() to open_and_lock_tables_derived() with additional parameter 'derived'. open_and_lock_tables() is now inline declared in mysql_priv.h. Added a check for end-of-list in two loops in lock_tables(). Added 'thd' argument to init_tmp_table_share(). sql/sql_insert.cc: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Changed from open_ltable() to open_n_lock_single_table() in handle_delayed_insert(). Reestablished LEX settings after lex initialization. Added 'thd' argument to init_tmp_table_share(). sql/sql_parse.cc: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Made check_merge_table_access() a static function. Disabled use of CREATE...SELECT for MERGE tables. sql/sql_partition.cc: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Fixed comment typo. sql/sql_select.cc: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Added 'thd' argument to init_tmp_table_share(). sql/sql_table.cc: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Optimized use of mysql_ha_flush() in mysql_rm_table_part2(). Disabled the use of MERGE tables with prepare_for_restore() and prepare_for_repair(). Changed from open_ltable() to open_n_lock_single_table() in mysql_alter_table() and mysql_checksum_table(). Disabled change of child list under LOCK TABLES. Initialized table_list->table in mysql_recreate_table(). sql/sql_trigger.cc: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Added code for allowing CREATE TRIGGER under LOCK TABLE, to be able to test it with MERGE tables. sql/table.cc: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Added 'thd' argument to init_tmp_table_share(). Setting table_map_id from query_id in init_tmp_table_share(). Added member function TABLE::is_children_attached(). sql/table.h: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Added access method get_table_def_version() to TABLE_SHARE. Added elements for MERGE tables to TABLE and TABLE_LIST. storage/myisam/ha_myisam.cc: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Added an unrelated comment to the function comment of table2myisam(). storage/myisam/ha_myisam.h: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Added new member function MI_INFO::file_ptr(). storage/myisammrg/ha_myisammrg.cc: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Added callback functions to support parent open and children attach of MERGE tables. Changed ha_myisammrg::open() to initialize storage engine structures and create a list of child tables only. Child tables are not opened. Added ha_myisammrg::attach_children(), which does now the main part of MERGE open. Added ha_myisammrg::detach_children(). Added calls to ::attach_children() and ::detach_children() to ::extra() on HA_EXTRA_ATTACH_CHILDREN and HA_EXTRA_DETACH_CHILDREN respectively. Added a check for matching TEMPORARY type for children against parent. Added a check for table def version. Added support for thd->open_options to attach_children(). Changed child path name generation for temporary tables so that it does nothing special for temporary tables. storage/myisammrg/ha_myisammrg.h: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Added elements to class ha_myisammrg to support the new open approach. Changed empty destructor definition to a declaration. Implemented in ha_myisammrg.cc. Added declaration for methods attach_children() and detach_children(). Added definition for method table_ptr() for use with callback functions. storage/myisammrg/myrg_close.c: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Added a check to avoid closing of MyISAM tables when the child tables are not attached. Added freeing of rec_per_key_part when the child tables are not attached. storage/myisammrg/myrg_extra.c: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Some ::extra() functions and ::reset() can be called when children are detached. storage/myisammrg/myrg_open.c: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table Kept old myrg_open() for MERGE use independent from MySQL. Removed an always true condition in myrg_open(). Set children_attached for independent MERGE use in myrg_open(). Added myrg_parent_open(), myrg_attach_children(), and myrg_detach_children() for the new MERGE table open approach. mysql-test/r/merge-big.result: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table New test result mysql-test/t/merge-big.test: Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table New test case
-rw-r--r--include/my_base.h8
-rw-r--r--include/myisammrg.h9
-rw-r--r--mysql-test/extra/binlog_tests/blackhole.test9
-rw-r--r--mysql-test/r/create.result2
-rw-r--r--mysql-test/r/delayed.result5
-rw-r--r--mysql-test/r/merge-big.result77
-rw-r--r--mysql-test/r/merge.result1094
-rw-r--r--mysql-test/r/myisam.result18
-rw-r--r--mysql-test/suite/binlog/r/binlog_stm_blackhole.result1
-rw-r--r--mysql-test/t/create.test2
-rw-r--r--mysql-test/t/delayed.test8
-rw-r--r--mysql-test/t/merge-big.test150
-rw-r--r--mysql-test/t/merge.test847
-rw-r--r--mysql-test/t/myisam.test26
-rw-r--r--mysys/thr_lock.c33
-rw-r--r--sql/ha_ndbcluster_binlog.cc2
-rw-r--r--sql/handler.cc11
-rw-r--r--sql/mysql_priv.h24
-rw-r--r--sql/slave.cc6
-rw-r--r--sql/sql_base.cc1072
-rw-r--r--sql/sql_insert.cc14
-rw-r--r--sql/sql_parse.cc73
-rw-r--r--sql/sql_partition.cc2
-rw-r--r--sql/sql_select.cc2
-rw-r--r--sql/sql_table.cc80
-rw-r--r--sql/sql_trigger.cc44
-rw-r--r--sql/table.cc29
-rw-r--r--sql/table.h33
-rw-r--r--storage/myisam/ha_myisam.cc4
-rw-r--r--storage/myisam/ha_myisam.h4
-rw-r--r--storage/myisammrg/ha_myisammrg.cc668
-rw-r--r--storage/myisammrg/ha_myisammrg.h9
-rw-r--r--storage/myisammrg/myrg_close.c34
-rw-r--r--storage/myisammrg/myrg_extra.c4
-rw-r--r--storage/myisammrg/myrg_open.c334
35 files changed, 4413 insertions, 325 deletions
diff --git a/include/my_base.h b/include/my_base.h
index 339554979a8..f79ff8805b9 100644
--- a/include/my_base.h
+++ b/include/my_base.h
@@ -187,7 +187,13 @@ enum ha_extra_function {
Inform handler that an "INSERT...ON DUPLICATE KEY UPDATE" will be
executed. This condition is unset by HA_EXTRA_NO_IGNORE_DUP_KEY.
*/
- HA_EXTRA_INSERT_WITH_UPDATE
+ HA_EXTRA_INSERT_WITH_UPDATE,
+ /*
+ Orders MERGE handler to attach or detach its child tables. Used at
+ begin and end of a statement.
+ */
+ HA_EXTRA_ATTACH_CHILDREN,
+ HA_EXTRA_DETACH_CHILDREN
};
/* The following is parameter to ha_panic() */
diff --git a/include/myisammrg.h b/include/myisammrg.h
index eed50bebaee..0cf3aa222b3 100644
--- a/include/myisammrg.h
+++ b/include/myisammrg.h
@@ -69,6 +69,8 @@ typedef struct st_myrg_info
uint merge_insert_method;
uint tables,options,reclength,keys;
my_bool cache_in_use;
+ /* If MERGE children attached to parent. See top comment in ha_myisammrg.cc */
+ my_bool children_attached;
LIST open_list;
QUEUE by_key;
ulong *rec_per_key_part; /* for sql optimizing */
@@ -80,6 +82,13 @@ typedef struct st_myrg_info
extern int myrg_close(MYRG_INFO *file);
extern int myrg_delete(MYRG_INFO *file,const uchar *buff);
extern MYRG_INFO *myrg_open(const char *name,int mode,int wait_if_locked);
+extern MYRG_INFO *myrg_parent_open(const char *parent_name,
+ int (*callback)(void*, const char*),
+ void *callback_param);
+extern int myrg_attach_children(MYRG_INFO *m_info, int handle_locking,
+ MI_INFO *(*callback)(void*),
+ void *callback_param);
+extern int myrg_detach_children(MYRG_INFO *m_info);
extern int myrg_panic(enum ha_panic_function function);
extern int myrg_rfirst(MYRG_INFO *file,uchar *buf,int inx);
extern int myrg_rlast(MYRG_INFO *file,uchar *buf,int inx);
diff --git a/mysql-test/extra/binlog_tests/blackhole.test b/mysql-test/extra/binlog_tests/blackhole.test
index 1ca59955d76..df2295af4ff 100644
--- a/mysql-test/extra/binlog_tests/blackhole.test
+++ b/mysql-test/extra/binlog_tests/blackhole.test
@@ -134,6 +134,15 @@ drop table t1,t2,t3;
# table
#
CREATE TABLE t1(a INT) ENGINE=BLACKHOLE;
+# NOTE: After exchanging open_ltable() by open_and_lock_tables() in
+# handle_delayed_insert() to fix problems with MERGE tables (Bug#26379),
+# problems with INSERT DELAYED and BLACKHOLE popped up. open_ltable()
+# does not check if the binlogging capabilities of the statement and the
+# table match. So the below used to succeed. But since INSERT DELAYED
+# switches to row-based logging in mixed-mode and BLACKHOLE cannot do
+# row-based logging, it could not really work. Until this problem is
+# correctly fixed, we have that error here.
+--error ER_BINLOG_LOGGING_IMPOSSIBLE
INSERT DELAYED INTO t1 VALUES(1);
DROP TABLE t1;
diff --git a/mysql-test/r/create.result b/mysql-test/r/create.result
index ca3b344af53..73a9dba4e69 100644
--- a/mysql-test/r/create.result
+++ b/mysql-test/r/create.result
@@ -594,7 +594,7 @@ create table t1 (a int);
create table t1 select * from t1;
ERROR HY000: You can't specify target table 't1' for update in FROM clause
create table t2 union = (t1) select * from t1;
-ERROR HY000: You can't specify target table 't1' for update in FROM clause
+ERROR HY000: 'test.t2' is not BASE TABLE
flush tables with read lock;
unlock tables;
drop table t1;
diff --git a/mysql-test/r/delayed.result b/mysql-test/r/delayed.result
index c520ab52ab3..92a20630d77 100644
--- a/mysql-test/r/delayed.result
+++ b/mysql-test/r/delayed.result
@@ -250,11 +250,6 @@ SELECT HEX(a) FROM t1;
HEX(a)
1
DROP TABLE t1;
-CREATE TABLE t1(c1 INT) ENGINE=MyISAM;
-CREATE TABLE t2(c1 INT) ENGINE=MERGE UNION=(t1);
-INSERT DELAYED INTO t2 VALUES(1);
-ERROR HY000: Table storage engine for 't2' doesn't have this option
-DROP TABLE t1, t2;
DROP TABLE IF EXISTS t1,t2;
SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO';
CREATE TABLE `t1` (
diff --git a/mysql-test/r/merge-big.result b/mysql-test/r/merge-big.result
new file mode 100644
index 00000000000..82fedc1df73
--- /dev/null
+++ b/mysql-test/r/merge-big.result
@@ -0,0 +1,77 @@
+drop table if exists t1,t2,t3,t4,t5,t6;
+#
+# Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
+# corrupts a MERGE table
+# Problem #3
+#
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+LOCK TABLE t1 WRITE;
+# connection con1
+SET SESSION debug="+d,sleep_open_and_lock_after_open";
+INSERT INTO t1 VALUES (1);
+# connection default
+# Let INSERT go into thr_multi_lock().
+# Kick INSERT out of thr_multi_lock().
+FLUSH TABLES;
+# Let INSERT go through open_tables() where it sleeps.
+# Unlock and close table and wait for con1 to close too.
+FLUSH TABLES;
+# This should give no result.
+SELECT * FROM t1;
+c1
+UNLOCK TABLES;
+# connection con1
+SET SESSION debug="-d,sleep_open_and_lock_after_open";
+# connection default
+DROP TABLE t1;
+#
+# Extra tests for Bug#26379 - Combination of FLUSH TABLE and
+# REPAIR TABLE corrupts a MERGE table
+#
+CREATE TABLE t1 (c1 INT);
+CREATE TABLE t2 (c1 INT);
+CREATE TABLE t3 (c1 INT);
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+INSERT INTO t3 VALUES (3);
+#
+# CREATE ... SELECT
+# try to access parent from another thread.
+#
+# connection con1
+SET SESSION debug="+d,sleep_create_select_before_lock";
+CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+INSERT_METHOD=FIRST SELECT * FROM t3;
+# connection default
+# Now try to access the parent.
+# If 3 is in table, SELECT had to wait.
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+# connection con1
+SET SESSION debug="-d,sleep_create_select_before_lock";
+# connection default
+# Cleanup for next test.
+DROP TABLE t4;
+DELETE FROM t1 WHERE c1 != 1;
+#
+# CREATE ... SELECT
+# try to access child from another thread.
+#
+# connection con1
+SET SESSION debug="+d,sleep_create_select_before_lock";
+CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+INSERT_METHOD=FIRST SELECT * FROM t3;
+# connection default
+# Now try to access a child.
+# If 3 is in table, SELECT had to wait.
+SELECT * FROM t1 ORDER BY c1;
+c1
+1
+3
+# connection con1
+SET SESSION debug="-d,sleep_create_select_before_lock";
+# connection default
+DROP TABLE t1, t2, t3, t4;
diff --git a/mysql-test/r/merge.result b/mysql-test/r/merge.result
index d6e19107ec4..74441db2512 100644
--- a/mysql-test/r/merge.result
+++ b/mysql-test/r/merge.result
@@ -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,54 @@ 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...SELECT is not implemented for MERGE tables.
+CREATE TEMPORARY TABLE t1 (c1 INT NOT NULL);
+CREATE TEMPORARY TABLE t2 (c1 INT NOT NULL);
+CREATE TABLE t3 (c1 INT NOT NULL);
+INSERT INTO t3 VALUES (3), (33);
+LOCK TABLES t3 READ;
+CREATE TEMPORARY TABLE t4 (c1 INT NOT NULL) ENGINE=MERGE UNION=(t1,t2)
+INSERT_METHOD=LAST SELECT * FROM t3;
+ERROR HY000: 'test.t4' is not BASE TABLE
+SELECT * FROM t4;
+ERROR HY000: Table 't4' was not locked with LOCK TABLES
+UNLOCK TABLES;
+CREATE TEMPORARY TABLE t4 (c1 INT NOT NULL) ENGINE=MERGE UNION=(t1,t2)
+INSERT_METHOD=LAST;
+INSERT INTO t4 SELECT * FROM t3;
+# Alter temporary MERGE table.
+ALTER TABLE t4 UNION=(t1);
+LOCK TABLES t4 WRITE;
+# Alter temporary MERGE table under LOCk tables.
+ALTER TABLE t4 UNION=(t1,t2);
+UNLOCK TABLES;
+# MERGE table and function.
+CREATE FUNCTION f1 () RETURNS INT RETURN (SELECT max(c1) FROM t3);
+SELECT * FROM t4 WHERE c1 < f1();
+c1
+3
+DROP FUNCTION f1;
+DROP TABLE t4, t3, t2, t1;
CREATE TABLE t1 (
fileset_id tinyint(3) unsigned NOT NULL default '0',
file_code varchar(32) NOT NULL default '',
@@ -650,11 +696,11 @@ create table t2 (a int);
insert into t1 values (0);
insert into t2 values (1);
create table t3 engine=merge union=(t1, t2) select * from t1;
-ERROR HY000: You can't specify target table 't1' for update in FROM clause
+ERROR HY000: 'test.t3' is not BASE TABLE
create table t3 engine=merge union=(t1, t2) select * from t2;
-ERROR HY000: You can't specify target table 't2' for update in FROM clause
+ERROR HY000: 'test.t3' is not BASE TABLE
create table t3 engine=merge union=(t1, t2) select (select max(a) from t2);
-ERROR HY000: You can't specify target table 't2' for update in FROM clause
+ERROR HY000: 'test.t3' is not BASE TABLE
drop table t1, t2;
create table t1 (
a double(14,4),
@@ -784,7 +830,7 @@ ERROR HY000: Unable to open underlying table which is differently defined or of
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,20 +895,17 @@ 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 Table 'test.t1' doesn't exist
test.tm1 check error Corrupt
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 Table 'test.t2' doesn't exist
test.tm1 check error Corrupt
CREATE TABLE t2(a BLOB);
SELECT * FROM tm1;
@@ -885,3 +928,1026 @@ CREATE TABLE IF NOT EXISTS t1 SELECT * FROM t2;
ERROR HY000: You can't specify target table 't1' for update in FROM clause
DROP TABLE t1, t2;
End of 5.0 tests
+create table t1 (c1 int, index(c1));
+create table t2 (c1 int, index(c1)) engine=merge union=(t1);
+insert into t1 values (1);
+flush tables;
+select * from t2;
+c1
+1
+flush tables;
+truncate table t1;
+insert into t1 values (1);
+flush tables;
+select * from t2;
+c1
+1
+truncate table t1;
+insert into t1 values (1);
+drop table t1,t2;
+#
+# Extra tests for TRUNCATE.
+#
+# Truncate MERGE table.
+CREATE TABLE t1 (c1 INT, INDEX(c1));
+CREATE TABLE t2 (c1 INT, INDEX(c1));
+CREATE TABLE t3 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t1,t2);
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+SELECT * FROM t3;
+c1
+1
+2
+TRUNCATE TABLE t3;
+SELECT * FROM t3;
+c1
+#
+# Truncate child table.
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+TRUNCATE TABLE t1;
+SELECT * FROM t3;
+c1
+2
+#
+# Truncate MERGE table under locked tables.
+LOCK TABLE t1 WRITE, t2 WRITE, t3 WRITE;
+INSERT INTO t1 VALUES (1);
+TRUNCATE TABLE t3;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+SELECT * FROM t3;
+c1
+1
+2
+#
+# Truncate child table under locked tables.
+TRUNCATE TABLE t1;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+SELECT * FROM t3;
+c1
+1
+2
+UNLOCK TABLES;
+DROP TABLE t1, t2, t3;
+#
+# Truncate temporary MERGE table.
+CREATE TEMPORARY TABLE t1 (c1 INT, INDEX(c1));
+CREATE TEMPORARY TABLE t2 (c1 INT, INDEX(c1));
+CREATE TEMPORARY TABLE t3 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t1,t2);
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+SELECT * FROM t3;
+c1
+1
+2
+TRUNCATE TABLE t3;
+SELECT * FROM t3;
+c1
+#
+# Truncate temporary child table.
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+TRUNCATE TABLE t1;
+SELECT * FROM t3;
+c1
+2
+#
+# Truncate temporary MERGE table under locked tables.
+INSERT INTO t1 VALUES (1);
+CREATE TABLE t4 (c1 INT, INDEX(c1));
+LOCK TABLE t4 WRITE;
+TRUNCATE TABLE t3;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+SELECT * FROM t3;
+c1
+1
+2
+#
+# Truncate temporary child table under locked tables.
+TRUNCATE TABLE t1;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+SELECT * FROM t3;
+c1
+1
+2
+UNLOCK TABLES;
+DROP TABLE t1, t2, t3, t4;
+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;
+#
+# Extra tests for Bug#26379 - Combination of FLUSH TABLE and
+# REPAIR TABLE corrupts a MERGE table
+#
+# CREATE ... SELECT is disabled for MERGE tables.
+#
+CREATE TABLE t1(c1 INT);
+INSERT INTO t1 VALUES (1);
+CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+CREATE TABLE t3 ENGINE=MRG_MYISAM INSERT_METHOD=LAST SELECT * FROM t2;
+ERROR HY000: Table 't3' is read only
+SHOW CREATE TABLE t3;
+ERROR 42S02: Table 'test.t3' doesn't exist
+CREATE TABLE t3 ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST
+SELECT * FROM t2;
+ERROR HY000: 'test.t3' is not BASE TABLE
+SHOW CREATE TABLE t3;
+ERROR 42S02: Table 'test.t3' doesn't exist
+DROP TABLE t1, t2;
+#
+# CREATE ... LIKE
+#
+# 1. Create like.
+CREATE TABLE t1 (c1 INT);
+CREATE TABLE t2 (c1 INT);
+CREATE TABLE t3 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+INSERT_METHOD=LAST;
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+INSERT INTO t3 VALUES (3);
+CREATE TABLE t4 LIKE t3;
+SHOW CREATE TABLE t4;
+Table Create Table
+t4 CREATE TABLE `t4` (
+ `c1` int(11) DEFAULT NULL
+) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 UNION=()
+INSERT INTO t4 VALUES (4);
+ERROR HY000: Table 't4' is read only
+DROP TABLE t4;
+#
+# 1. Create like with locked tables.
+LOCK TABLES t3 WRITE, t2 WRITE, t1 WRITE;
+CREATE TABLE t4 LIKE t3;
+SHOW CREATE TABLE t4;
+ERROR HY000: Table 't4' was not locked with LOCK TABLES
+INSERT INTO t4 VALUES (4);
+ERROR HY000: Table 't4' was not locked with LOCK TABLES
+UNLOCK TABLES;
+SHOW CREATE TABLE t4;
+Table Create Table
+t4 CREATE TABLE `t4` (
+ `c1` int(11) DEFAULT NULL
+) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 UNION=()
+INSERT INTO t4 VALUES (4);
+ERROR HY000: Table 't4' is read only
+DROP TABLE t4;
+#
+# Rename child.
+#
+# 1. Normal rename of non-MERGE table.
+CREATE TABLE t4 (c1 INT);
+INSERT INTO t4 VALUES (4);
+SELECT * FROM t4 ORDER BY c1;
+c1
+4
+RENAME TABLE t4 TO t5;
+SELECT * FROM t5 ORDER BY c1;
+c1
+4
+RENAME TABLE t5 TO t4;
+SELECT * FROM t4 ORDER BY c1;
+c1
+4
+DROP TABLE t4;
+#
+# 2. Normal rename.
+SELECT * FROM t3 ORDER BY c1;
+c1
+1
+2
+3
+RENAME TABLE t2 TO t5;
+SELECT * FROM t3 ORDER BY c1;
+ERROR 42S02: Table 'test.t2' doesn't exist
+RENAME TABLE t5 TO t2;
+SELECT * FROM t3 ORDER BY c1;
+c1
+1
+2
+3
+#
+# 3. Normal rename with locked tables.
+LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE;
+SELECT * FROM t3 ORDER BY c1;
+c1
+1
+2
+3
+RENAME TABLE t2 TO t5;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+SELECT * FROM t3 ORDER BY c1;
+c1
+1
+2
+3
+RENAME TABLE t5 TO t2;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+SELECT * FROM t3 ORDER BY c1;
+c1
+1
+2
+3
+UNLOCK TABLES;
+#
+# 4. Alter table rename.
+ALTER TABLE t2 RENAME TO t5;
+SELECT * FROM t3 ORDER BY c1;
+ERROR 42S02: Table 'test.t2' doesn't exist
+ALTER TABLE t5 RENAME TO t2;
+SELECT * FROM t3 ORDER BY c1;
+c1
+1
+2
+3
+#
+# 5. Alter table rename with locked tables.
+LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE;
+ALTER TABLE t2 RENAME TO t5;
+SELECT * FROM t3 ORDER BY c1;
+ERROR HY000: Table 't3' was not locked with LOCK TABLES
+ALTER TABLE t5 RENAME TO t2;
+ERROR HY000: Table 't5' was not locked with LOCK TABLES
+UNLOCK TABLES;
+ALTER TABLE t5 RENAME TO t2;
+SELECT * FROM t3 ORDER BY c1;
+c1
+1
+2
+3
+#
+# Rename parent.
+#
+# 1. Normal rename with locked tables.
+LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE;
+SELECT * FROM t3 ORDER BY c1;
+c1
+1
+2
+3
+RENAME TABLE t3 TO t5;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+SELECT * FROM t3 ORDER BY c1;
+c1
+1
+2
+3
+RENAME TABLE t5 TO t3;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+SELECT * FROM t3 ORDER BY c1;
+c1
+1
+2
+3
+#
+# 5. Alter table rename with locked tables.
+ALTER TABLE t3 RENAME TO t5;
+SELECT * FROM t5 ORDER BY c1;
+ERROR HY000: Table 't5' was not locked with LOCK TABLES
+ALTER TABLE t5 RENAME TO t3;
+ERROR HY000: Table 't5' was not locked with LOCK TABLES
+UNLOCK TABLES;
+ALTER TABLE t5 RENAME TO t3;
+SELECT * FROM t3 ORDER BY c1;
+c1
+1
+2
+3
+DROP TABLE t1, t2, t3;
+#
+# Drop locked tables.
+#
+# 1. Drop parent.
+CREATE TABLE t1 (c1 INT, INDEX(c1));
+CREATE TABLE t2 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t1)
+INSERT_METHOD=LAST;
+LOCK TABLES t1 WRITE, t2 WRITE;
+INSERT INTO t1 VALUES (1);
+DROP TABLE t2;
+SELECT * FROM t2;
+ERROR HY000: Table 't2' was not locked with LOCK TABLES
+SELECT * FROM t1;
+c1
+1
+UNLOCK TABLES;
+# 2. Drop child.
+CREATE TABLE t2 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t1)
+INSERT_METHOD=LAST;
+LOCK TABLES t1 WRITE, t2 WRITE;
+INSERT INTO t1 VALUES (1);
+DROP TABLE t1;
+SELECT * FROM t2;
+ERROR 42S02: Table 'test.t1' doesn't exist
+SELECT * FROM t1;
+ERROR 42S02: Table 'test.t1' doesn't exist
+UNLOCK TABLES;
+DROP TABLE t2;
+#
+# ALTER TABLE. Change child list.
+#
+CREATE TABLE t1 (c1 INT, INDEX(c1));
+CREATE TABLE t2 (c1 INT, INDEX(c1));
+CREATE TABLE t3 (c1 INT, INDEX(c1));
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+INSERT INTO t3 VALUES (3);
+CREATE TABLE t4 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t3,t2)
+INSERT_METHOD=LAST;
+# Shrink child list.
+ALTER TABLE t4 UNION=(t3);
+SHOW CREATE TABLE t4;
+Table Create Table
+t4 CREATE TABLE `t4` (
+ `c1` int(11) DEFAULT NULL,
+ KEY `c1` (`c1`)
+) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 INSERT_METHOD=LAST UNION=(`t3`)
+SELECT * FROM t4 ORDER BY c1;
+c1
+3
+# Extend child list.
+ALTER TABLE t4 UNION=(t3,t2);
+SHOW CREATE TABLE t4;
+Table Create Table
+t4 CREATE TABLE `t4` (
+ `c1` int(11) DEFAULT NULL,
+ KEY `c1` (`c1`)
+) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 INSERT_METHOD=LAST UNION=(`t3`,`t2`)
+SELECT * FROM t4 ORDER BY c1;
+c1
+2
+3
+#
+# ALTER TABLE under LOCK TABLES. Change child list.
+#
+LOCK TABLES t4 WRITE, t3 WRITE, t2 WRITE;
+# Shrink child list.
+ALTER TABLE t4 UNION=(t3);
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+# Extend child list within locked tables.
+ALTER TABLE t4 UNION=(t3,t2);
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+# Extend child list beyond locked tables.
+ALTER TABLE t4 UNION=(t3,t2,t1);
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+SHOW CREATE TABLE t4;
+Table Create Table
+t4 CREATE TABLE `t4` (
+ `c1` int(11) DEFAULT NULL,
+ KEY `c1` (`c1`)
+) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 INSERT_METHOD=LAST UNION=(`t3`,`t2`)
+SELECT * FROM t4 ORDER BY c1;
+c1
+2
+3
+UNLOCK TABLES;
+DROP TABLE t4;
+#
+# ALTER TABLE under LOCK TABLES. Grave change, table re-creation.
+#
+CREATE TABLE t4 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t1,t2,t3)
+INSERT_METHOD=LAST;
+# Lock parent first and then children.
+LOCK TABLES t4 WRITE, t3 WRITE, t2 WRITE, t1 WRITE;
+ALTER TABLE t4 DROP INDEX c1, ADD UNIQUE INDEX (c1);
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+ALTER TABLE t2 DROP INDEX c1, ADD UNIQUE INDEX (c1);
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+UNLOCK TABLES;
+# Lock children first and then parent.
+LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE;
+ALTER TABLE t4 DROP INDEX c1, ADD UNIQUE INDEX (c1);
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+ALTER TABLE t2 DROP INDEX c1, ADD UNIQUE INDEX (c1);
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+UNLOCK TABLES;
+# Lock parent between children.
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+ALTER TABLE t4 DROP INDEX c1, ADD UNIQUE INDEX (c1);
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+ALTER TABLE t2 DROP INDEX c1, ADD UNIQUE INDEX (c1);
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+UNLOCK TABLES;
+DROP TABLE t1, t2, t3, t4;
+#
+# ALTER TABLE under LOCK TABLES. Simple change, no re-creation.
+#
+CREATE TABLE t1 (c1 INT);
+CREATE TABLE t2 (c1 INT);
+CREATE TABLE t3 (c1 INT);
+CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2,t3)
+INSERT_METHOD=LAST;
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+INSERT INTO t3 VALUES (3);
+# Lock parent first and then children.
+LOCK TABLES t4 WRITE, t3 WRITE, t2 WRITE, t1 WRITE;
+ALTER TABLE t4 ALTER COLUMN c1 SET DEFAULT 44;
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+ALTER TABLE t2 ALTER COLUMN c1 SET DEFAULT 22;
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+UNLOCK TABLES;
+# Lock children first and then parent.
+LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE;
+ALTER TABLE t4 ALTER COLUMN c1 SET DEFAULT 44;
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+ALTER TABLE t2 ALTER COLUMN c1 SET DEFAULT 22;
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+UNLOCK TABLES;
+# Lock parent between children.
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+ALTER TABLE t4 ALTER COLUMN c1 SET DEFAULT 44;
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+ALTER TABLE t2 ALTER COLUMN c1 SET DEFAULT 22;
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+UNLOCK TABLES;
+#
+# FLUSH TABLE under LOCK TABLES.
+#
+# Lock parent first and then children.
+LOCK TABLES t4 WRITE, t3 WRITE, t2 WRITE, t1 WRITE;
+FLUSH TABLE t4;
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+FLUSH TABLE t2;
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+FLUSH TABLES;
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+UNLOCK TABLES;
+# Lock children first and then parent.
+LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE;
+FLUSH TABLE t4;
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+FLUSH TABLE t2;
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+FLUSH TABLES;
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+UNLOCK TABLES;
+# Lock parent between children.
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+FLUSH TABLE t4;
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+FLUSH TABLE t2;
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+FLUSH TABLES;
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+UNLOCK TABLES;
+#
+# Triggers
+#
+# Trigger on parent
+DELETE FROM t4 WHERE c1 = 4;
+CREATE TRIGGER t4_ai AFTER INSERT ON t4 FOR EACH ROW SET @a=1;
+SET @a=0;
+INSERT INTO t4 VALUES (4);
+SELECT @a;
+@a
+1
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+4
+DROP TRIGGER t4_ai;
+# Trigger on parent under LOCK TABLES
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+CREATE TRIGGER t4_ai AFTER INSERT ON t4 FOR EACH ROW SET @a=1;
+SET @a=0;
+INSERT INTO t4 VALUES (4);
+SELECT @a;
+@a
+1
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+4
+4
+DROP TRIGGER t4_ai;
+UNLOCK TABLES;
+#
+# Trigger on child
+DELETE FROM t4 WHERE c1 = 4;
+CREATE TRIGGER t3_ai AFTER INSERT ON t3 FOR EACH ROW SET @a=1;
+SET @a=0;
+INSERT INTO t4 VALUES (4);
+SELECT @a;
+@a
+0
+INSERT INTO t3 VALUES (33);
+SELECT @a;
+@a
+1
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+4
+33
+DROP TRIGGER t3_ai;
+# Trigger on child under LOCK TABLES
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+CREATE TRIGGER t3_ai AFTER INSERT ON t3 FOR EACH ROW SET @a=1;
+SET @a=0;
+INSERT INTO t4 VALUES (4);
+SELECT @a;
+@a
+0
+INSERT INTO t3 VALUES (33);
+SELECT @a;
+@a
+1
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+4
+4
+33
+33
+DELETE FROM t4 WHERE c1 = 33;
+DROP TRIGGER t3_ai;
+#
+# Trigger with table use on child
+DELETE FROM t4 WHERE c1 = 4;
+CREATE TRIGGER t3_ai AFTER INSERT ON t3 FOR EACH ROW INSERT INTO t2 VALUES(22);
+INSERT INTO t4 VALUES (4);
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+4
+INSERT INTO t3 VALUES (33);
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+4
+22
+33
+DELETE FROM t4 WHERE c1 = 22;
+DELETE FROM t4 WHERE c1 = 33;
+DROP TRIGGER t3_ai;
+# Trigger with table use on child under LOCK TABLES
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+CREATE TRIGGER t3_ai AFTER INSERT ON t3 FOR EACH ROW INSERT INTO t2 VALUES(22);
+INSERT INTO t4 VALUES (4);
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+4
+4
+INSERT INTO t3 VALUES (33);
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+4
+4
+22
+33
+DROP TRIGGER t3_ai;
+DELETE FROM t4 WHERE c1 = 22;
+DELETE FROM t4 WHERE c1 = 33;
+UNLOCK TABLES;
+#
+# Repair
+#
+REPAIR TABLE t4;
+Table Op Msg_type Msg_text
+test.t4 repair note The storage engine for the table doesn't support repair
+REPAIR TABLE t2;
+Table Op Msg_type Msg_text
+test.t2 repair status OK
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+4
+4
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+REPAIR TABLE t4;
+Table Op Msg_type Msg_text
+test.t4 repair note The storage engine for the table doesn't support repair
+REPAIR TABLE t2;
+Table Op Msg_type Msg_text
+test.t2 repair status OK
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+4
+4
+UNLOCK TABLES;
+#
+# Optimize
+#
+OPTIMIZE TABLE t4;
+Table Op Msg_type Msg_text
+test.t4 optimize note The storage engine for the table doesn't support optimize
+OPTIMIZE TABLE t2;
+Table Op Msg_type Msg_text
+test.t2 optimize status OK
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+4
+4
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+OPTIMIZE TABLE t4;
+Table Op Msg_type Msg_text
+test.t4 optimize note The storage engine for the table doesn't support optimize
+OPTIMIZE TABLE t2;
+Table Op Msg_type Msg_text
+test.t2 optimize status Table is already up to date
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+4
+4
+UNLOCK TABLES;
+#
+# Checksum
+#
+CHECKSUM TABLE t4;
+Table Checksum
+test.t4 46622073
+CHECKSUM TABLE t2;
+Table Checksum
+test.t2 3700403066
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+4
+4
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+CHECKSUM TABLE t4;
+Table Checksum
+test.t4 46622073
+CHECKSUM TABLE t2;
+Table Checksum
+test.t2 3700403066
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+4
+4
+UNLOCK TABLES;
+#
+# Insert delayed
+#
+INSERT DELAYED INTO t4 VALUES(44);
+DELETE FROM t4 WHERE c1 = 44;
+INSERT DELAYED INTO t3 VALUES(33);
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+4
+4
+33
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+INSERT DELAYED INTO t4 VALUES(444);
+Got one of the listed errors
+INSERT DELAYED INTO t3 VALUES(333);
+Got one of the listed errors
+SELECT * FROM t4 ORDER BY c1;
+c1
+1
+2
+3
+4
+4
+33
+UNLOCK TABLES;
+DROP TABLE t1, t2, t3, t4;
+#
+# Recursive inclusion of merge tables in their union clauses.
+#
+CREATE TABLE t1 (c1 INT, INDEX(c1));
+CREATE TABLE t2 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t1)
+INSERT_METHOD=LAST;
+CREATE TABLE t3 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t2,t1)
+INSERT_METHOD=LAST;
+ALTER TABLE t2 UNION=(t3,t1);
+SELECT * FROM t2;
+ERROR HY000: Table 't3' is differently defined or of non-MyISAM type or doesn't exist
+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, 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 --git a/mysql-test/r/myisam.result b/mysql-test/r/myisam.result
index 50fbe89db52..353d66b1ad5 100644
--- a/mysql-test/r/myisam.result
+++ b/mysql-test/r/myisam.result
@@ -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 --git a/mysql-test/suite/binlog/r/binlog_stm_blackhole.result b/mysql-test/suite/binlog/r/binlog_stm_blackhole.result
index a1c83ffc73d..a79642a9204 100644
--- a/mysql-test/suite/binlog/r/binlog_stm_blackhole.result
+++ b/mysql-test/suite/binlog/r/binlog_stm_blackhole.result
@@ -124,6 +124,7 @@ master-bin.000001 # Query # # use `test`; replace into t1 select * from t3
drop table t1,t2,t3;
CREATE TABLE t1(a INT) ENGINE=BLACKHOLE;
INSERT DELAYED INTO t1 VALUES(1);
+ERROR HY000: Binary logging not possible. Message: Row-based format required for this statement, but not allowed by this combination of engines
DROP TABLE t1;
CREATE TABLE t1(a INT, b INT) ENGINE=BLACKHOLE;
DELETE FROM t1 WHERE a=10;
diff --git a/mysql-test/t/create.test b/mysql-test/t/create.test
index 023e55ea418..45ee4c1c88d 100644
--- a/mysql-test/t/create.test
+++ b/mysql-test/t/create.test
@@ -483,7 +483,7 @@ drop table t1,t2;
create table t1 (a int);
--error 1093
create table t1 select * from t1;
---error 1093
+--error ER_WRONG_OBJECT
create table t2 union = (t1) select * from t1;
flush tables with read lock;
unlock tables;
diff --git a/mysql-test/t/delayed.test b/mysql-test/t/delayed.test
index 03b4f8b3013..a0e25509ba2 100644
--- a/mysql-test/t/delayed.test
+++ b/mysql-test/t/delayed.test
@@ -244,14 +244,6 @@ SELECT HEX(a) FROM t1;
DROP TABLE t1;
#
-# Bug#26464 - insert delayed + update + merge = corruption
-#
-CREATE TABLE t1(c1 INT) ENGINE=MyISAM;
-CREATE TABLE t2(c1 INT) ENGINE=MERGE UNION=(t1);
---error 1031
-INSERT DELAYED INTO t2 VALUES(1);
-DROP TABLE t1, t2;
-#
# Bug#27358 INSERT DELAYED does not honour SQL_MODE of the client
#
--disable_warnings
diff --git a/mysql-test/t/merge-big.test b/mysql-test/t/merge-big.test
new file mode 100644
index 00000000000..eddcbb59ed4
--- /dev/null
+++ b/mysql-test/t/merge-big.test
@@ -0,0 +1,150 @@
+#
+# Test of MERGE tables with multisession and many waits.
+#
+# This test takes rather long time so let us run it only in --big-test mode
+--source include/big_test.inc
+# We are using some debug-only features in this test
+--source include/have_debug.inc
+
+--disable_warnings
+drop table if exists t1,t2,t3,t4,t5,t6;
+--enable_warnings
+
+--echo #
+--echo # Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
+--echo # corrupts a MERGE table
+--echo # Problem #3
+--echo #
+# 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()
+# (open_tables()) and before retrying to lock, the thread got the lock.
+# And it did not notice that the table needed a refresh after the
+# [re-]open. 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()). The second FLUSH
+# TABLES must happen while the INSERT was on its way from
+# wait_for_tables() (open_table()) 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;
+#SELECT NOW();
+ --echo # connection con1
+ connect (con1,localhost,root,,);
+ let $con1_id= `SELECT CONNECTION_ID()`;
+ SET SESSION debug="+d,sleep_open_and_lock_after_open";
+ send INSERT INTO t1 VALUES (1);
+--echo # connection default
+connection default;
+--echo # Let INSERT go into thr_multi_lock().
+let $wait_condition= SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST
+ WHERE ID = $con1_id AND STATE = 'Locked';
+--source include/wait_condition.inc
+#SELECT NOW();
+--echo # Kick INSERT out of thr_multi_lock().
+FLUSH TABLES;
+#SELECT NOW();
+--echo # Let INSERT go through open_tables() where it sleeps.
+let $wait_condition= SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST
+ WHERE ID = $con1_id AND STATE = 'DBUG sleep';
+--source include/wait_condition.inc
+#SELECT NOW();
+--echo # Unlock and close table and wait for con1 to close too.
+FLUSH TABLES;
+#SELECT NOW();
+--echo # This should give no result.
+SELECT * FROM t1;
+#SELECT NOW();
+UNLOCK TABLES;
+ --echo # connection con1
+ connection con1;
+ reap;
+ SET SESSION debug="-d,sleep_open_and_lock_after_open";
+ disconnect con1;
+--echo # connection default
+connection default;
+DROP TABLE t1;
+
+--echo #
+--echo # Extra tests for Bug#26379 - Combination of FLUSH TABLE and
+--echo # REPAIR TABLE corrupts a MERGE table
+--echo #
+CREATE TABLE t1 (c1 INT);
+CREATE TABLE t2 (c1 INT);
+CREATE TABLE t3 (c1 INT);
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+INSERT INTO t3 VALUES (3);
+--echo #
+--echo # CREATE ... SELECT
+--echo # try to access parent from another thread.
+--echo #
+#SELECT NOW();
+ --echo # connection con1
+ connect (con1,localhost,root,,);
+ let $con1_id= `SELECT CONNECTION_ID()`;
+ SET SESSION debug="+d,sleep_create_select_before_lock";
+ send CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+ INSERT_METHOD=FIRST SELECT * FROM t3;
+--echo # connection default
+connection default;
+# wait for the other query to start executing
+let $wait_condition= SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST
+ WHERE ID = $con1_id AND STATE = 'DBUG sleep';
+--source include/wait_condition.inc
+#SELECT NOW();
+--echo # Now try to access the parent.
+--echo # If 3 is in table, SELECT had to wait.
+SELECT * FROM t4 ORDER BY c1;
+#SELECT NOW();
+ --echo # connection con1
+ connection con1;
+ reap;
+ #SELECT NOW();
+ SET SESSION debug="-d,sleep_create_select_before_lock";
+ disconnect con1;
+--echo # connection default
+connection default;
+--echo # Cleanup for next test.
+DROP TABLE t4;
+DELETE FROM t1 WHERE c1 != 1;
+--echo #
+--echo # CREATE ... SELECT
+--echo # try to access child from another thread.
+--echo #
+#SELECT NOW();
+ --echo # connection con1
+ connect (con1,localhost,root,,);
+ let $con1_id= `SELECT CONNECTION_ID()`;
+ SET SESSION debug="+d,sleep_create_select_before_lock";
+ send CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+ INSERT_METHOD=FIRST SELECT * FROM t3;
+--echo # connection default
+connection default;
+# wait for the other query to start executing
+let $wait_condition= SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST
+ WHERE ID = $con1_id AND STATE = 'DBUG sleep';
+--source include/wait_condition.inc
+#SELECT NOW();
+--echo # Now try to access a child.
+--echo # If 3 is in table, SELECT had to wait.
+SELECT * FROM t1 ORDER BY c1;
+#SELECT NOW();
+ --echo # connection con1
+ connection con1;
+ reap;
+ #SELECT NOW();
+ SET SESSION debug="-d,sleep_create_select_before_lock";
+ disconnect con1;
+--echo # connection default
+connection default;
+DROP TABLE t1, t2, t3, t4;
diff --git a/mysql-test/t/merge.test b/mysql-test/t/merge.test
index a50588b1e78..cd4e732b954 100644
--- a/mysql-test/t/merge.test
+++ b/mysql-test/t/merge.test
@@ -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,58 @@ 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;
+--echo # CREATE...SELECT is not implemented for MERGE tables.
+CREATE TEMPORARY TABLE t1 (c1 INT NOT NULL);
+CREATE TEMPORARY TABLE t2 (c1 INT NOT NULL);
+CREATE TABLE t3 (c1 INT NOT NULL);
+INSERT INTO t3 VALUES (3), (33);
+LOCK TABLES t3 READ;
+--error ER_WRONG_OBJECT
+CREATE TEMPORARY TABLE t4 (c1 INT NOT NULL) ENGINE=MERGE UNION=(t1,t2)
+ INSERT_METHOD=LAST SELECT * FROM t3;
+--error ER_TABLE_NOT_LOCKED
+SELECT * FROM t4;
+UNLOCK TABLES;
+CREATE TEMPORARY TABLE t4 (c1 INT NOT NULL) ENGINE=MERGE UNION=(t1,t2)
+ INSERT_METHOD=LAST;
+INSERT INTO t4 SELECT * FROM t3;
+--echo # Alter temporary MERGE table.
+ALTER TABLE t4 UNION=(t1);
+LOCK TABLES t4 WRITE;
+--echo # Alter temporary MERGE table under LOCk tables.
+ALTER TABLE t4 UNION=(t1,t2);
+UNLOCK TABLES;
+--echo # MERGE table and function.
+CREATE FUNCTION f1 () RETURNS INT RETURN (SELECT max(c1) FROM t3);
+SELECT * FROM t4 WHERE c1 < f1();
+DROP FUNCTION f1;
+DROP TABLE t4, t3, t2, t1;
#
# testing merge::records_in_range and optimizer
@@ -284,11 +337,11 @@ create table t1 (a int);
create table t2 (a int);
insert into t1 values (0);
insert into t2 values (1);
---error 1093
+--error ER_WRONG_OBJECT
create table t3 engine=merge union=(t1, t2) select * from t1;
---error 1093
+--error ER_WRONG_OBJECT
create table t3 engine=merge union=(t1, t2) select * from t2;
---error 1093
+--error ER_WRONG_OBJECT
create table t3 engine=merge union=(t1, t2) select (select max(a) from t2);
drop table t1, t2;
@@ -403,7 +456,7 @@ CREATE TABLE t2(a INT) ENGINE=MERGE UNION=(t1);
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 +548,11 @@ drop table t1;
# CREATE TABLE fails
#
CREATE TABLE tm1(a INT) ENGINE=MERGE UNION=(t1, t2);
---error 1168
+--error ER_NO_SUCH_TABLE
SELECT * FROM tm1;
CHECK TABLE tm1;
CREATE TABLE t1(a INT);
---error 1168
+--error ER_NO_SUCH_TABLE
SELECT * FROM tm1;
CHECK TABLE tm1;
CREATE TABLE t2(a BLOB);
@@ -526,3 +579,785 @@ CREATE TABLE IF NOT EXISTS t1 SELECT * FROM t2;
DROP TABLE t1, t2;
--echo End of 5.0 tests
+
+#
+# Bug #8306: TRUNCATE leads to index corruption
+#
+create table t1 (c1 int, index(c1));
+create table t2 (c1 int, index(c1)) engine=merge union=(t1);
+insert into t1 values (1);
+# Close all tables.
+flush tables;
+# Open t2 and (implicitly) t1.
+select * from t2;
+# Truncate after flush works (unless another threads reopens t2 in between).
+flush tables;
+truncate table t1;
+insert into t1 values (1);
+# Close all tables.
+flush tables;
+# Open t2 and (implicitly) t1.
+select * from t2;
+# Truncate t1, wich was not recognized as open without the bugfix.
+# After fix for Bug#8306 and before fix for Bug#26379,
+# it should fail with a table-in-use error message, otherwise succeed.
+truncate table t1;
+# The insert used to fail on the crashed table.
+insert into t1 values (1);
+drop table t1,t2;
+--echo #
+--echo # Extra tests for TRUNCATE.
+--echo #
+--echo # Truncate MERGE table.
+CREATE TABLE t1 (c1 INT, INDEX(c1));
+CREATE TABLE t2 (c1 INT, INDEX(c1));
+CREATE TABLE t3 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t1,t2);
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+SELECT * FROM t3;
+TRUNCATE TABLE t3;
+SELECT * FROM t3;
+--echo #
+--echo # Truncate child table.
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+TRUNCATE TABLE t1;
+SELECT * FROM t3;
+--echo #
+--echo # Truncate MERGE table under locked tables.
+LOCK TABLE t1 WRITE, t2 WRITE, t3 WRITE;
+INSERT INTO t1 VALUES (1);
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+TRUNCATE TABLE t3;
+SELECT * FROM t3;
+--echo #
+--echo # Truncate child table under locked tables.
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+TRUNCATE TABLE t1;
+SELECT * FROM t3;
+UNLOCK TABLES;
+DROP TABLE t1, t2, t3;
+--echo #
+--echo # Truncate temporary MERGE table.
+CREATE TEMPORARY TABLE t1 (c1 INT, INDEX(c1));
+CREATE TEMPORARY TABLE t2 (c1 INT, INDEX(c1));
+CREATE TEMPORARY TABLE t3 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t1,t2);
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+SELECT * FROM t3;
+TRUNCATE TABLE t3;
+SELECT * FROM t3;
+--echo #
+--echo # Truncate temporary child table.
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+TRUNCATE TABLE t1;
+SELECT * FROM t3;
+--echo #
+--echo # Truncate temporary MERGE table under locked tables.
+INSERT INTO t1 VALUES (1);
+CREATE TABLE t4 (c1 INT, INDEX(c1));
+LOCK TABLE t4 WRITE;
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+TRUNCATE TABLE t3;
+SELECT * FROM t3;
+--echo #
+--echo # Truncate temporary child table under locked tables.
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+TRUNCATE TABLE t1;
+SELECT * FROM t3;
+UNLOCK TABLES;
+DROP TABLE t1, t2, t3, t4;
+
+#
+# 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.
+#
+# This is the same test case as for
+# 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;
+ 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;
+#
+--echo #
+--echo # Extra tests for Bug#26379 - Combination of FLUSH TABLE and
+--echo # REPAIR TABLE corrupts a MERGE table
+#
+--echo #
+--echo # CREATE ... SELECT is disabled for MERGE tables.
+--echo #
+CREATE TABLE t1(c1 INT);
+INSERT INTO t1 VALUES (1);
+CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+--error ER_OPEN_AS_READONLY
+CREATE TABLE t3 ENGINE=MRG_MYISAM INSERT_METHOD=LAST SELECT * FROM t2;
+--error ER_NO_SUCH_TABLE
+SHOW CREATE TABLE t3;
+--error ER_WRONG_OBJECT
+CREATE TABLE t3 ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST
+ SELECT * FROM t2;
+--error ER_NO_SUCH_TABLE
+SHOW CREATE TABLE t3;
+DROP TABLE t1, t2;
+#
+--echo #
+--echo # CREATE ... LIKE
+--echo #
+--echo # 1. Create like.
+CREATE TABLE t1 (c1 INT);
+CREATE TABLE t2 (c1 INT);
+CREATE TABLE t3 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+ INSERT_METHOD=LAST;
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+INSERT INTO t3 VALUES (3);
+CREATE TABLE t4 LIKE t3;
+SHOW CREATE TABLE t4;
+--error ER_OPEN_AS_READONLY
+INSERT INTO t4 VALUES (4);
+DROP TABLE t4;
+--echo #
+--echo # 1. Create like with locked tables.
+LOCK TABLES t3 WRITE, t2 WRITE, t1 WRITE;
+CREATE TABLE t4 LIKE t3;
+--error ER_TABLE_NOT_LOCKED
+SHOW CREATE TABLE t4;
+--error ER_TABLE_NOT_LOCKED
+INSERT INTO t4 VALUES (4);
+UNLOCK TABLES;
+SHOW CREATE TABLE t4;
+--error ER_OPEN_AS_READONLY
+INSERT INTO t4 VALUES (4);
+DROP TABLE t4;
+#
+--echo #
+--echo # Rename child.
+--echo #
+--echo # 1. Normal rename of non-MERGE table.
+CREATE TABLE t4 (c1 INT);
+INSERT INTO t4 VALUES (4);
+SELECT * FROM t4 ORDER BY c1;
+RENAME TABLE t4 TO t5;
+SELECT * FROM t5 ORDER BY c1;
+RENAME TABLE t5 TO t4;
+SELECT * FROM t4 ORDER BY c1;
+DROP TABLE t4;
+--echo #
+--echo # 2. Normal rename.
+SELECT * FROM t3 ORDER BY c1;
+RENAME TABLE t2 TO t5;
+--error ER_NO_SUCH_TABLE
+SELECT * FROM t3 ORDER BY c1;
+RENAME TABLE t5 TO t2;
+SELECT * FROM t3 ORDER BY c1;
+--echo #
+--echo # 3. Normal rename with locked tables.
+LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE;
+SELECT * FROM t3 ORDER BY c1;
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+RENAME TABLE t2 TO t5;
+SELECT * FROM t3 ORDER BY c1;
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+RENAME TABLE t5 TO t2;
+SELECT * FROM t3 ORDER BY c1;
+UNLOCK TABLES;
+--echo #
+--echo # 4. Alter table rename.
+ALTER TABLE t2 RENAME TO t5;
+--error ER_NO_SUCH_TABLE
+SELECT * FROM t3 ORDER BY c1;
+ALTER TABLE t5 RENAME TO t2;
+SELECT * FROM t3 ORDER BY c1;
+--echo #
+--echo # 5. Alter table rename with locked tables.
+LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE;
+ALTER TABLE t2 RENAME TO t5;
+--error ER_TABLE_NOT_LOCKED
+SELECT * FROM t3 ORDER BY c1;
+--error ER_TABLE_NOT_LOCKED
+ALTER TABLE t5 RENAME TO t2;
+UNLOCK TABLES;
+ALTER TABLE t5 RENAME TO t2;
+SELECT * FROM t3 ORDER BY c1;
+#
+--echo #
+--echo # Rename parent.
+--echo #
+--echo # 1. Normal rename with locked tables.
+LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE;
+SELECT * FROM t3 ORDER BY c1;
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+RENAME TABLE t3 TO t5;
+SELECT * FROM t3 ORDER BY c1;
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+RENAME TABLE t5 TO t3;
+SELECT * FROM t3 ORDER BY c1;
+--echo #
+--echo # 5. Alter table rename with locked tables.
+ALTER TABLE t3 RENAME TO t5;
+--error ER_TABLE_NOT_LOCKED
+SELECT * FROM t5 ORDER BY c1;
+--error ER_TABLE_NOT_LOCKED
+ALTER TABLE t5 RENAME TO t3;
+UNLOCK TABLES;
+ALTER TABLE t5 RENAME TO t3;
+SELECT * FROM t3 ORDER BY c1;
+DROP TABLE t1, t2, t3;
+#
+--echo #
+--echo # Drop locked tables.
+--echo #
+--echo # 1. Drop parent.
+CREATE TABLE t1 (c1 INT, INDEX(c1));
+CREATE TABLE t2 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t1)
+ INSERT_METHOD=LAST;
+LOCK TABLES t1 WRITE, t2 WRITE;
+INSERT INTO t1 VALUES (1);
+DROP TABLE t2;
+--error ER_TABLE_NOT_LOCKED
+SELECT * FROM t2;
+SELECT * FROM t1;
+UNLOCK TABLES;
+--echo # 2. Drop child.
+CREATE TABLE t2 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t1)
+ INSERT_METHOD=LAST;
+LOCK TABLES t1 WRITE, t2 WRITE;
+INSERT INTO t1 VALUES (1);
+DROP TABLE t1;
+--error ER_NO_SUCH_TABLE
+SELECT * FROM t2;
+--error ER_NO_SUCH_TABLE
+SELECT * FROM t1;
+UNLOCK TABLES;
+DROP TABLE t2;
+#
+--echo #
+--echo # ALTER TABLE. Change child list.
+--echo #
+CREATE TABLE t1 (c1 INT, INDEX(c1));
+CREATE TABLE t2 (c1 INT, INDEX(c1));
+CREATE TABLE t3 (c1 INT, INDEX(c1));
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+INSERT INTO t3 VALUES (3);
+CREATE TABLE t4 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t3,t2)
+ INSERT_METHOD=LAST;
+--echo # Shrink child list.
+ALTER TABLE t4 UNION=(t3);
+SHOW CREATE TABLE t4;
+SELECT * FROM t4 ORDER BY c1;
+--echo # Extend child list.
+ALTER TABLE t4 UNION=(t3,t2);
+SHOW CREATE TABLE t4;
+SELECT * FROM t4 ORDER BY c1;
+#
+--echo #
+--echo # ALTER TABLE under LOCK TABLES. Change child list.
+--echo #
+LOCK TABLES t4 WRITE, t3 WRITE, t2 WRITE;
+--echo # Shrink child list.
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+ALTER TABLE t4 UNION=(t3);
+--echo # Extend child list within locked tables.
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+ALTER TABLE t4 UNION=(t3,t2);
+--echo # Extend child list beyond locked tables.
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+ALTER TABLE t4 UNION=(t3,t2,t1);
+SHOW CREATE TABLE t4;
+SELECT * FROM t4 ORDER BY c1;
+UNLOCK TABLES;
+DROP TABLE t4;
+#
+--echo #
+--echo # ALTER TABLE under LOCK TABLES. Grave change, table re-creation.
+--echo #
+CREATE TABLE t4 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t1,t2,t3)
+ INSERT_METHOD=LAST;
+--echo # Lock parent first and then children.
+LOCK TABLES t4 WRITE, t3 WRITE, t2 WRITE, t1 WRITE;
+ALTER TABLE t4 DROP INDEX c1, ADD UNIQUE INDEX (c1);
+SELECT * FROM t4 ORDER BY c1;
+ALTER TABLE t2 DROP INDEX c1, ADD UNIQUE INDEX (c1);
+SELECT * FROM t4 ORDER BY c1;
+UNLOCK TABLES;
+--echo # Lock children first and then parent.
+LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE;
+ALTER TABLE t4 DROP INDEX c1, ADD UNIQUE INDEX (c1);
+SELECT * FROM t4 ORDER BY c1;
+ALTER TABLE t2 DROP INDEX c1, ADD UNIQUE INDEX (c1);
+SELECT * FROM t4 ORDER BY c1;
+UNLOCK TABLES;
+--echo # Lock parent between children.
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+ALTER TABLE t4 DROP INDEX c1, ADD UNIQUE INDEX (c1);
+SELECT * FROM t4 ORDER BY c1;
+ALTER TABLE t2 DROP INDEX c1, ADD UNIQUE INDEX (c1);
+SELECT * FROM t4 ORDER BY c1;
+UNLOCK TABLES;
+DROP TABLE t1, t2, t3, t4;
+#
+--echo #
+--echo # ALTER TABLE under LOCK TABLES. Simple change, no re-creation.
+--echo #
+CREATE TABLE t1 (c1 INT);
+CREATE TABLE t2 (c1 INT);
+CREATE TABLE t3 (c1 INT);
+CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2,t3)
+ INSERT_METHOD=LAST;
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+INSERT INTO t3 VALUES (3);
+--echo # Lock parent first and then children.
+LOCK TABLES t4 WRITE, t3 WRITE, t2 WRITE, t1 WRITE;
+ALTER TABLE t4 ALTER COLUMN c1 SET DEFAULT 44;
+SELECT * FROM t4 ORDER BY c1;
+ALTER TABLE t2 ALTER COLUMN c1 SET DEFAULT 22;
+SELECT * FROM t4 ORDER BY c1;
+UNLOCK TABLES;
+--echo # Lock children first and then parent.
+LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE;
+ALTER TABLE t4 ALTER COLUMN c1 SET DEFAULT 44;
+SELECT * FROM t4 ORDER BY c1;
+ALTER TABLE t2 ALTER COLUMN c1 SET DEFAULT 22;
+SELECT * FROM t4 ORDER BY c1;
+UNLOCK TABLES;
+--echo # Lock parent between children.
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+ALTER TABLE t4 ALTER COLUMN c1 SET DEFAULT 44;
+SELECT * FROM t4 ORDER BY c1;
+ALTER TABLE t2 ALTER COLUMN c1 SET DEFAULT 22;
+SELECT * FROM t4 ORDER BY c1;
+UNLOCK TABLES;
+#
+--echo #
+--echo # FLUSH TABLE under LOCK TABLES.
+--echo #
+--echo # Lock parent first and then children.
+LOCK TABLES t4 WRITE, t3 WRITE, t2 WRITE, t1 WRITE;
+FLUSH TABLE t4;
+SELECT * FROM t4 ORDER BY c1;
+FLUSH TABLE t2;
+SELECT * FROM t4 ORDER BY c1;
+FLUSH TABLES;
+SELECT * FROM t4 ORDER BY c1;
+UNLOCK TABLES;
+--echo # Lock children first and then parent.
+LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE;
+FLUSH TABLE t4;
+SELECT * FROM t4 ORDER BY c1;
+FLUSH TABLE t2;
+SELECT * FROM t4 ORDER BY c1;
+FLUSH TABLES;
+SELECT * FROM t4 ORDER BY c1;
+UNLOCK TABLES;
+--echo # Lock parent between children.
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+FLUSH TABLE t4;
+SELECT * FROM t4 ORDER BY c1;
+FLUSH TABLE t2;
+SELECT * FROM t4 ORDER BY c1;
+FLUSH TABLES;
+SELECT * FROM t4 ORDER BY c1;
+UNLOCK TABLES;
+#
+--echo #
+--echo # Triggers
+--echo #
+--echo # Trigger on parent
+DELETE FROM t4 WHERE c1 = 4;
+CREATE TRIGGER t4_ai AFTER INSERT ON t4 FOR EACH ROW SET @a=1;
+SET @a=0;
+INSERT INTO t4 VALUES (4);
+SELECT @a;
+SELECT * FROM t4 ORDER BY c1;
+DROP TRIGGER t4_ai;
+--echo # Trigger on parent under LOCK TABLES
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+CREATE TRIGGER t4_ai AFTER INSERT ON t4 FOR EACH ROW SET @a=1;
+SET @a=0;
+INSERT INTO t4 VALUES (4);
+SELECT @a;
+SELECT * FROM t4 ORDER BY c1;
+DROP TRIGGER t4_ai;
+UNLOCK TABLES;
+--echo #
+--echo # Trigger on child
+DELETE FROM t4 WHERE c1 = 4;
+CREATE TRIGGER t3_ai AFTER INSERT ON t3 FOR EACH ROW SET @a=1;
+SET @a=0;
+INSERT INTO t4 VALUES (4);
+SELECT @a;
+INSERT INTO t3 VALUES (33);
+SELECT @a;
+SELECT * FROM t4 ORDER BY c1;
+DROP TRIGGER t3_ai;
+--echo # Trigger on child under LOCK TABLES
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+CREATE TRIGGER t3_ai AFTER INSERT ON t3 FOR EACH ROW SET @a=1;
+SET @a=0;
+INSERT INTO t4 VALUES (4);
+SELECT @a;
+INSERT INTO t3 VALUES (33);
+SELECT @a;
+SELECT * FROM t4 ORDER BY c1;
+DELETE FROM t4 WHERE c1 = 33;
+DROP TRIGGER t3_ai;
+--echo #
+--echo # Trigger with table use on child
+DELETE FROM t4 WHERE c1 = 4;
+CREATE TRIGGER t3_ai AFTER INSERT ON t3 FOR EACH ROW INSERT INTO t2 VALUES(22);
+INSERT INTO t4 VALUES (4);
+SELECT * FROM t4 ORDER BY c1;
+INSERT INTO t3 VALUES (33);
+SELECT * FROM t4 ORDER BY c1;
+DELETE FROM t4 WHERE c1 = 22;
+DELETE FROM t4 WHERE c1 = 33;
+DROP TRIGGER t3_ai;
+--echo # Trigger with table use on child under LOCK TABLES
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+CREATE TRIGGER t3_ai AFTER INSERT ON t3 FOR EACH ROW INSERT INTO t2 VALUES(22);
+INSERT INTO t4 VALUES (4);
+SELECT * FROM t4 ORDER BY c1;
+INSERT INTO t3 VALUES (33);
+SELECT * FROM t4 ORDER BY c1;
+DROP TRIGGER t3_ai;
+DELETE FROM t4 WHERE c1 = 22;
+DELETE FROM t4 WHERE c1 = 33;
+UNLOCK TABLES;
+#
+--echo #
+--echo # Repair
+--echo #
+REPAIR TABLE t4;
+REPAIR TABLE t2;
+SELECT * FROM t4 ORDER BY c1;
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+REPAIR TABLE t4;
+REPAIR TABLE t2;
+SELECT * FROM t4 ORDER BY c1;
+UNLOCK TABLES;
+#
+--echo #
+--echo # Optimize
+--echo #
+OPTIMIZE TABLE t4;
+OPTIMIZE TABLE t2;
+SELECT * FROM t4 ORDER BY c1;
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+OPTIMIZE TABLE t4;
+OPTIMIZE TABLE t2;
+SELECT * FROM t4 ORDER BY c1;
+UNLOCK TABLES;
+#
+--echo #
+--echo # Checksum
+--echo #
+CHECKSUM TABLE t4;
+CHECKSUM TABLE t2;
+SELECT * FROM t4 ORDER BY c1;
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+CHECKSUM TABLE t4;
+CHECKSUM TABLE t2;
+SELECT * FROM t4 ORDER BY c1;
+UNLOCK TABLES;
+#
+--echo #
+--echo # Insert delayed
+--echo #
+# See also Bug#26464 - insert delayed + update + merge = corruption
+# Succeeds in embedded server - is converted to normal insert
+# Fails in normal server, ps-protocol - not supported by engine
+# Fails in normal server, normal protocol - not a base table
+--error 0, ER_ILLEGAL_HA, ER_WRONG_OBJECT
+INSERT DELAYED INTO t4 VALUES(44);
+# Get rid of row in embedded server
+DELETE FROM t4 WHERE c1 = 44;
+INSERT DELAYED INTO t3 VALUES(33);
+ let $wait_cmd= SHOW STATUS LIKE 'Not_flushed_delayed_rows';
+ let $run= query_get_value($wait_cmd, Value, 1);
+ while ($run)
+ {
+ let $run= query_get_value($wait_cmd, Value, 1);
+ }
+SELECT * FROM t4 ORDER BY c1;
+LOCK TABLES t3 WRITE, t2 WRITE, t4 WRITE, t1 WRITE;
+--error ER_DELAYED_INSERT_TABLE_LOCKED, ER_ILLEGAL_HA
+INSERT DELAYED INTO t4 VALUES(444);
+--error ER_DELAYED_INSERT_TABLE_LOCKED, ER_ILLEGAL_HA
+INSERT DELAYED INTO t3 VALUES(333);
+SELECT * FROM t4 ORDER BY c1;
+UNLOCK TABLES;
+DROP TABLE t1, t2, t3, t4;
+#
+--echo #
+--echo # Recursive inclusion of merge tables in their union clauses.
+--echo #
+CREATE TABLE t1 (c1 INT, INDEX(c1));
+CREATE TABLE t2 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t1)
+ INSERT_METHOD=LAST;
+CREATE TABLE t3 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t2,t1)
+ INSERT_METHOD=LAST;
+ALTER TABLE t2 UNION=(t3,t1);
+--error ER_ADMIN_WRONG_MRG_TABLE
+SELECT * FROM t2;
+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#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 --git a/mysql-test/t/myisam.test b/mysql-test/t/myisam.test
index 43c40ab8966..cadab8b3b70 100644
--- a/mysql-test/t/myisam.test
+++ b/mysql-test/t/myisam.test
@@ -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 --git a/mysys/thr_lock.c b/mysys/thr_lock.c
index a81ed925562..7f7be4835a5 100644
--- a/mysys/thr_lock.c
+++ b/mysys/thr_lock.c
@@ -396,6 +396,7 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data,
struct timespec wait_timeout;
enum enum_thr_lock_result result= THR_LOCK_ABORTED;
my_bool can_deadlock= test(data->owner->info->n_cursors);
+ DBUG_ENTER("wait_for_lock");
if (!in_wait_list)
{
@@ -431,13 +432,21 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data,
if the predicate is true.
*/
if (data->cond == 0)
+ {
+ DBUG_PRINT("thr_lock", ("lock granted/aborted"));
break;
+ }
if (rc == ETIMEDOUT || rc == ETIME)
{
+ /* purecov: begin inspected */
+ DBUG_PRINT("thr_lock", ("lock timed out"));
result= THR_LOCK_WAIT_TIMEOUT;
break;
+ /* purecov: end */
}
}
+ DBUG_PRINT("thr_lock", ("aborted: %d in_wait_list: %d",
+ thread_var->abort, in_wait_list));
if (data->cond || data->type == TL_UNLOCK)
{
@@ -453,6 +462,7 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data,
}
else
{
+ DBUG_PRINT("thr_lock", ("lock aborted"));
check_locks(data->lock, "aborted wait_for_lock", 0);
}
}
@@ -471,7 +481,7 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data,
thread_var->current_mutex= 0;
thread_var->current_cond= 0;
pthread_mutex_unlock(&thread_var->mutex);
- return result;
+ DBUG_RETURN(result);
}
@@ -509,7 +519,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner,
and the read lock is not TL_READ_NO_INSERT
*/
- DBUG_PRINT("lock",("write locked by thread: 0x%lx",
+ DBUG_PRINT("lock",("write locked 1 by thread: 0x%lx",
lock->write.data->owner->info->thread_id));
if (thr_lock_owner_equal(data->owner, lock->write.data->owner) ||
(lock->write.data->type <= TL_WRITE_DELAYED &&
@@ -598,10 +608,14 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner,
{
if (lock->write.data->type == TL_WRITE_ONLY)
{
- /* We are not allowed to get a lock in this case */
- data->type=TL_UNLOCK;
- result= THR_LOCK_ABORTED; /* Can't wait for this one */
- goto end;
+ /* Allow lock owner to bypass TL_WRITE_ONLY. */
+ if (!thr_lock_owner_equal(data->owner, lock->write.data->owner))
+ {
+ /* We are not allowed to get a lock in this case */
+ data->type=TL_UNLOCK;
+ result= THR_LOCK_ABORTED; /* Can't wait for this one */
+ goto end;
+ }
}
/*
@@ -631,10 +645,8 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner,
statistic_increment(locks_immediate,&THR_LOCK_lock);
goto end;
}
- /* purecov: begin inspected */
- DBUG_PRINT("lock",("write locked by thread: 0x%lx",
+ DBUG_PRINT("lock",("write locked 2 by thread: 0x%lx",
lock->write.data->owner->info->thread_id));
- /* purecov: end */
}
else
{
@@ -669,7 +681,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner,
goto end;
}
}
- DBUG_PRINT("lock",("write locked by thread: 0x%lx type: %d",
+ DBUG_PRINT("lock",("write locked 3 by thread: 0x%lx type: %d",
lock->read.data->owner->info->thread_id, data->type));
}
wait_queue= &lock->write_wait;
@@ -683,6 +695,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner,
lock_owner= lock->read.data ? lock->read.data : lock->write.data;
if (lock_owner && lock_owner->owner->info == owner->info)
{
+ DBUG_PRINT("lock",("deadlock"));
result= THR_LOCK_DEADLOCK;
goto end;
}
diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc
index 55af0c38aed..be75eff2575 100644
--- a/sql/ha_ndbcluster_binlog.cc
+++ b/sql/ha_ndbcluster_binlog.cc
@@ -321,7 +321,7 @@ ndbcluster_binlog_open_table(THD *thd, NDB_SHARE *share,
DBUG_ENTER("ndbcluster_binlog_open_table");
safe_mutex_assert_owner(&LOCK_open);
- init_tmp_table_share(table_share, share->db, 0, share->table_name,
+ init_tmp_table_share(thd, table_share, share->db, 0, share->table_name,
share->key);
if ((error= open_table_def(thd, table_share, 0)))
{
diff --git a/sql/handler.cc b/sql/handler.cc
index 8a2355c8a87..a4926071598 100644
--- a/sql/handler.cc
+++ b/sql/handler.cc
@@ -2643,7 +2643,7 @@ int ha_create_table(THD *thd, const char *path,
TABLE_SHARE share;
DBUG_ENTER("ha_create_table");
- init_tmp_table_share(&share, db, 0, table_name, path);
+ init_tmp_table_share(thd, &share, db, 0, table_name, path);
if (open_table_def(thd, &share, 0) ||
open_table_from_share(thd, &share, "", 0, (uint) READ_ALL, 0, &table,
TRUE))
@@ -2709,7 +2709,7 @@ int ha_create_table_from_engine(THD* thd, const char *db, const char *name)
if (error)
DBUG_RETURN(2);
- init_tmp_table_share(&share, db, 0, name, path);
+ init_tmp_table_share(thd, &share, db, 0, name, path);
if (open_table_def(thd, &share, 0))
{
DBUG_RETURN(3);
@@ -3717,11 +3717,12 @@ int handler::ha_reset()
int handler::ha_write_row(uchar *buf)
{
int error;
+ DBUG_ENTER("handler::ha_write_row");
if (unlikely(error= write_row(buf)))
- return error;
+ DBUG_RETURN(error);
if (unlikely(error= binlog_log_row<Write_rows_log_event>(table, 0, buf)))
- return error;
- return 0;
+ DBUG_RETURN(error); /* purecov: inspected */
+ DBUG_RETURN(0);
}
diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h
index 12838317646..2663de0b16a 100644
--- a/sql/mysql_priv.h
+++ b/sql/mysql_priv.h
@@ -686,7 +686,6 @@ bool check_single_table_access(THD *thd, ulong privilege,
bool check_routine_access(THD *thd,ulong want_access,char *db,char *name,
bool is_proc, bool no_errors);
bool check_some_access(THD *thd, ulong want_access, TABLE_LIST *table);
-bool check_merge_table_access(THD *thd, char *db, TABLE_LIST *table_list);
bool check_some_routine_access(THD *thd, const char *db, const char *name, bool is_proc);
bool multi_update_precheck(THD *thd, TABLE_LIST *tables);
bool multi_delete_precheck(THD *thd, TABLE_LIST *tables);
@@ -1148,6 +1147,9 @@ TABLE *table_cache_insert_placeholder(THD *thd, const char *key,
bool lock_table_name_if_not_cached(THD *thd, const char *db,
const char *table_name, TABLE **table);
TABLE *find_locked_table(THD *thd, const char *db,const char *table_name);
+void detach_merge_children(TABLE *table, bool clear_refs);
+bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last,
+ TABLE_LIST *new_child_list, TABLE_LIST **new_last);
bool reopen_table(TABLE *table);
bool reopen_tables(THD *thd,bool get_locks,bool in_refresh);
void close_data_files_and_morph_locks(THD *thd, const char *db,
@@ -1398,8 +1400,21 @@ int init_ftfuncs(THD *thd, SELECT_LEX* select, bool no_order);
void wait_for_condition(THD *thd, pthread_mutex_t *mutex,
pthread_cond_t *cond);
int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags);
-int simple_open_n_lock_tables(THD *thd,TABLE_LIST *tables);
-bool open_and_lock_tables(THD *thd,TABLE_LIST *tables);
+/* open_and_lock_tables with optional derived handling */
+bool open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived);
+/* simple open_and_lock_tables without derived handling */
+inline bool simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables)
+{
+ return open_and_lock_tables_derived(thd, tables, FALSE);
+}
+/* open_and_lock_tables with derived handling */
+inline bool open_and_lock_tables(THD *thd, TABLE_LIST *tables)
+{
+ return open_and_lock_tables_derived(thd, tables, TRUE);
+}
+/* simple open_and_lock_tables without derived handling for single table */
+TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l,
+ thr_lock_type lock_type);
bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags);
int lock_tables(THD *thd, TABLE_LIST *tables, uint counter, bool *need_reopen);
int decide_logging_format(THD *thd, TABLE_LIST *tables);
@@ -1976,7 +1991,8 @@ int format_number(uint inputflag,uint max_length,char * pos,uint length,
/* table.cc */
TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key,
uint key_length);
-void init_tmp_table_share(TABLE_SHARE *share, const char *key, uint key_length,
+void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key,
+ uint key_length,
const char *table_name, const char *path);
void free_table_share(TABLE_SHARE *share);
int open_table_def(THD *thd, TABLE_SHARE *share, uint db_flags);
diff --git a/sql/slave.cc b/sql/slave.cc
index 2512954f805..6c130627b14 100644
--- a/sql/slave.cc
+++ b/sql/slave.cc
@@ -1013,6 +1013,12 @@ static int create_table_from_dump(THD* thd, MYSQL *mysql, const char* db,
goto err; // mysql_parse took care of the error send
thd->proc_info = "Opening master dump table";
+ /*
+ Note: If this function starts to fail for MERGE tables,
+ change the next two lines to these:
+ tables.table= NULL; // was set by mysql_rm_table()
+ if (!open_n_lock_single_table(thd, &tables, TL_WRITE))
+ */
tables.lock_type = TL_WRITE;
if (!open_ltable(thd, &tables, TL_WRITE, 0))
{
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index 66d0cda2155..24b5998b380 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -684,6 +684,9 @@ TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name)
to open the table
thd->killed will be set if we run out of memory
+
+ If closing a MERGE child, the calling function has to take care for
+ closing the parent too, if necessary.
*/
@@ -712,6 +715,12 @@ void close_handle_and_leave_table_as_lock(TABLE *table)
share->tmp_table= INTERNAL_TMP_TABLE; // for intern_close_table()
}
+ /*
+ When closing a MERGE parent or child table, detach the children first.
+ Do not clear child table references to allow for reopen.
+ */
+ if (table->child_l || table->parent)
+ detach_merge_children(table, FALSE);
table->file->close();
table->db_stat= 0; // Mark file closed
release_table_share(table->s, RELEASE_NORMAL);
@@ -812,6 +821,10 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild)
void intern_close_table(TABLE *table)
{ // Free all structures
DBUG_ENTER("intern_close_table");
+ DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx",
+ table->s ? table->s->db.str : "?",
+ table->s ? table->s->table_name.str : "?",
+ (long) table));
free_io_cache(table);
delete table->triggers;
@@ -835,6 +848,9 @@ static void free_cache_entry(TABLE *table)
{
DBUG_ENTER("free_cache_entry");
+ /* Assert that MERGE children are not attached before final close. */
+ DBUG_ASSERT(!table->is_children_attached());
+
intern_close_table(table);
if (!table->in_use)
{
@@ -901,6 +917,54 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh,
pthread_mutex_lock(&oldest_unused_share->mutex);
VOID(hash_delete(&table_def_cache, (uchar*) oldest_unused_share));
}
+ DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu",
+ refresh_version));
+ if (if_wait_for_refresh)
+ {
+ /*
+ Other threads could wait in a loop in open_and_lock_tables(),
+ trying to lock one or more of our tables.
+
+ If they wait for the locks in thr_multi_lock(), their lock
+ request is aborted. They loop in open_and_lock_tables() and
+ enter open_table(). Here they notice the table is refreshed and
+ wait for COND_refresh. Then they loop again in
+ open_and_lock_tables() and this time open_table() succeeds. At
+ this moment, if we (the FLUSH TABLES thread) are scheduled and
+ on another FLUSH TABLES enter close_cached_tables(), they could
+ awake while we sleep below, waiting for others threads (us) to
+ close their open tables. If this happens, the other threads
+ would find the tables unlocked. They would get the locks, one
+ after the other, and could do their destructive work. This is an
+ issue if we have LOCK TABLES in effect.
+
+ The problem is that the other threads passed all checks in
+ open_table() before we refresh the table.
+
+ The fix for this problem is to set some_tables_deleted for all
+ threads with open tables. These threads can still get their
+ locks, but will immediately release them again after checking
+ this variable. They will then loop in open_and_lock_tables()
+ again. There they will wait until we update all tables version
+ below.
+
+ Setting some_tables_deleted is done by remove_table_from_cache()
+ in the other branch.
+
+ In other words (reviewer suggestion): You need this setting of
+ some_tables_deleted for the case when table was opened and all
+ related checks were passed before incrementing refresh_version
+ (which you already have) but attempt to lock the table happened
+ after the call to close_old_data_files() i.e. after removal of
+ current thread locks.
+ */
+ for (uint idx=0 ; idx < open_cache.records ; idx++)
+ {
+ TABLE *table=(TABLE*) hash_element(&open_cache,idx);
+ if (table->in_use)
+ table->in_use->some_tables_deleted= 1;
+ }
+ }
}
else
{
@@ -1073,6 +1137,14 @@ static void mark_temp_tables_as_free_for_reuse(THD *thd)
{
table->query_id= 0;
table->file->ha_reset();
+ /*
+ Detach temporary MERGE children from temporary parent to allow new
+ attach at next open. Do not do the detach, if close_thread_tables()
+ is called from a sub-statement. The temporary table might still be
+ used in the top-level statement.
+ */
+ if (table->child_l || table->parent)
+ detach_merge_children(table, TRUE);
}
}
}
@@ -1170,9 +1242,17 @@ static void close_open_tables(THD *thd)
void close_thread_tables(THD *thd)
{
+ TABLE *table;
prelocked_mode_type prelocked_mode= thd->prelocked_mode;
DBUG_ENTER("close_thread_tables");
+#ifdef EXTRA_DEBUG
+ DBUG_PRINT("tcache", ("open tables:"));
+ for (table= thd->open_tables; table; table= table->next)
+ DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
+ table->s->table_name.str, (long) table));
+#endif
+
/*
We are assuming here that thd->derived_tables contains ONLY derived
tables for this substatement. i.e. instead of approach which uses
@@ -1186,7 +1266,7 @@ void close_thread_tables(THD *thd)
*/
if (thd->derived_tables)
{
- TABLE *table, *next;
+ TABLE *next;
/*
Close all derived tables generated in queries like
SELECT * FROM (SELECT * FROM t1)
@@ -1266,6 +1346,13 @@ void close_thread_tables(THD *thd)
if (!thd->active_transaction())
thd->transaction.xid_state.xid.null();
+ /*
+ Note that we need to hold LOCK_open while changing the
+ open_tables list. Another thread may work on it.
+ (See: remove_table_from_cache(), mysql_wait_completed_table())
+ Closing a MERGE child before the parent would be fatal if the
+ other thread tries to abort the MERGE lock in between.
+ */
if (thd->open_tables)
close_open_tables(thd);
@@ -1292,8 +1379,17 @@ bool close_thread_table(THD *thd, TABLE **table_ptr)
DBUG_ENTER("close_thread_table");
DBUG_ASSERT(table->key_read == 0);
DBUG_ASSERT(!table->file || table->file->inited == handler::NONE);
+ DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
+ table->s->table_name.str, (long) table));
*table_ptr=table->next;
+ /*
+ When closing a MERGE parent or child table, detach the children first.
+ Clear child table references to force new assignment at next open.
+ */
+ if (table->child_l || table->parent)
+ detach_merge_children(table, TRUE);
+
if (table->needs_reopen_or_name_lock() ||
thd->version != refresh_version || !table->db_stat)
{
@@ -1308,6 +1404,9 @@ bool close_thread_table(THD *thd, TABLE **table_ptr)
*/
DBUG_ASSERT(!table->open_placeholder);
+ /* Assert that MERGE children are not attached in unused_tables. */
+ DBUG_ASSERT(!table->is_children_attached());
+
/* Free memory and reset for next loop */
table->file->ha_reset();
table->in_use=0;
@@ -1729,6 +1828,8 @@ int drop_temporary_table(THD *thd, TABLE_LIST *table_list)
{
TABLE *table;
DBUG_ENTER("drop_temporary_table");
+ DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'",
+ table_list->db, table_list->table_name));
if (!(table= find_temporary_table(thd, table_list)))
DBUG_RETURN(1);
@@ -1756,6 +1857,24 @@ int drop_temporary_table(THD *thd, TABLE_LIST *table_list)
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));
+
+ /*
+ When closing a MERGE parent or child table, detach the children
+ first. Clear child table references as MERGE table cannot be
+ reopened after final close of one of its tables.
+
+ This is necessary here because it is sometimes called with attached
+ tables and without prior close_thread_tables(). E.g. in
+ mysql_alter_table(), mysql_rm_table_part2(), mysql_truncate(),
+ drop_open_table().
+ */
+ if (table->child_l || table->parent)
+ detach_merge_children(table, TRUE);
+
if (table->prev)
{
table->prev->next= table->next;
@@ -1782,6 +1901,7 @@ void close_temporary_table(THD *thd, TABLE *table,
slave_open_temp_tables--;
}
close_temporary(table, free_share, delete_table);
+ DBUG_VOID_RETURN;
}
@@ -1797,6 +1917,8 @@ void close_temporary(TABLE *table, bool free_share, bool delete_table)
{
handlerton *table_type= table->s->db_type();
DBUG_ENTER("close_temporary");
+ DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'",
+ table->s->db.str, table->s->table_name.str));
free_io_cache(table);
closefrm(table, 0);
@@ -1843,6 +1965,9 @@ bool rename_temporary_table(THD* thd, TABLE *table, const char *db,
static void relink_unused(TABLE *table)
{
+ /* Assert that MERGE children are not attached in unused_tables. */
+ DBUG_ASSERT(!table->is_children_attached());
+
if (table != unused_tables)
{
table->prev->next=table->next; /* Remove from unused list */
@@ -1858,6 +1983,77 @@ static void relink_unused(TABLE *table)
/**
+ @brief Prepare an open merge table for close.
+
+ @param[in] thd thread context
+ @param[in] table table to prepare
+ @param[in,out] prev_pp pointer to pointer of previous table
+
+ @detail
+ If the table is a MERGE parent, just detach the children.
+ If the table is a MERGE child, close the parent (incl. detach).
+*/
+
+static void unlink_open_merge(THD *thd, TABLE *table, TABLE ***prev_pp)
+{
+ DBUG_ENTER("unlink_open_merge");
+
+ if (table->parent)
+ {
+ /*
+ If MERGE child, close parent too. Closing includes detaching.
+
+ This is used for example in ALTER TABLE t1 RENAME TO t5 under
+ LOCK TABLES where t1 is a MERGE child:
+ CREATE TABLE t1 (c1 INT);
+ CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1);
+ LOCK TABLES t1 WRITE, t2 WRITE;
+ ALTER TABLE t1 RENAME TO t5;
+ */
+ TABLE *parent= table->parent;
+ TABLE **prv_p;
+
+ /* Find parent in open_tables list. */
+ for (prv_p= &thd->open_tables;
+ *prv_p && (*prv_p != parent);
+ prv_p= &(*prv_p)->next) {}
+ if (*prv_p)
+ {
+ /* Special treatment required if child follows parent in list. */
+ if (*prev_pp == &parent->next)
+ *prev_pp= prv_p;
+ /*
+ Remove parent from open_tables list and close it.
+ This includes detaching and hence clearing parent references.
+ */
+ close_thread_table(thd, prv_p);
+ }
+ }
+ else if (table->child_l)
+ {
+ /*
+ When closing a MERGE parent, detach the children first. It is
+ not necessary to clear the child or parent table reference of
+ this table because the TABLE is freed. But we need to clear
+ the child or parent references of the other belonging tables
+ so that they cannot be moved into the unused_tables chain with
+ these pointers set.
+
+ This is used for example in ALTER TABLE t2 RENAME TO t5 under
+ LOCK TABLES where t2 is a MERGE parent:
+ CREATE TABLE t1 (c1 INT);
+ CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1);
+ LOCK TABLES t1 WRITE, t2 WRITE;
+ ALTER TABLE t2 RENAME TO t5;
+ */
+ detach_merge_children(table, TRUE);
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
@brief Remove all instances of table from thread's open list and
table cache.
@@ -1868,7 +2064,7 @@ static void relink_unused(TABLE *table)
FALSE - otherwise
@note When unlock parameter is FALSE or current thread doesn't have
- any tables locked with LOCK TABLES tables are assumed to be
+ any tables locked with LOCK TABLES, tables are assumed to be
not locked (for example already unlocked).
*/
@@ -1876,31 +2072,45 @@ void unlink_open_table(THD *thd, TABLE *find, bool unlock)
{
char key[MAX_DBKEY_LENGTH];
uint key_length= find->s->table_cache_key.length;
- TABLE *list, **prev, *next;
+ TABLE *list, **prev;
DBUG_ENTER("unlink_open_table");
safe_mutex_assert_owner(&LOCK_open);
- list= thd->open_tables;
- prev= &thd->open_tables;
memcpy(key, find->s->table_cache_key.str, key_length);
- for (; list ; list=next)
+ /*
+ Note that we need to hold LOCK_open while changing the
+ open_tables list. Another thread may work on it.
+ (See: remove_table_from_cache(), mysql_wait_completed_table())
+ Closing a MERGE child before the parent would be fatal if the
+ other thread tries to abort the MERGE lock in between.
+ */
+ for (prev= &thd->open_tables; *prev; )
{
- next=list->next;
+ list= *prev;
+
if (list->s->table_cache_key.length == key_length &&
!memcmp(list->s->table_cache_key.str, key, key_length))
{
if (unlock && thd->locked_tables)
- mysql_lock_remove(thd, thd->locked_tables, list, TRUE);
+ mysql_lock_remove(thd, thd->locked_tables,
+ list->parent ? list->parent : list, TRUE);
+
+ /* Prepare MERGE table for close. Close parent if necessary. */
+ unlink_open_merge(thd, list, &prev);
+
+ /* Remove table from open_tables list. */
+ *prev= list->next;
+ /* Close table. */
VOID(hash_delete(&open_cache,(uchar*) list)); // Close table
}
else
{
- *prev=list; // put in use list
+ /* Step to next entry in open_tables list. */
prev= &list->next;
}
}
- *prev=0;
+
// Notify any 'refresh' threads
broadcast_refresh();
DBUG_VOID_RETURN;
@@ -2383,9 +2593,14 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
table->s->table_name.str);
DBUG_RETURN(0);
}
+ /*
+ When looking for a usable TABLE, ignore MERGE children, as they
+ belong to their parent and cannot be used explicitly.
+ */
if (!my_strcasecmp(system_charset_info, table->alias, alias) &&
table->query_id != thd->query_id && /* skip tables already used */
- !(thd->prelocked_mode && table->query_id))
+ !(thd->prelocked_mode && table->query_id) &&
+ !table->parent)
{
int distance= ((int) table->reginfo.lock_type -
(int) table_list->lock_type);
@@ -2534,6 +2749,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
table= (TABLE*) hash_next(&open_cache, (uchar*) key, key_length,
&state))
{
+ DBUG_PRINT("tcache", ("in_use table: '%s'.'%s' 0x%lx", table->s->db.str,
+ table->s->table_name.str, (long) table));
/*
Here we flush tables marked for flush.
Normally, table->s->version contains the value of
@@ -2622,6 +2839,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
}
if (table)
{
+ DBUG_PRINT("tcache", ("unused table: '%s'.'%s' 0x%lx", table->s->db.str,
+ table->s->table_name.str, (long) table));
/* Unlink the table from "unused_tables" list. */
if (table == unused_tables)
{ // First unused
@@ -2637,6 +2856,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
{
/* 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 */
@@ -2703,7 +2923,9 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
VOID(pthread_mutex_unlock(&LOCK_open));
DBUG_RETURN(0); // VIEW
}
- DBUG_PRINT("info", ("inserting table 0x%lx into the cache", (long) table));
+ DBUG_PRINT("info", ("inserting table '%s'.'%s' 0x%lx into the cache",
+ table->s->db.str, table->s->table_name.str,
+ (long) table));
VOID(my_hash_insert(&open_cache,(uchar*) table));
}
@@ -2793,9 +3015,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)
@@ -2836,6 +3061,17 @@ bool reopen_table(TABLE *table)
tmp.next= table->next;
tmp.prev= table->prev;
+ /* Preserve MERGE parent. */
+ tmp.parent= table->parent;
+ /* Fix MERGE child list and check for unchanged union. */
+ if ((table->child_l || tmp.child_l) &&
+ fix_merge_after_open(table->child_l, table->child_last_l,
+ tmp.child_l, tmp.child_last_l))
+ {
+ VOID(closefrm(&tmp, 1)); // close file, free everything
+ goto end;
+ }
+
delete table->triggers;
if (table->file)
VOID(closefrm(table, 1)); // close file, free everything
@@ -2857,6 +3093,11 @@ bool reopen_table(TABLE *table)
}
if (table->triggers)
table->triggers->set_table(table);
+ /*
+ Do not attach MERGE children here. The children might be reopened
+ after the parent. Attach children after reopening all tables that
+ require reopen. See for example reopen_tables().
+ */
broadcast_refresh();
error=0;
@@ -2909,7 +3150,22 @@ void close_data_files_and_morph_locks(THD *thd, const char *db,
!strcmp(table->s->db.str, db))
{
if (thd->locked_tables)
- mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
+ {
+ if (table->parent)
+ {
+ /*
+ If MERGE child, need to reopen parent too. This means that
+ the first child to be closed will detach all children from
+ the parent and close it. OTOH in most cases a MERGE table
+ won't have multiple children with the same db.table_name.
+ */
+ mysql_lock_remove(thd, thd->locked_tables, table->parent, TRUE);
+ table->parent->open_placeholder= 1;
+ close_handle_and_leave_table_as_lock(table->parent);
+ }
+ else
+ mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
+ }
table->open_placeholder= 1;
close_handle_and_leave_table_as_lock(table);
}
@@ -2919,6 +3175,62 @@ void close_data_files_and_morph_locks(THD *thd, const char *db,
/**
+ @brief Reattach MERGE children after reopen.
+
+ @param[in] thd thread context
+ @param[in,out] err_tables_p pointer to pointer of tables in error
+
+ @return status
+ @retval FALSE OK, err_tables_p unchanged
+ @retval TRUE Error, err_tables_p contains table(s)
+*/
+
+static bool reattach_merge(THD *thd, TABLE **err_tables_p)
+{
+ TABLE *table;
+ TABLE *next;
+ TABLE **prv_p= &thd->open_tables;
+ bool error= FALSE;
+ DBUG_ENTER("reattach_merge");
+
+ for (table= thd->open_tables; table; table= next)
+ {
+ next= table->next;
+ DBUG_PRINT("tcache", ("check table: '%s'.'%s' 0x%lx next: 0x%lx",
+ table->s->db.str, table->s->table_name.str,
+ (long) table, (long) next));
+ /* Reattach children for MERGE tables with "closed data files" only. */
+ if (table->child_l && !table->children_attached)
+ {
+ DBUG_PRINT("tcache", ("MERGE parent, attach children"));
+ if(table->file->extra(HA_EXTRA_ATTACH_CHILDREN))
+ {
+ my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias);
+ error= TRUE;
+ /* Remove table from open_tables. */
+ *prv_p= next;
+ if (next)
+ prv_p= &next->next;
+ /* Stack table on error list. */
+ table->next= *err_tables_p;
+ *err_tables_p= table;
+ continue;
+ }
+ else
+ {
+ table->children_attached= TRUE;
+ DBUG_PRINT("myrg", ("attached parent: '%s'.'%s' 0x%lx",
+ table->s->db.str,
+ table->s->table_name.str, (long) table));
+ }
+ }
+ prv_p= &table->next;
+ }
+ DBUG_RETURN(error);
+}
+
+
+/**
@brief Reopen all tables with closed data files.
@param thd Thread context
@@ -2942,7 +3254,9 @@ bool reopen_tables(THD *thd,bool get_locks,bool in_refresh)
{
TABLE *table,*next,**prev;
TABLE **tables,**tables_ptr; // For locks
+ TABLE *err_tables= NULL;
bool error=0, not_used;
+ bool merge_table_found= FALSE;
DBUG_ENTER("reopen_tables");
if (!thd->open_tables)
@@ -2951,10 +3265,15 @@ bool reopen_tables(THD *thd,bool get_locks,bool in_refresh)
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
@@ -2966,17 +3285,37 @@ bool reopen_tables(THD *thd,bool get_locks,bool in_refresh)
{
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);
+ /*
+ If we could not allocate 'tables', we may close open tables
+ here. If a MERGE table is affected, detach the children first.
+ It is not necessary to clear the child or parent table reference
+ of this table because the TABLE is freed. But we need to clear
+ the child or parent references of the other belonging tables so
+ that they cannot be moved into the unused_tables chain with
+ these pointers set.
+ */
+ if (table->child_l || table->parent)
+ detach_merge_children(table, TRUE);
VOID(hash_delete(&open_cache,(uchar*) table));
error=1;
}
else
{
+ DBUG_PRINT("tcache", ("opened. need lock: %d",
+ get_locks && !db_stat && !table->parent));
*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)
{
@@ -2985,25 +3324,52 @@ bool reopen_tables(THD *thd,bool get_locks,bool in_refresh)
}
}
}
+ *prev=0;
+ /*
+ When all tables are open again, we can re-attach MERGE children to
+ their parents. All TABLE objects are still present.
+ */
+ DBUG_PRINT("tcache", ("re-attaching MERGE tables: %d", merge_table_found));
+ if (!error && merge_table_found && reattach_merge(thd, &err_tables))
+ {
+ while (err_tables)
+ {
+ VOID(hash_delete(&open_cache, (uchar*) err_tables));
+ err_tables= err_tables->next;
+ }
+ }
+ DBUG_PRINT("tcache", ("open tables to lock: %u",
+ (uint) (tables_ptr - tables)));
if (tables != tables_ptr) // Should we get back old locks
{
MYSQL_LOCK *lock;
- /* We should always get these locks */
+ /*
+ We should always get these locks. Anyway, we must not go into
+ wait_for_tables() as it tries to acquire LOCK_open, which is
+ already locked.
+ */
thd->some_tables_deleted=0;
if ((lock= mysql_lock_tables(thd, tables, (uint) (tables_ptr - tables),
- 0, &not_used)))
+ MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN, &not_used)))
{
thd->locked_tables=mysql_lock_merge(thd->locked_tables,lock);
}
else
+ {
+ /*
+ This case should only happen if there is a bug in the reopen logic.
+ Need to issue error message to have a reply for the application.
+ Not exactly what happened though, but close enough.
+ */
+ my_error(ER_LOCK_DEADLOCK, MYF(0));
error=1;
+ }
}
if (get_locks && tables)
{
my_afree((uchar*) tables);
}
broadcast_refresh();
- *prev=0;
DBUG_RETURN(error);
}
@@ -3030,6 +3396,11 @@ void close_old_data_files(THD *thd, TABLE *table, bool morph_locks,
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.
*/
@@ -3041,13 +3412,33 @@ void close_old_data_files(THD *thd, TABLE *table, bool morph_locks,
if (morph_locks)
{
/*
- Wake up threads waiting for table-level lock on this table
- so they won't sneak in when we will temporarily remove our
- lock on it. This will also give them a chance to close their
- instances of this table.
+ Forward lock handling to MERGE parent. But unlock parent
+ once only.
*/
- mysql_lock_abort(thd, table, TRUE);
- mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
+ TABLE *ulcktbl= table->parent ? table->parent : table;
+ if (ulcktbl->lock_count)
+ {
+ /*
+ Wake up threads waiting for table-level lock on this table
+ so they won't sneak in when we will temporarily remove our
+ lock on it. This will also give them a chance to close their
+ instances of this table.
+ */
+ mysql_lock_abort(thd, ulcktbl, TRUE);
+ mysql_lock_remove(thd, thd->locked_tables, ulcktbl, TRUE);
+ ulcktbl->lock_count= 0;
+ }
+ if ((ulcktbl != table) && ulcktbl->db_stat)
+ {
+ /*
+ Close the parent too. Note that parent can come later in
+ the list of tables. It will then be noticed as closed and
+ as a placeholder. When this happens, do not clear the
+ placeholder flag. See the branch below ("***").
+ */
+ ulcktbl->open_placeholder= 1;
+ close_handle_and_leave_table_as_lock(ulcktbl);
+ }
/*
We want to protect the table from concurrent DDL operations
(like RENAME TABLE) until we will re-open and re-lock it.
@@ -3056,7 +3447,7 @@ void close_old_data_files(THD *thd, TABLE *table, bool morph_locks,
}
close_handle_and_leave_table_as_lock(table);
}
- else if (table->open_placeholder)
+ else if (table->open_placeholder && !morph_locks)
{
/*
We come here only in close-for-back-off scenario. So we have to
@@ -3064,8 +3455,11 @@ void close_old_data_files(THD *thd, TABLE *table, bool morph_locks,
in case of concurrent execution of CREATE TABLE t1 SELECT * FROM t2
and RENAME TABLE t2 TO t1). In close-for-re-open scenario we will
probably want to let it stay.
+
+ Note "***": We must not enter this branch if the placeholder
+ flag has been set because of a former close through a child.
+ See above the comment that refers to this note.
*/
- DBUG_ASSERT(!morph_locks);
table->open_placeholder= 0;
}
}
@@ -3186,13 +3580,29 @@ TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name)
prev= &thd->open_tables;
DBUG_ENTER("drop_locked_tables");
+ /*
+ Note that we need to hold LOCK_open while changing the
+ open_tables list. Another thread may work on it.
+ (See: remove_table_from_cache(), mysql_wait_completed_table())
+ Closing a MERGE child before the parent would be fatal if the
+ other thread tries to abort the MERGE lock in between.
+ */
for (table= thd->open_tables; table ; table=next)
{
next=table->next;
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);
+ /*
+ When closing a MERGE parent or child table, detach the children first.
+ Clear child table references in case this object is opened again.
+ */
+ if (table->child_l || table->parent)
+ detach_merge_children(table, TRUE);
+
if (!found)
{
found= table;
@@ -3241,7 +3651,8 @@ void abort_locked_tables(THD *thd,const char *db, const char *table_name)
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;
}
}
@@ -3272,7 +3683,8 @@ void abort_locked_tables(THD *thd,const char *db, const char *table_name)
share->table_map_id is given a value that with a high certainty is
not used by any other table (the only case where a table id can be
reused is on wrap-around, which means more than 4 billion table
- shares open at the same time).
+ share opens have been executed while one table was open all the
+ time).
share->table_map_id is not ~0UL.
*/
@@ -3511,6 +3923,340 @@ err:
}
+/**
+ @brief Add list of MERGE children to a TABLE_LIST list.
+
+ @param[in] tlist the parent TABLE_LIST object just opened
+
+ @return status
+ @retval 0 OK
+ @retval != 0 Error
+
+ @detail
+ When a MERGE parent table has just been opened, insert the
+ TABLE_LIST chain from the MERGE handle into the table list used for
+ opening tables for this statement. This lets the children be opened
+ too.
+*/
+
+static int add_merge_table_list(TABLE_LIST *tlist)
+{
+ TABLE *parent= tlist->table;
+ TABLE_LIST *child_l;
+ DBUG_ENTER("add_merge_table_list");
+ DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", parent->s->db.str,
+ parent->s->table_name.str, (long) parent));
+
+ /* Must not call this with attached children. */
+ DBUG_ASSERT(!parent->children_attached);
+ /* Must not call this with children list in place. */
+ DBUG_ASSERT(tlist->next_global != parent->child_l);
+ /* Prevent inclusion of another MERGE table. Could make infinite recursion. */
+ if (tlist->parent_l)
+ {
+ my_error(ER_ADMIN_WRONG_MRG_TABLE, MYF(0), tlist->alias);
+ DBUG_RETURN(1);
+ }
+
+ /* Fix children.*/
+ for (child_l= parent->child_l; ; child_l= child_l->next_global)
+ {
+ /*
+ Note: child_l->table may still be set if this parent was taken
+ from the unused_tables chain. Ignore this fact here. The
+ reference will be replaced by the handler in
+ ::extra(HA_EXTRA_ATTACH_CHILDREN).
+ */
+
+ /* Set lock type. */
+ child_l->lock_type= tlist->lock_type;
+
+ /* Set parent reference. */
+ child_l->parent_l= tlist;
+
+ /* Break when this was the last child. */
+ if (&child_l->next_global == parent->child_last_l)
+ break;
+ }
+
+ /* Insert children into the table list. */
+ *parent->child_last_l= tlist->next_global;
+ tlist->next_global= parent->child_l;
+
+ /*
+ Do not fix the prev_global pointers. We will remove the
+ chain soon anyway.
+ */
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief Attach MERGE children to the parent.
+
+ @param[in] tlist the child TABLE_LIST object just opened
+
+ @return status
+ @retval 0 OK
+ @retval != 0 Error
+
+ @note
+ This is called when the last MERGE child has just been opened, let
+ the handler attach the MyISAM tables to the MERGE table. Remove
+ MERGE TABLE_LIST chain from the statement list so that it cannot be
+ changed or freed.
+*/
+
+static int attach_merge_children(TABLE_LIST *tlist)
+{
+ TABLE *parent= tlist->parent_l->table;
+ int error;
+ DBUG_ENTER("attach_merge_children");
+ DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", parent->s->db.str,
+ parent->s->table_name.str, (long) parent));
+
+ /* Must not call this with attached children. */
+ DBUG_ASSERT(!parent->children_attached);
+ /* Must call this with children list in place. */
+ DBUG_ASSERT(tlist->parent_l->next_global == parent->child_l);
+
+ /* Attach MyISAM tables to MERGE table. */
+ error= parent->file->extra(HA_EXTRA_ATTACH_CHILDREN);
+
+ /*
+ Remove children from the table list. Even in case of an error.
+ This should prevent tampering with them.
+ */
+ tlist->parent_l->next_global= *parent->child_last_l;
+
+ /*
+ Do not fix the last childs next_global pointer. It is needed for
+ stepping to the next table in the enclosing loop in open_tables().
+ Do not fix prev_global pointers. We did not set them.
+ */
+
+ if (error)
+ {
+ DBUG_PRINT("error", ("attaching MERGE children failed: %d", my_errno));
+ parent->file->print_error(error, MYF(0));
+ DBUG_RETURN(1);
+ }
+
+ parent->children_attached= TRUE;
+ DBUG_PRINT("myrg", ("attached parent: '%s'.'%s' 0x%lx", parent->s->db.str,
+ parent->s->table_name.str, (long) parent));
+
+ /*
+ Note that we have the cildren in the thd->open_tables list at this
+ point.
+ */
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief Detach MERGE children from the parent.
+
+ @note
+ Call this before the first table of a MERGE table (parent or child)
+ is closed.
+
+ When closing thread tables at end of statement, both parent and
+ children are in thd->open_tables and will be closed. In most cases
+ the children will be closed before the parent. They are opened after
+ the parent and thus stacked into thd->open_tables before it.
+
+ To avoid that we touch a closed children in any way, we must detach
+ the children from the parent when the first belonging table is
+ closed (parent or child).
+
+ All references to the children should be removed on handler level
+ and optionally on table level.
+
+ @note
+ Assure that you call it for a MERGE parent or child only.
+ Either table->child_l or table->parent must be set.
+
+ @param[in] table the TABLE object of the parent
+ @param[in] clear_refs if to clear TABLE references
+ this must be true when called from
+ close_thread_tables() to enable fresh
+ open in open_tables()
+ it must be false when called in preparation
+ for reopen_tables()
+*/
+
+void detach_merge_children(TABLE *table, bool clear_refs)
+{
+ TABLE_LIST *child_l;
+ TABLE *parent= table->child_l ? table : table->parent;
+ bool first_detach;
+ DBUG_ENTER("detach_merge_children");
+ /*
+ Either table->child_l or table->parent must be set. Parent must have
+ child_l set.
+ */
+ DBUG_ASSERT(parent && parent->child_l);
+ DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx clear_refs: %d",
+ table->s->db.str, table->s->table_name.str,
+ (long) table, clear_refs));
+ DBUG_PRINT("myrg", ("parent: '%s'.'%s' 0x%lx", parent->s->db.str,
+ parent->s->table_name.str, (long) parent));
+
+ /*
+ In a open_tables() loop it can happen that not all tables have their
+ children attached yet. Also this is called for every child and the
+ parent from close_thread_tables().
+ */
+ if ((first_detach= parent->children_attached))
+ {
+ VOID(parent->file->extra(HA_EXTRA_DETACH_CHILDREN));
+ parent->children_attached= FALSE;
+ DBUG_PRINT("myrg", ("detached parent: '%s'.'%s' 0x%lx", parent->s->db.str,
+ parent->s->table_name.str, (long) parent));
+ }
+ else
+ DBUG_PRINT("myrg", ("parent is already detached"));
+
+ if (clear_refs)
+ {
+ /* In any case clear the own parent reference. (***) */
+ table->parent= NULL;
+
+ /*
+ On the first detach, clear all references. If this table is the
+ parent, we still may need to clear the child references. The first
+ detach might not have done this.
+ */
+ if (first_detach || (table == parent))
+ {
+ /* Clear TABLE references to force new assignment at next open. */
+ for (child_l= parent->child_l; ; child_l= child_l->next_global)
+ {
+ /*
+ Do not DBUG_ASSERT(child_l->table); open_tables might be
+ incomplete.
+
+ Clear the parent reference of the children only on the first
+ detach. The children might already be closed. They will clear
+ it themseves when this function is called for them with
+ 'clear_refs' true. See above "(***)".
+ */
+ if (first_detach && child_l->table)
+ child_l->table->parent= NULL;
+
+ /* Clear the table reference to force new assignment at next open. */
+ child_l->table= NULL;
+
+ /* Break when this was the last child. */
+ if (&child_l->next_global == parent->child_last_l)
+ break;
+ }
+ }
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ @brief Fix MERGE children after open.
+
+ @param[in] old_child_list first list member from original table
+ @param[in] old_last pointer to &next_global of last list member
+ @param[in] new_child_list first list member from freshly opened table
+ @param[in] new_last pointer to &next_global of last list member
+
+ @return mismatch
+ @retval FALSE OK, no mismatch
+ @retval TRUE Error, lists mismatch
+
+ @detail
+ Main action is to copy TABLE reference for each member of original
+ child list to new child list. After a fresh open these references
+ are NULL. Assign the old children to the new table. Some of them
+ might also be reopened or will be reopened soon.
+
+ Other action is to verify that the table definition with respect to
+ the UNION list did not change.
+
+ @note
+ This function terminates the child list if the respective '*_last'
+ pointer is non-NULL. Do not call it from a place where the list is
+ embedded in another list and this would break it.
+
+ Terminating the list is required for example in the first
+ reopen_table() after open_tables(). open_tables() requires the end
+ of the list not to be terminated because other tables could follow
+ behind the child list.
+
+ If a '*_last' pointer is NULL, the respective list is assumed to be
+ NULL terminated.
+*/
+
+bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last,
+ TABLE_LIST *new_child_list, TABLE_LIST **new_last)
+{
+ bool mismatch= FALSE;
+ DBUG_ENTER("fix_merge_after_open");
+ DBUG_PRINT("myrg", ("old last addr: 0x%lx new last addr: 0x%lx",
+ (long) old_last, (long) new_last));
+
+ /* Terminate the lists for easier check of list end. */
+ if (old_last)
+ *old_last= NULL;
+ if (new_last)
+ *new_last= NULL;
+
+ for (;;)
+ {
+ DBUG_PRINT("myrg", ("old list item: 0x%lx new list item: 0x%lx",
+ (long) old_child_list, (long) new_child_list));
+ /* Break if one of the list is at its end. */
+ if (!old_child_list || !new_child_list)
+ break;
+ /* Old table has references to child TABLEs. */
+ DBUG_ASSERT(old_child_list->table);
+ /* New table does not yet have references to child TABLEs. */
+ DBUG_ASSERT(!new_child_list->table);
+ DBUG_PRINT("myrg", ("old table: '%s'.'%s' new table: '%s'.'%s'",
+ old_child_list->db, old_child_list->table_name,
+ new_child_list->db, new_child_list->table_name));
+ /* Child db.table names must match. */
+ if (strcmp(old_child_list->table_name, new_child_list->table_name) ||
+ strcmp(old_child_list->db, new_child_list->db))
+ break;
+ /*
+ Copy TABLE reference. Child TABLE objects are still in place
+ though not necessarily open yet.
+ */
+ DBUG_PRINT("myrg", ("old table ref: 0x%lx replaces new table ref: 0x%lx",
+ (long) old_child_list->table,
+ (long) new_child_list->table));
+ new_child_list->table= old_child_list->table;
+ /* Step both lists. */
+ old_child_list= old_child_list->next_global;
+ new_child_list= new_child_list->next_global;
+ }
+ DBUG_PRINT("myrg", ("end of list, mismatch: %d", mismatch));
+ /*
+ If the list pointers are not both NULL after the loop, then the
+ lists differ. If the are both identical, but not NULL, then they
+ have at least one table in common and hence the rest of the list
+ would be identical too. But in this case the loop woul run until the
+ list end, where both pointers would become NULL.
+ */
+ if (old_child_list != new_child_list)
+ mismatch= TRUE;
+ if (mismatch)
+ my_error(ER_TABLE_DEF_CHANGED, MYF(0));
+
+ DBUG_RETURN(mismatch);
+}
+
+
/*
Open all tables in list
@@ -3541,7 +4287,7 @@ err:
int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
{
- TABLE_LIST *tables;
+ TABLE_LIST *tables= NULL;
bool refresh;
int result=0;
MEM_ROOT new_frm_mem;
@@ -3601,6 +4347,9 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
*/
for (tables= *start; tables ;tables= tables->next_global)
{
+ DBUG_PRINT("tcache", ("opening table: '%s'.'%s' item: 0x%lx",
+ tables->db, tables->table_name, (long) tables));
+
safe_to_ignore_table= FALSE;
/*
@@ -3652,6 +4401,10 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
else
tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags);
}
+ else
+ DBUG_PRINT("tcache", ("referenced table: '%s'.'%s' 0x%lx",
+ tables->db, tables->table_name,
+ (long) tables->table));
if (!tables->table)
{
@@ -3683,6 +4436,19 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
goto process_view_routines;
}
+ /*
+ If in a MERGE table open, we need to remove the children list
+ from statement table list before restarting. Otherwise the list
+ will be inserted another time.
+ */
+ if (tables->parent_l)
+ {
+ TABLE_LIST *parent_l= tables->parent_l;
+ /* The parent table should be correctly open at this point. */
+ DBUG_ASSERT(parent_l->table);
+ parent_l->next_global= *parent_l->table->child_last_l;
+ }
+
if (refresh) // Refresh in progress
{
/*
@@ -3751,6 +4517,24 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
thd->update_lock_default : tables->lock_type;
tables->table->grant= tables->grant;
+ /* Attach MERGE children if not locked already. */
+ DBUG_PRINT("tcache", ("is parent: %d is child: %d",
+ test(tables->table->child_l),
+ test(tables->parent_l)));
+ DBUG_PRINT("tcache", ("in lock tables: %d in prelock mode: %d",
+ test(thd->locked_tables), test(thd->prelocked_mode)));
+ if (((!thd->locked_tables && !thd->prelocked_mode) ||
+ tables->table->s->tmp_table) &&
+ ((tables->table->child_l &&
+ add_merge_table_list(tables)) ||
+ (tables->parent_l &&
+ (&tables->next_global == tables->parent_l->table->child_last_l) &&
+ attach_merge_children(tables))))
+ {
+ result= -1;
+ goto err;
+ }
+
process_view_routines:
/*
Again we may need cache all routines used by this view and add
@@ -3783,6 +4567,18 @@ process_view_routines:
if (query_tables_last_own)
thd->lex->mark_as_requiring_prelocking(query_tables_last_own);
+ if (result && tables)
+ {
+ /*
+ Some functions determine success as (tables->table != NULL).
+ tables->table is in thd->open_tables. It won't go lost. If the
+ error happens on a MERGE child, clear the parents TABLE reference.
+ */
+ if (tables->parent_l)
+ tables->parent_l->table= NULL;
+ tables->table= NULL;
+ }
+ DBUG_PRINT("tcache", ("returning: %d", result));
DBUG_RETURN(result);
}
@@ -3822,6 +4618,63 @@ static bool check_lock_and_start_stmt(THD *thd, TABLE *table,
}
+/**
+ @brief Open and lock one table
+
+ @param[in] thd thread handle
+ @param[in] table_l table to open is first table in this list
+ @param[in] lock_type lock to use for table
+
+ @return table
+ @retval != NULL OK, opened table returned
+ @retval NULL Error
+
+ @note
+ If ok, the following are also set:
+ table_list->lock_type lock_type
+ table_list->table table
+
+ @note
+ If table_l is a list, not a single table, the list is temporarily
+ broken.
+
+ @detail
+ This function is meant as a replacement for open_ltable() when
+ MERGE tables can be opened. open_ltable() cannot open MERGE tables.
+
+ There may be more differences between open_n_lock_single_table() and
+ open_ltable(). One known difference is that open_ltable() does
+ neither call decide_logging_format() nor handle some other logging
+ and locking issues because it does not call lock_tables().
+*/
+
+TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l,
+ thr_lock_type lock_type)
+{
+ TABLE_LIST *save_next_global;
+ DBUG_ENTER("open_n_lock_single_table");
+
+ /* Remember old 'next' pointer. */
+ save_next_global= table_l->next_global;
+ /* Break list. */
+ table_l->next_global= NULL;
+
+ /* Set requested lock type. */
+ table_l->lock_type= lock_type;
+ /* Allow to open real tables only. */
+ table_l->required_type= FRMTYPE_TABLE;
+
+ /* Open the table. */
+ if (simple_open_n_lock_tables(thd, table_l))
+ table_l->table= NULL; /* Just to be sure. */
+
+ /* Restore list. */
+ table_l->next_global= save_next_global;
+
+ DBUG_RETURN(table_l->table);
+}
+
+
/*
Open and lock one table
@@ -3863,6 +4716,17 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type,
if (table)
{
+ if (table->child_l)
+ {
+ /* A MERGE table must not come here. */
+ /* purecov: begin tested */
+ my_error(ER_WRONG_OBJECT, MYF(0), table->s->db.str,
+ table->s->table_name.str, "BASE TABLE");
+ table= 0;
+ goto end;
+ /* purecov: end */
+ }
+
table_list->lock_type= lock_type;
table_list->table= table;
table->grant= table_list->grant;
@@ -3880,56 +4744,21 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type,
table= 0;
}
}
+
+ end:
thd->proc_info=0;
DBUG_RETURN(table);
}
/*
- Open all tables in list and locks them for read without derived
- tables processing.
-
- SYNOPSIS
- simple_open_n_lock_tables()
- thd - thread handler
- tables - list of tables for open&locking
-
- RETURN
- 0 - ok
- -1 - error
-
- NOTE
- The lock will automaticaly be freed by close_thread_tables()
-*/
-
-int simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables)
-{
- uint counter;
- bool need_reopen;
- DBUG_ENTER("simple_open_n_lock_tables");
-
- for ( ; ; )
- {
- if (open_tables(thd, &tables, &counter, 0))
- DBUG_RETURN(-1);
- if (!lock_tables(thd, tables, counter, &need_reopen))
- break;
- if (!need_reopen)
- DBUG_RETURN(-1);
- close_tables_for_reopen(thd, &tables);
- }
- DBUG_RETURN(0);
-}
-
-
-/*
- Open all tables in list, locks them and process derived tables
- tables processing.
+ Open all tables in list, locks them and optionally process derived tables.
SYNOPSIS
- open_and_lock_tables()
+ open_and_lock_tables_derived()
thd - thread handler
tables - list of tables for open&locking
+ derived - if to handle derived tables
RETURN
FALSE - ok
@@ -3937,27 +4766,43 @@ int simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables)
NOTE
The lock will automaticaly be freed by close_thread_tables()
+
+ NOTE
+ There are two convenience functions:
+ - simple_open_n_lock_tables(thd, tables) without derived handling
+ - open_and_lock_tables(thd, tables) with derived handling
+ Both inline functions call open_and_lock_tables_derived() with
+ the third argument set appropriately.
*/
-bool open_and_lock_tables(THD *thd, TABLE_LIST *tables)
+bool open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived)
{
uint counter;
bool need_reopen;
- DBUG_ENTER("open_and_lock_tables");
+ DBUG_ENTER("open_and_lock_tables_derived");
+ DBUG_PRINT("enter", ("derived handling: %d", derived));
for ( ; ; )
{
if (open_tables(thd, &tables, &counter, 0))
DBUG_RETURN(-1);
+
+ DBUG_EXECUTE_IF("sleep_open_and_lock_after_open", {
+ const char *old_proc_info= thd->proc_info;
+ thd->proc_info= "DBUG sleep";
+ my_sleep(6000000);
+ thd->proc_info= old_proc_info;});
+
if (!lock_tables(thd, tables, counter, &need_reopen))
break;
if (!need_reopen)
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 (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);
}
@@ -4271,7 +5116,17 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
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".
+ Look for example at those places where open_n_lock_single_table()
+ is called. That function implements the temporary breaking of
+ a table list for opening a single table.
+ */
+ for (table= tables;
+ table && table != first_not_own;
+ table= table->next_global)
{
if (!table->placeholder())
{
@@ -4298,7 +5153,17 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
else
{
TABLE_LIST *first_not_own= thd->lex->first_not_own_table();
- 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".
+ Look for example at those places where open_n_lock_single_table()
+ is called. That function implements the temporary breaking of
+ a table list for opening a single table.
+ */
+ for (table= tables;
+ table && table != first_not_own;
+ table= table->next_global)
{
if (!table->placeholder() &&
check_lock_and_start_stmt(thd, table->table, table->lock_type))
@@ -4401,7 +5266,7 @@ TABLE *open_temporary_table(THD *thd, const char *path, const char *db,
saved_cache_key= strmov(tmp_path, path)+1;
memcpy(saved_cache_key, cache_key, key_length);
- init_tmp_table_share(share, saved_cache_key, key_length,
+ init_tmp_table_share(thd, share, saved_cache_key, key_length,
strend(saved_cache_key)+1, tmp_path);
if (open_table_def(thd, share, 0) ||
@@ -4434,6 +5299,8 @@ TABLE *open_temporary_table(THD *thd, const char *path, const char *db,
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);
}
@@ -7112,7 +7979,7 @@ my_bool mysql_rm_tmp_tables(void)
/* We should cut file extention before deleting of table */
memcpy(filePathCopy, filePath, filePath_len - ext_len);
filePathCopy[filePath_len - ext_len]= 0;
- init_tmp_table_share(&share, "", 0, "", filePathCopy);
+ init_tmp_table_share(thd, &share, "", 0, "", filePathCopy);
if (!open_table_def(thd, &share, 0) &&
((handler_file= get_new_handler(&share, thd->mem_root,
share.db_type()))))
@@ -7214,7 +8081,7 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name,
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 (;;)
@@ -7229,6 +8096,8 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name,
&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))
@@ -7267,13 +8136,19 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name,
}
/*
Now we must abort all tables locks used by this thread
- as the thread may be waiting to get a lock for another table
+ as the thread may be waiting to get a lock for another table.
+ Note that we need to hold LOCK_open while going through the
+ list. So that the other thread cannot change it. The other
+ thread must also hold LOCK_open whenever changing the
+ open_tables list. Aborting the MERGE lock after a child was
+ closed and before the parent is closed would be fatal.
*/
for (TABLE *thd_table= in_use->open_tables;
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);
}
}
@@ -7476,7 +8351,9 @@ int abort_and_upgrade_lock(ALTER_PARTITION_PARAM_TYPE *lpt)
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);
@@ -7497,14 +8374,18 @@ int abort_and_upgrade_lock(ALTER_PARTITION_PARAM_TYPE *lpt)
We also downgrade locks after the upgrade to WRITE_ONLY
*/
+/* purecov: begin unused */
void close_open_tables_and_downgrade(ALTER_PARTITION_PARAM_TYPE *lpt)
{
VOID(pthread_mutex_lock(&LOCK_open));
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);
}
+/* purecov: end */
/*
@@ -7570,13 +8451,19 @@ void mysql_wait_completed_table(ALTER_PARTITION_PARAM_TYPE *lpt, TABLE *my_table
}
/*
Now we must abort all tables locks used by this thread
- as the thread may be waiting to get a lock for another table
+ as the thread may be waiting to get a lock for another table.
+ Note that we need to hold LOCK_open while going through the
+ list. So that the other thread cannot change it. The other
+ thread must also hold LOCK_open whenever changing the
+ open_tables list. Aborting the MERGE lock after a child was
+ closed and before the parent is closed would be fatal.
*/
for (TABLE *thd_table= in_use->open_tables;
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);
}
}
@@ -7586,8 +8473,10 @@ void mysql_wait_completed_table(ALTER_PARTITION_PARAM_TYPE *lpt, TABLE *my_table
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;
}
@@ -7846,6 +8735,13 @@ void close_performance_schema_table(THD *thd, Open_tables_state *backup)
pthread_mutex_lock(&LOCK_open);
found_old_table= false;
+ /*
+ Note that we need to hold LOCK_open while changing the
+ open_tables list. Another thread may work on it.
+ (See: remove_table_from_cache(), mysql_wait_completed_table())
+ Closing a MERGE child before the parent would be fatal if the
+ other thread tries to abort the MERGE lock in between.
+ */
while (thd->open_tables)
found_old_table|= close_thread_table(thd, &thd->open_tables);
diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc
index 11e70a2e5da..dabdbd5a030 100644
--- a/sql/sql_insert.cc
+++ b/sql/sql_insert.cc
@@ -2273,7 +2273,17 @@ pthread_handler_t handle_delayed_insert(void *arg)
parsed using a lex, that depends on initialized thd->lex.
*/
lex_start(thd);
- if (!(di->table=open_ltable(thd, &di->table_list, TL_WRITE_DELAYED, 0)))
+ thd->lex->sql_command= SQLCOM_INSERT; // For innodb::store_lock()
+ /*
+ Statement-based replication of INSERT DELAYED has problems with RAND()
+ and user vars, so in mixed mode we go to row-based.
+ */
+ thd->lex->set_stmt_unsafe();
+ thd->set_current_stmt_binlog_row_based_if_mixed();
+
+ /* Open table */
+ if (!(di->table= open_n_lock_single_table(thd, &di->table_list,
+ TL_WRITE_DELAYED)))
{
thd->fatal_error(); // Abort waiting inserts
goto err;
@@ -3309,7 +3319,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
tmp_table.alias= 0;
tmp_table.timestamp_field= 0;
tmp_table.s= &share;
- init_tmp_table_share(&share, "", 0, "", "");
+ init_tmp_table_share(thd, &share, "", 0, "", "");
tmp_table.s->db_create_options=0;
tmp_table.s->blob_ptr_size= portable_sizeof_char_ptr;
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 7e194ac76dc..01bb18592a1 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -470,6 +470,46 @@ end:
}
+/**
+ @brief Check access privs for a MERGE table and fix children lock types.
+
+ @param[in] thd thread handle
+ @param[in] db database name
+ @param[in,out] table_list list of child tables (merge_list)
+ lock_type and optionally db set per table
+
+ @return status
+ @retval 0 OK
+ @retval != 0 Error
+
+ @detail
+ This function is used for write access to MERGE tables only
+ (CREATE TABLE, ALTER TABLE ... UNION=(...)). Set TL_WRITE for
+ every child. Set 'db' for every child if not present.
+*/
+
+static bool check_merge_table_access(THD *thd, char *db,
+ TABLE_LIST *table_list)
+{
+ int error= 0;
+
+ if (table_list)
+ {
+ /* Check that all tables use the current database */
+ TABLE_LIST *tlist;
+
+ for (tlist= table_list; tlist; tlist= tlist->next_local)
+ {
+ if (!tlist->db || !tlist->db[0])
+ tlist->db= db; /* purecov: inspected */
+ }
+ error= check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL,
+ table_list,0);
+ }
+ return error;
+}
+
+
/* This works because items are allocated with sql_alloc() */
void free_items(Item *item)
@@ -2254,6 +2294,19 @@ mysql_execute_command(THD *thd)
select_lex->options|= SELECT_NO_UNLOCK;
unit->set_limit(select_lex);
+ /*
+ Disable non-empty MERGE tables with CREATE...SELECT. Too
+ complicated. See Bug #26379. Empty MERGE tables are read-only
+ and don't allow CREATE...SELECT anyway.
+ */
+ if (create_info.used_fields & HA_CREATE_USED_UNION)
+ {
+ my_error(ER_WRONG_OBJECT, MYF(0), create_table->db,
+ create_table->table_name, "BASE TABLE");
+ res= 1;
+ goto end_with_restore_list;
+ }
+
if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE))
{
lex->link_first_table_back(create_table, link_to_local);
@@ -5094,26 +5147,6 @@ bool check_some_access(THD *thd, ulong want_access, TABLE_LIST *table)
}
-bool check_merge_table_access(THD *thd, char *db,
- TABLE_LIST *table_list)
-{
- int error=0;
- if (table_list)
- {
- /* Check that all tables use the current database */
- TABLE_LIST *tmp;
- for (tmp= table_list; tmp; tmp= tmp->next_local)
- {
- if (!tmp->db || !tmp->db[0])
- tmp->db=db;
- }
- error=check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL,
- table_list,0);
- }
- return error;
-}
-
-
/****************************************************************************
Check stack size; Send error if there isn't enough stack to continue
****************************************************************************/
diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc
index ad9eec1906a..94b58bf913f 100644
--- a/sql/sql_partition.cc
+++ b/sql/sql_partition.cc
@@ -4951,7 +4951,7 @@ the generated partition syntax in a correct manner.
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 --git a/sql/sql_select.cc b/sql/sql_select.cc
index 862948e48a4..c6c21dccc37 100644
--- a/sql/sql_select.cc
+++ b/sql/sql_select.cc
@@ -9608,7 +9608,7 @@ create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List<Item> &fields,
table->keys_in_use_for_query.init();
table->s= share;
- init_tmp_table_share(share, "", 0, tmpname, tmpname);
+ init_tmp_table_share(thd, share, "", 0, tmpname, tmpname);
share->blob_field= blob_field;
share->blob_ptr_size= mi_portable_sizeof_char_ptr;
share->db_low_byte_first=1; // True for HEAP and MyISAM
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
index bf4a02ae5cc..91acd8d20a3 100644
--- a/sql/sql_table.cc
+++ b/sql/sql_table.cc
@@ -1556,13 +1556,18 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
/* Don't give warnings for not found errors, as we already generate notes */
thd->no_warnings_for_error= 1;
+ /* Remove the tables from the HANDLER list, if they are in it. */
+ mysql_ha_flush(thd, tables, MYSQL_HA_CLOSE_FINAL, 1);
+
for (table= tables; table; table= table->next_local)
{
char *db=table->db;
handlerton *table_type;
enum legacy_db_type frm_db_type;
- mysql_ha_flush(thd, table, MYSQL_HA_CLOSE_FINAL, 1);
+ DBUG_PRINT("table", ("table_l: '%s'.'%s' table: 0x%lx s: 0x%lx",
+ table->db, table->table_name, (long) table->table,
+ table->table ? (long) table->table->s : (long) -1));
error= drop_temporary_table(thd, table);
@@ -1690,6 +1695,8 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
wrong_tables.append(',');
wrong_tables.append(String(table->table_name,system_charset_info));
}
+ DBUG_PRINT("table", ("table: 0x%lx s: 0x%lx", (long) table->table,
+ table->table ? (long) table->table->s : (long) -1));
}
/*
It's safe to unlock LOCK_open: we have an exclusive lock
@@ -3836,6 +3843,8 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table,
DBUG_RETURN(send_check_errmsg(thd, table, "restore",
"Failed to open partially restored table"));
}
+ /* A MERGE table must not come here. */
+ DBUG_ASSERT(!table->table || !table->table->child_l);
pthread_mutex_unlock(&LOCK_open);
DBUG_RETURN(0);
}
@@ -3878,6 +3887,10 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
table= &tmp_table;
pthread_mutex_unlock(&LOCK_open);
}
+
+ /* A MERGE table must not come here. */
+ DBUG_ASSERT(!table->child_l);
+
/*
REPAIR TABLE ... USE_FRM for temporary tables makes little sense.
*/
@@ -4032,6 +4045,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
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;
@@ -4062,16 +4077,24 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
table->next_local= save_next_local;
thd->open_options&= ~extra_open_options;
}
+ DBUG_PRINT("admin", ("table: 0x%lx", (long) table->table));
+
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
+ /* purecov: begin inspected */
+ DBUG_PRINT("admin", ("severe error, stop"));
goto err;
+ /* purecov: end */
default: // should be 0 otherwise
+ DBUG_PRINT("admin", ("prepare_func succeeded"));
;
}
}
@@ -4086,6 +4109,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
*/
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));
@@ -4100,14 +4124,17 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
if (table->view)
{
+ DBUG_PRINT("admin", ("calling view_operator_func"));
result_code= (*view_operator_func)(thd, table);
goto send_result;
}
if ((table->table->db_stat & HA_READ_ONLY) && open_for_modify)
{
+ /* purecov: begin inspected */
char buff[FN_REFLEN + MYSQL_ERRMSG_SIZE];
uint length;
+ DBUG_PRINT("admin", ("sending error message"));
protocol->prepare_for_resend();
protocol->store(table_name, system_charset_info);
protocol->store(operator_name, system_charset_info);
@@ -4122,11 +4149,13 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
if (protocol->write())
goto err;
continue;
+ /* purecov: end */
}
/* Close all instances of the table to allow repair to rename files */
if (lock_type == TL_WRITE && table->table->s->version)
{
+ DBUG_PRINT("admin", ("removing table from cache"));
pthread_mutex_lock(&LOCK_open);
const char *old_message=thd->enter_cond(&COND_refresh, &LOCK_open,
"Waiting to get writelock");
@@ -4146,6 +4175,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
if (table->table->s->crashed && operator_func == &handler::ha_check)
{
+ /* purecov: begin inspected */
+ DBUG_PRINT("admin", ("sending crashed warning"));
protocol->prepare_for_resend();
protocol->store(table_name, system_charset_info);
protocol->store(operator_name, system_charset_info);
@@ -4154,6 +4185,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
system_charset_info);
if (protocol->write())
goto err;
+ /* purecov: end */
}
if (operator_func == &handler::ha_repair &&
@@ -4164,6 +4196,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
HA_ADMIN_NEEDS_ALTER))
{
my_bool save_no_send_ok= thd->net.no_send_ok;
+ DBUG_PRINT("admin", ("recreating table"));
ha_autocommit_or_rollback(thd, 1);
close_thread_tables(thd);
tmp_disable_binlog(thd); // binlogging is done by caller if wanted
@@ -4176,7 +4209,9 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
}
+ 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:
@@ -5834,10 +5869,25 @@ view_err:
start_waiting_global_read_lock(thd);
DBUG_RETURN(error);
}
- if (!(table=open_ltable(thd, table_list, TL_WRITE_ALLOW_READ, 0)))
+
+ if (!(table= open_n_lock_single_table(thd, table_list, TL_WRITE_ALLOW_READ)))
DBUG_RETURN(TRUE);
table->use_all_columns();
+ /*
+ Prohibit changing of the UNION list of a non-temporary MERGE table
+ under LOCK tables. It would be quite difficult to reuse a shrinked
+ set of tables from the old table or to open a new TABLE object for
+ an extended list and verify that they belong to locked tables.
+ */
+ if (thd->locked_tables &&
+ (create_info->used_fields & HA_CREATE_USED_UNION) &&
+ (table->s->tmp_table == NO_TMP_TABLE))
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
/* Check that we are not trying to rename to an existing table */
if (new_name)
{
@@ -6305,6 +6355,7 @@ view_err:
goto err;
/* Open the table if we need to copy the data. */
+ DBUG_PRINT("info", ("need_copy_table: %u", need_copy_table));
if (need_copy_table != ALTER_TABLE_METADATA_ONLY)
{
if (table->s->tmp_table)
@@ -6328,6 +6379,10 @@ view_err:
}
if (!new_table)
goto err1;
+ /*
+ Note: In case of MERGE table, we do not attach children. We do not
+ copy data for MERGE tables. Only the children have data.
+ */
}
/* Copy the data if necessary. */
@@ -6335,6 +6390,10 @@ view_err:
thd->cuted_fields=0L;
thd->proc_info="copy to tmp table";
copied=deleted=0;
+ /*
+ We do not copy data for MERGE tables. Only the children have data.
+ MERGE tables have HA_NO_COPY_ON_ALTER set.
+ */
if (new_table && !(new_table->file->ha_table_flags() & HA_NO_COPY_ON_ALTER))
{
/* We don't want update TIMESTAMP fields during ALTER TABLE. */
@@ -6472,7 +6531,10 @@ view_err:
if (new_table)
{
- /* Close the intermediate table that will be the new table */
+ /*
+ Close the intermediate table that will be the new table.
+ Note that MERGE tables do not have their children attached here.
+ */
intern_close_table(new_table);
my_free(new_table,MYF(0));
}
@@ -6565,6 +6627,7 @@ view_err:
/*
Now we have to inform handler that new .FRM file is in place.
To do this we need to obtain a handler object for it.
+ NO need to tamper with MERGE tables. The real open is done later.
*/
TABLE *t_table;
if (new_name != table_name || new_db != db)
@@ -6632,7 +6695,7 @@ view_err:
/*
For the alter table to be properly flushed to the logs, we
have to open the new table. If not, we get a problem on server
- shutdown.
+ shutdown. But we do not need to attach MERGE children.
*/
char path[FN_REFLEN];
TABLE *t_table;
@@ -6962,6 +7025,12 @@ bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list)
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;
@@ -6993,6 +7062,7 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables,
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];
@@ -7000,7 +7070,7 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables,
strxmov(table_name, table->db ,".", table->table_name, NullS);
- t= table->table= open_ltable(thd, table, TL_READ, 0);
+ t= table->table= open_n_lock_single_table(thd, table, TL_READ);
thd->clear_error(); // these errors shouldn't get client
protocol->prepare_for_resend();
diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc
index ce26b025430..53f799fff1c 100644
--- a/sql/sql_trigger.cc
+++ b/sql/sql_trigger.cc
@@ -436,13 +436,37 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
if (lock_table_names(thd, tables))
goto end;
- /* We also don't allow creation of triggers on views. */
- tables->required_type= FRMTYPE_TABLE;
-
- if (reopen_name_locked_table(thd, tables, TRUE))
+ /*
+ If the table is under LOCK TABLES, lock_table_names() does not set
+ tables->table. Find the table in open_tables.
+ */
+ if (!tables->table && thd->locked_tables)
+ {
+ for (table= thd->open_tables;
+ table && (strcmp(table->s->table_name.str, tables->table_name) ||
+ strcmp(table->s->db.str, tables->db));
+ table= table->next) {}
+ tables->table= table;
+ }
+ if (!tables->table)
{
- unlock_table_name(thd, tables);
+ /* purecov: begin inspected */
+ my_error(ER_TABLE_NOT_LOCKED, MYF(0), tables->alias);
goto end;
+ /* purecov: end */
+ }
+
+ /* No need to reopen the table if it is locked with LOCK TABLES. */
+ if (!thd->locked_tables || (tables->table->in_use != thd))
+ {
+ /* We also don't allow creation of triggers on views. */
+ tables->required_type= FRMTYPE_TABLE;
+
+ if (reopen_name_locked_table(thd, tables, TRUE))
+ {
+ unlock_table_name(thd, tables);
+ goto end;
+ }
}
table= tables->table;
@@ -462,6 +486,16 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
table->triggers->create_trigger(thd, tables, &stmt_query):
table->triggers->drop_trigger(thd, tables, &stmt_query));
+ /* Under LOCK TABLES we must reopen the table to activate the trigger. */
+ if (!result && thd->locked_tables)
+ {
+ close_data_files_and_morph_locks(thd, table->s->db.str,
+ table->s->table_name.str);
+ thd->in_lock_tables= 1;
+ result= reopen_tables(thd, 1, 0);
+ thd->in_lock_tables= 0;
+ }
+
end:
if (!result)
diff --git a/sql/table.cc b/sql/table.cc
index c3ddb809b9e..2143faaff5c 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -329,6 +329,7 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key,
SYNOPSIS
init_tmp_table_share()
+ thd thread handle
share Share to fill
key Table_cache_key, as generated from create_table_def_key.
must start with db name.
@@ -346,7 +347,7 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key,
use key_length= 0 as neither table_cache_key or key_length will be used).
*/
-void init_tmp_table_share(TABLE_SHARE *share, const char *key,
+void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key,
uint key_length, const char *table_name,
const char *path)
{
@@ -373,9 +374,14 @@ void init_tmp_table_share(TABLE_SHARE *share, const char *key,
anyway to be able to catch errors.
*/
share->table_map_version= ~(ulonglong)0;
- share->table_map_id= ~0UL;
share->cached_row_logging_check= -1;
+ /*
+ table_map_id is also used for MERGE tables to suppress repeated
+ compatibility checks.
+ */
+ share->table_map_id= (ulong) thd->query_id;
+
DBUG_VOID_RETURN;
}
@@ -4477,6 +4483,25 @@ void st_table::mark_columns_needed_for_insert()
mark_auto_increment_column();
}
+
+/**
+ @brief Check if this is part of a MERGE table with attached children.
+
+ @return status
+ @retval TRUE children are attached
+ @retval FALSE no MERGE part or children not attached
+
+ @detail
+ A MERGE table consists of a parent TABLE and zero or more child
+ TABLEs. Each of these TABLEs is called a part of a MERGE table.
+*/
+
+bool st_table::is_children_attached(void)
+{
+ return((child_l && children_attached) ||
+ (parent && parent->children_attached));
+}
+
/*
Cleanup this table for re-execution.
diff --git a/sql/table.h b/sql/table.h
index 2bbd71b70c6..7bf0f6bb792 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -431,6 +431,12 @@ typedef struct st_table_share
{
return (table_category == TABLE_CATEGORY_PERFORMANCE);
}
+
+ inline ulong get_table_def_version()
+ {
+ return table_map_id;
+ }
+
} TABLE_SHARE;
@@ -455,6 +461,11 @@ struct st_table {
#endif
struct st_table *next, *prev;
+ /* For the below MERGE related members see top comment in ha_myisammrg.cc */
+ struct st_table *parent; /* Set in MERGE child. Ptr to parent */
+ TABLE_LIST *child_l; /* Set in MERGE parent. List of children */
+ TABLE_LIST **child_last_l; /* Set in MERGE parent. End of list */
+
THD *in_use; /* Which thread uses this */
Field **field; /* Pointer to fields */
@@ -622,6 +633,8 @@ struct st_table {
my_bool insert_or_update; /* Can be used by the handler */
my_bool alias_name_used; /* true if table_name is alias */
my_bool get_fields_in_item_tree; /* Signal to fix_field */
+ /* If MERGE children attached to parent. See top comment in ha_myisammrg.cc */
+ my_bool children_attached;
REGINFO reginfo; /* field connections */
MEM_ROOT mem_root;
@@ -673,6 +686,7 @@ struct st_table {
*/
inline bool needs_reopen_or_name_lock()
{ return s->version != refresh_version; }
+ bool is_children_attached(void);
};
enum enum_schema_table_state
@@ -996,6 +1010,8 @@ struct TABLE_LIST
(non-zero only for merged underlying tables of a view).
*/
TABLE_LIST *referencing_view;
+ /* Ptr to parent MERGE table list item. See top comment in ha_myisammrg.cc */
+ TABLE_LIST *parent_l;
/*
Security context (non-zero only for tables which belong
to view with SQL SECURITY DEFINER)
@@ -1176,6 +1192,20 @@ struct TABLE_LIST
*/
bool process_index_hints(TABLE *table);
+ /* Access MERGE child def version. See top comment in ha_myisammrg.cc */
+ inline ulong get_child_def_version()
+ {
+ return child_def_version;
+ }
+ inline void set_child_def_version(ulong version)
+ {
+ child_def_version= version;
+ }
+ inline void init_child_def_version()
+ {
+ child_def_version= ~0UL;
+ }
+
private:
bool prep_check_option(THD *thd, uint8 check_opt_type);
bool prep_where(THD *thd, Item **conds, bool no_where_clause);
@@ -1183,6 +1213,9 @@ private:
Cleanup for re-execution in a prepared statement or a stored
procedure.
*/
+
+ /* Remembered MERGE child def version. See top comment in ha_myisammrg.cc */
+ ulong child_def_version;
};
class Item;
diff --git a/storage/myisam/ha_myisam.cc b/storage/myisam/ha_myisam.cc
index ca4c40547ee..dcedcf03f65 100644
--- a/storage/myisam/ha_myisam.cc
+++ b/storage/myisam/ha_myisam.cc
@@ -119,6 +119,10 @@ static void mi_check_print_msg(MI_CHECK *param, const char* msg_type,
definition for further use in mi_create or for a check for underlying
table conformance in merge engine.
+ The caller needs to free *recinfo_out after use. Since *recinfo_out
+ and *keydef_out are allocated with a my_multi_malloc, *keydef_out
+ is freed automatically when *recinfo_out is freed.
+
RETURN VALUE
0 OK
!0 error code
diff --git a/storage/myisam/ha_myisam.h b/storage/myisam/ha_myisam.h
index e8594fc9039..ca44ae9ad87 100644
--- a/storage/myisam/ha_myisam.h
+++ b/storage/myisam/ha_myisam.h
@@ -140,4 +140,8 @@ class ha_myisam: public handler
*engine_callback,
ulonglong *engine_data);
#endif
+ MI_INFO *file_ptr(void)
+ {
+ return file;
+ }
};
diff --git a/storage/myisammrg/ha_myisammrg.cc b/storage/myisammrg/ha_myisammrg.cc
index 8a914e8a2de..f91b0dc7a92 100644
--- a/storage/myisammrg/ha_myisammrg.cc
+++ b/storage/myisammrg/ha_myisammrg.cc
@@ -14,6 +14,82 @@
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+/*
+ MyISAM MERGE tables
+
+ A MyISAM MERGE table is kind of a union of zero or more MyISAM tables.
+
+ Besides the normal form file (.frm) a MERGE table has a meta file
+ (.MRG) with a list of tables. These are paths to the MyISAM table
+ files. The last two components of the path contain the database name
+ and the table name respectively.
+
+ When a MERGE table is open, there exists an TABLE object for the MERGE
+ table itself and a TABLE object for each of the MyISAM tables. For
+ abbreviated writing, I call the MERGE table object "parent" and the
+ MyISAM table objects "children".
+
+ A MERGE table is almost always opened through open_and_lock_tables()
+ and hence through open_tables(). When the parent appears in the list
+ of tables to open, the initial open of the handler does nothing but
+ read the meta file and collect a list of TABLE_LIST objects for the
+ children. This list is attached to the parent TABLE object as
+ TABLE::child_l. The end of the children list is saved in
+ TABLE::child_last_l.
+
+ Back in open_tables(), add_merge_table_list() is called. It updates
+ each list member with the lock type and a back pointer to the parent
+ TABLE_LIST object TABLE_LIST::parent_l. The list is then inserted in
+ the list of tables to open, right behind the parent. Consequently,
+ open_tables() opens the children, one after the other. The TABLE
+ references of the TABLE_LIST objects are implicitly set to the open
+ tables. The children are opened as independent MyISAM tables, right as
+ if they are used by the SQL statement.
+
+ TABLE_LIST::parent_l is required to find the parent 1. when the last
+ child has been opened and children are to be attached, and 2. when an
+ error happens during child open and the child list must be removed
+ from the queuery list. In these cases the current child does not have
+ TABLE::parent set or does not have a TABLE at all respectively.
+
+ When the last child is open, attach_merge_children() is called. It
+ removes the list of children from the open list. Then the children are
+ "attached" to the parent. All required references between parent and
+ children are set up.
+
+ The MERGE storage engine sets up an array with references to the
+ low-level MyISAM table objects (MI_INFO). It remembers the state of
+ the table in MYRG_INFO::children_attached.
+
+ Every child TABLE::parent references the parent TABLE object. That way
+ TABLE objects belonging to a MERGE table can be identified.
+ TABLE::parent is required because the parent and child TABLE objects
+ can live longer than the parent TABLE_LIST object. So the path
+ child->pos_in_table_list->parent_l->table can be broken.
+
+ If necessary, the compatibility of parent and children is checked.
+ This check is necessary when any of the objects are reopend. This is
+ detected by comparing the current table def version against the
+ remembered child def version. On parent open, the list members are
+ initialized to an "impossible"/"undefined" version value. So the check
+ is always executed on the first attach.
+
+ The version check is done in myisammrg_attach_children_callback(),
+ which is called for every child. ha_myisammrg::attach_children()
+ initializes 'need_compat_check' to FALSE and
+ myisammrg_attach_children_callback() sets it ot TRUE if a table
+ def version mismatches the remembered child def version.
+
+ Finally the parent TABLE::children_attached is set.
+
+ ---
+
+ On parent open the storage engine structures are allocated and initialized.
+ They stay with the open table until its final close.
+
+
+*/
+
#ifdef USE_PRAGMA_IMPLEMENTATION
#pragma implementation // gcc: Class implementation
#endif
@@ -22,14 +98,11 @@
#include "mysql_priv.h"
#include <mysql/plugin.h>
#include <m_ctype.h>
+#include "../myisam/ha_myisam.h"
#include "ha_myisammrg.h"
#include "myrg_def.h"
-/*****************************************************************************
-** MyISAM MERGE tables
-*****************************************************************************/
-
static handler *myisammrg_create_handler(handlerton *hton,
TABLE_SHARE *table,
MEM_ROOT *mem_root)
@@ -38,10 +111,23 @@ static handler *myisammrg_create_handler(handlerton *hton,
}
+/**
+ @brief Constructor
+*/
+
ha_myisammrg::ha_myisammrg(handlerton *hton, TABLE_SHARE *table_arg)
:handler(hton, table_arg), file(0)
{}
+
+/**
+ @brief Destructor
+*/
+
+ha_myisammrg::~ha_myisammrg(void)
+{}
+
+
static const char *ha_myisammrg_exts[] = {
".MRG",
NullS
@@ -89,25 +175,311 @@ const char *ha_myisammrg::index_type(uint key_number)
}
-int ha_myisammrg::open(const char *name, int mode, uint test_if_locked)
+/**
+ @brief Callback function for open of a MERGE parent table.
+
+ @detail This function adds a TABLE_LIST object for a MERGE child table
+ to a list of tables of the parent TABLE object. It is called for
+ each child table.
+
+ The list of child TABLE_LIST objects is kept in the TABLE object of
+ the parent for the whole life time of the MERGE table. It is
+ inserted in the statement list behind the MERGE parent TABLE_LIST
+ object when the MERGE table is opened. It is removed from the
+ statement list after the last child is opened.
+
+ All memeory used for the child TABLE_LIST objects and the strings
+ referred by it are taken from the parent TABLE::mem_root. Thus they
+ are all freed implicitly at the final close of the table.
+
+ TABLE::child_l -> TABLE_LIST::next_global -> TABLE_LIST::next_global
+ # # ^ # ^
+ # # | # |
+ # # +--------- TABLE_LIST::prev_global
+ # # |
+ # |<--- TABLE_LIST::prev_global |
+ # |
+ TABLE::child_last_l -----------------------------------------+
+
+ @param[in] callback_param data pointer as given to myrg_parent_open()
+ @param[in] filename file name of MyISAM table
+ without extension.
+
+ @return status
+ @retval 0 OK
+ @retval != 0 Error
+*/
+
+static int myisammrg_parent_open_callback(void *callback_param,
+ const char *filename)
+{
+ ha_myisammrg *ha_myrg;
+ TABLE *parent;
+ TABLE_LIST *child_l;
+ const char *db;
+ const char *table_name;
+ uint dirlen;
+ char dir_path[FN_REFLEN];
+ DBUG_ENTER("myisammrg_parent_open_callback");
+
+ /* Extract child table name and database name from filename. */
+ dirlen= dirname_length(filename);
+ if (dirlen >= FN_REFLEN)
+ {
+ /* purecov: begin inspected */
+ DBUG_PRINT("error", ("name too long: '%.64s'", filename));
+ my_errno= ENAMETOOLONG;
+ DBUG_RETURN(1);
+ /* purecov: end */
+ }
+ table_name= filename + dirlen;
+ dirlen--; /* Strip off trailing '/'. */
+ memcpy(dir_path, filename, dirlen);
+ dir_path[dirlen]= '\0';
+ db= base_name(dir_path);
+ dirlen-= db - dir_path; /* This is now the length of 'db'. */
+ DBUG_PRINT("myrg", ("open: '%s'.'%s'", db, table_name));
+
+ ha_myrg= (ha_myisammrg*) callback_param;
+ parent= ha_myrg->table_ptr();
+
+ /* Get a TABLE_LIST object. */
+ if (!(child_l= (TABLE_LIST*) alloc_root(&parent->mem_root,
+ sizeof(TABLE_LIST))))
+ {
+ /* purecov: begin inspected */
+ DBUG_PRINT("error", ("my_malloc error: %d", my_errno));
+ DBUG_RETURN(1);
+ /* purecov: end */
+ }
+ bzero((char*) child_l, sizeof(TABLE_LIST));
+
+ /* Set database (schema) name. */
+ child_l->db_length= dirlen;
+ child_l->db= strmake_root(&parent->mem_root, db, dirlen);
+ /* Set table name. */
+ child_l->table_name_length= strlen(table_name);
+ child_l->table_name= strmake_root(&parent->mem_root, table_name,
+ child_l->table_name_length);
+ /* Convert to lowercase if required. */
+ if (lower_case_table_names && child_l->table_name_length)
+ child_l->table_name_length= my_casedn_str(files_charset_info,
+ child_l->table_name);
+ /* Set alias. */
+ child_l->alias= child_l->table_name;
+
+ /* Initialize table map to 'undefined'. */
+ child_l->init_child_def_version();
+
+ /* Link TABLE_LIST object into the parent list. */
+ if (!parent->child_last_l)
+ {
+ /* Initialize parent->child_last_l when handling first child. */
+ parent->child_last_l= &parent->child_l;
+ }
+ *parent->child_last_l= child_l;
+ child_l->prev_global= parent->child_last_l;
+ parent->child_last_l= &child_l->next_global;
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief Callback function for attaching a MERGE child table.
+
+ @detail This function retrieves the MyISAM table handle from the
+ next child table. It is called for each child table.
+
+ @param[in] callback_param data pointer as given to
+ myrg_attach_children()
+
+ @return pointer to open MyISAM table structure
+ @retval !=NULL OK, returning pointer
+ @retval NULL, my_errno == 0 Ok, no more child tables
+ @retval NULL, my_errno != 0 error
+*/
+
+static MI_INFO *myisammrg_attach_children_callback(void *callback_param)
+{
+ ha_myisammrg *ha_myrg;
+ TABLE *parent;
+ TABLE *child;
+ TABLE_LIST *child_l;
+ MI_INFO *myisam;
+ DBUG_ENTER("myisammrg_attach_children_callback");
+
+ my_errno= 0;
+ ha_myrg= (ha_myisammrg*) callback_param;
+ parent= ha_myrg->table_ptr();
+
+ /* Get child list item. */
+ child_l= ha_myrg->next_child_attach;
+ if (!child_l)
+ {
+ 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.
+ */
+ if (&child_l->next_global == parent->child_last_l)
+ {
+ DBUG_PRINT("myrg", ("attaching last child"));
+ ha_myrg->next_child_attach= NULL;
+ }
+ else
+ ha_myrg->next_child_attach= child_l->next_global;
+
+ /* Set parent reference. */
+ child->parent= parent;
+
+ /*
+ Do a quick compatibility check. The table def version is set when
+ the table share is created. The child def version is copied
+ from the table def version after a sucessful compatibility check.
+ We need to repeat the compatibility check only if a child is opened
+ from a different share than last time it was used with this MERGE
+ table.
+ */
+ DBUG_PRINT("myrg", ("table_def_version last: %lu current: %lu",
+ (ulong) child_l->get_child_def_version(),
+ (ulong) child->s->get_table_def_version()));
+ if (child_l->get_child_def_version() != child->s->get_table_def_version())
+ ha_myrg->need_compat_check= TRUE;
+
+ /*
+ If parent is temporary, children must be temporary too and vice
+ versa. This check must be done for every child on every open because
+ the table def version can overlap between temporary and
+ non-temporary tables. We need to detect the case where a
+ non-temporary table has been replaced with a temporary table of the
+ same version. Or vice versa. A very unlikely case, but it could
+ happen.
+ */
+ 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;
+ goto err;
+ }
+
+ /* Extract the MyISAM table structure pointer from the handler object. */
+ if ((child->file->ht->db_type != DB_TYPE_MYISAM) ||
+ !(myisam= ((ha_myisam*) child->file)->file_ptr()))
+ {
+ 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",
+ (long) myisam, my_errno));
+
+ err:
+ DBUG_RETURN(my_errno ? NULL : myisam);
+}
+
+
+/**
+ @brief Open a MERGE parent table, not its children.
+
+ @detail This function initializes the MERGE storage engine structures
+ and adds a child list of TABLE_LIST to the parent TABLE.
+
+ @param[in] name MERGE table path name
+ @param[in] mode read/write mode, unused
+ @param[in] test_if_locked open flags
+
+ @return status
+ @retval 0 OK
+ @retval -1 Error, my_errno gives reason
+*/
+
+int ha_myisammrg::open(const char *name, int mode __attribute__((unused)),
+ uint test_if_locked)
+{
+ DBUG_ENTER("ha_myisammrg::open");
+ DBUG_PRINT("myrg", ("name: '%s' table: 0x%lx", name, (long) table));
+ DBUG_PRINT("myrg", ("test_if_locked: %u", test_if_locked));
+
+ /* Save for later use. */
+ this->test_if_locked= test_if_locked;
+
+ /* retrieve children table list. */
+ my_errno= 0;
+ if (!(file= myrg_parent_open(name, myisammrg_parent_open_callback, this)))
+ {
+ DBUG_PRINT("error", ("my_errno %d", my_errno));
+ DBUG_RETURN(my_errno ? my_errno : -1);
+ }
+ DBUG_PRINT("myrg", ("MYRG_INFO: 0x%lx", (long) file));
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief Attach children to a MERGE table.
+
+ @detail Let the storage engine attach its children through a callback
+ function. Check table definitions for consistency.
+
+ @note Special thd->open_options may be in effect. We can make use of
+ them in attach. I.e. we use HA_OPEN_FOR_REPAIR to report the names
+ of mismatching child tables. We cannot transport these options in
+ ha_myisammrg::test_if_locked because they may change after the
+ parent is opened. The parent is kept open in the table cache over
+ multiple statements and can be used by other threads. Open options
+ can change over time.
+
+ @return status
+ @retval 0 OK
+ @retval != 0 Error, my_errno gives reason
+*/
+
+int ha_myisammrg::attach_children(void)
{
- MI_KEYDEF *keyinfo;
- MI_COLUMNDEF *recinfo;
- MYRG_TABLE *u_table;
- uint recs;
- uint keys= table->s->keys;
- int error;
- char name_buff[FN_REFLEN];
+ MYRG_TABLE *u_table;
+ MI_COLUMNDEF *recinfo;
+ MI_KEYDEF *keyinfo;
+ uint recs;
+ uint keys= table->s->keys;
+ int error;
+ 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);
+
+ /*
+ Initialize variables that are used, modified, and/or set by
+ myisammrg_attach_children_callback().
+ 'next_child_attach' traverses the chain of TABLE_LIST objects
+ that has been compiled during myrg_parent_open(). Every call
+ to myisammrg_attach_children_callback() moves the pointer to
+ the next object.
+ 'need_compat_check' is set by myisammrg_attach_children_callback()
+ if a child fails the table def version check.
+ 'my_errno' is set by myisammrg_attach_children_callback() in
+ case of an error.
+ */
+ next_child_attach= table->child_l;
+ need_compat_check= FALSE;
+ my_errno= 0;
- DBUG_PRINT("info", ("ha_myisammrg::open"));
- if (!(file=myrg_open(fn_format(name_buff,name,"","",
- MY_UNPACK_FILENAME|MY_APPEND_EXT),
- mode, test_if_locked)))
+ if (myrg_attach_children(this->file, this->test_if_locked |
+ current_thd->open_options,
+ myisammrg_attach_children_callback, this))
{
- DBUG_PRINT("info", ("ha_myisammrg::open exit %d", my_errno));
- return (my_errno ? my_errno : -1);
+ DBUG_PRINT("error", ("my_errno %d", my_errno));
+ DBUG_RETURN(my_errno ? my_errno : -1);
}
- DBUG_PRINT("info", ("ha_myisammrg::open myrg_extrafunc..."));
+ DBUG_PRINT("myrg", ("calling myrg_extrafunc"));
myrg_extrafunc(file, query_cache_invalidate_by_MyISAM_filename_ref);
if (!(test_if_locked == HA_OPEN_WAIT_IF_LOCKED ||
test_if_locked == HA_OPEN_ABORT_IF_LOCKED))
@@ -116,69 +488,147 @@ int ha_myisammrg::open(const char *name, int mode, uint test_if_locked)
if (!(test_if_locked & HA_OPEN_WAIT_IF_LOCKED))
myrg_extra(file,HA_EXTRA_WAIT_LOCK,0);
- if (table->s->reclength != stats.mean_rec_length && stats.mean_rec_length)
- {
- DBUG_PRINT("error",("reclength: %lu mean_rec_length: %lu",
- table->s->reclength, stats.mean_rec_length));
- if (test_if_locked & HA_OPEN_FOR_REPAIR)
- myrg_print_wrong_table(file->open_tables->table->filename);
- error= HA_ERR_WRONG_MRG_TABLE_DEF;
- goto err;
- }
- if ((error= table2myisam(table, &keyinfo, &recinfo, &recs)))
- {
- /* purecov: begin inspected */
- DBUG_PRINT("error", ("Failed to convert TABLE object to MyISAM "
- "key and column definition"));
- goto err;
- /* purecov: end */
- }
- for (u_table= file->open_tables; u_table < file->end_table; u_table++)
+ /*
+ The compatibility check is required only if one or more children do
+ not match their table def version from the last check. This will
+ always happen at the first attach because the reference child def
+ version is initialized to 'undefined' at open.
+ */
+ DBUG_PRINT("myrg", ("need_compat_check: %d", need_compat_check));
+ if (need_compat_check)
{
- if (check_definition(keyinfo, recinfo, keys, recs,
- u_table->table->s->keyinfo, u_table->table->s->rec,
- u_table->table->s->base.keys,
- u_table->table->s->base.fields, false))
+ TABLE_LIST *child_l;
+
+ if (table->s->reclength != stats.mean_rec_length && stats.mean_rec_length)
{
- error= HA_ERR_WRONG_MRG_TABLE_DEF;
+ DBUG_PRINT("error",("reclength: %lu mean_rec_length: %lu",
+ table->s->reclength, stats.mean_rec_length));
if (test_if_locked & HA_OPEN_FOR_REPAIR)
- myrg_print_wrong_table(u_table->table->filename);
- else
+ myrg_print_wrong_table(file->open_tables->table->filename);
+ error= HA_ERR_WRONG_MRG_TABLE_DEF;
+ goto err;
+ }
+ /*
+ Both recinfo and keyinfo are allocated by my_multi_malloc(), thus
+ only recinfo must be freed.
+ */
+ if ((error= table2myisam(table, &keyinfo, &recinfo, &recs)))
+ {
+ /* purecov: begin inspected */
+ DBUG_PRINT("error", ("failed to convert TABLE object to MyISAM "
+ "key and column definition"));
+ goto err;
+ /* purecov: end */
+ }
+ for (u_table= file->open_tables; u_table < file->end_table; u_table++)
+ {
+ if (check_definition(keyinfo, recinfo, keys, recs,
+ u_table->table->s->keyinfo, u_table->table->s->rec,
+ u_table->table->s->base.keys,
+ u_table->table->s->base.fields, false))
{
- my_free((uchar*) recinfo, MYF(0));
- goto err;
+ DBUG_PRINT("error", ("table definition mismatch: '%s'",
+ u_table->table->filename));
+ error= HA_ERR_WRONG_MRG_TABLE_DEF;
+ if (!(this->test_if_locked & HA_OPEN_FOR_REPAIR))
+ {
+ my_free((uchar*) recinfo, MYF(0));
+ goto err;
+ }
+ myrg_print_wrong_table(u_table->table->filename);
}
}
+ my_free((uchar*) recinfo, MYF(0));
+ if (error == HA_ERR_WRONG_MRG_TABLE_DEF)
+ goto err;
+
+ /* All checks passed so far. Now update child def version. */
+ for (child_l= table->child_l; ; child_l= child_l->next_global)
+ {
+ child_l->set_child_def_version(
+ child_l->table->s->get_table_def_version());
+
+ if (&child_l->next_global == table->child_last_l)
+ break;
+ }
}
- my_free((uchar*) recinfo, MYF(0));
- if (error == HA_ERR_WRONG_MRG_TABLE_DEF)
- goto err;
#if !defined(BIG_TABLES) || SIZEOF_OFF_T == 4
/* Merge table has more than 2G rows */
if (table->s->crashed)
{
+ DBUG_PRINT("error", ("MERGE table marked crashed"));
error= HA_ERR_WRONG_MRG_TABLE_DEF;
goto err;
}
#endif
- return (0);
+ DBUG_RETURN(0);
+
err:
- myrg_close(file);
- file=0;
- return (my_errno= error);
+ myrg_detach_children(file);
+ DBUG_RETURN(my_errno= error);
}
+
+/**
+ @brief Detach all children from a MERGE table.
+
+ @note Detach must not touch the children in any way.
+ They may have been closed at ths point already.
+ All references to the children should be removed.
+
+ @return status
+ @retval 0 OK
+ @retval != 0 Error, my_errno gives reason
+*/
+
+int ha_myisammrg::detach_children(void)
+{
+ DBUG_ENTER("ha_myisammrg::detach_children");
+ DBUG_ASSERT(this->file && this->file->children_attached);
+
+ if (myrg_detach_children(this->file))
+ {
+ /* purecov: begin inspected */
+ DBUG_PRINT("error", ("my_errno %d", my_errno));
+ DBUG_RETURN(my_errno ? my_errno : -1);
+ /* purecov: end */
+ }
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief Close a MERGE parent table, not its children.
+
+ @note The children are expected to be closed separately by the caller.
+
+ @return status
+ @retval 0 OK
+ @retval != 0 Error, my_errno gives reason
+*/
+
int ha_myisammrg::close(void)
{
- return myrg_close(file);
+ int rc;
+ DBUG_ENTER("ha_myisammrg::close");
+ /*
+ Children must not be attached here. Unless the MERGE table has no
+ children. In this case children_attached is always true.
+ */
+ DBUG_ASSERT(!this->file->children_attached || !this->file->tables);
+ rc= myrg_close(file);
+ file= 0;
+ DBUG_RETURN(rc);
}
int ha_myisammrg::write_row(uchar * buf)
{
+ DBUG_ENTER("ha_myisammrg::write_row");
+ DBUG_ASSERT(this->file->children_attached);
ha_statistic_increment(&SSV::ha_write_count);
if (file->merge_insert_method == MERGE_INSERT_DISABLED || !file->tables)
- return (HA_ERR_TABLE_READONLY);
+ DBUG_RETURN(HA_ERR_TABLE_READONLY);
if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT)
table->timestamp_field->set_time();
@@ -186,13 +636,14 @@ int ha_myisammrg::write_row(uchar * buf)
{
int error;
if ((error= update_auto_increment()))
- return error;
+ DBUG_RETURN(error); /* purecov: inspected */
}
- return myrg_write(file,buf);
+ DBUG_RETURN(myrg_write(file,buf));
}
int ha_myisammrg::update_row(const uchar * old_data, uchar * new_data)
{
+ DBUG_ASSERT(this->file->children_attached);
ha_statistic_increment(&SSV::ha_update_count);
if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE)
table->timestamp_field->set_time();
@@ -201,6 +652,7 @@ int ha_myisammrg::update_row(const uchar * old_data, uchar * new_data)
int ha_myisammrg::delete_row(const uchar * buf)
{
+ DBUG_ASSERT(this->file->children_attached);
ha_statistic_increment(&SSV::ha_delete_count);
return myrg_delete(file,buf);
}
@@ -209,6 +661,7 @@ int ha_myisammrg::index_read_map(uchar * buf, const uchar * key,
key_part_map keypart_map,
enum ha_rkey_function find_flag)
{
+ DBUG_ASSERT(this->file->children_attached);
ha_statistic_increment(&SSV::ha_read_key_count);
int error=myrg_rkey(file,buf,active_index, key, keypart_map, find_flag);
table->status=error ? STATUS_NOT_FOUND: 0;
@@ -219,6 +672,7 @@ int ha_myisammrg::index_read_idx_map(uchar * buf, uint index, const uchar * key,
key_part_map keypart_map,
enum ha_rkey_function find_flag)
{
+ DBUG_ASSERT(this->file->children_attached);
ha_statistic_increment(&SSV::ha_read_key_count);
int error=myrg_rkey(file,buf,index, key, keypart_map, find_flag);
table->status=error ? STATUS_NOT_FOUND: 0;
@@ -228,6 +682,7 @@ int ha_myisammrg::index_read_idx_map(uchar * buf, uint index, const uchar * key,
int ha_myisammrg::index_read_last_map(uchar *buf, const uchar *key,
key_part_map keypart_map)
{
+ DBUG_ASSERT(this->file->children_attached);
ha_statistic_increment(&SSV::ha_read_key_count);
int error=myrg_rkey(file,buf,active_index, key, keypart_map,
HA_READ_PREFIX_LAST);
@@ -237,6 +692,7 @@ int ha_myisammrg::index_read_last_map(uchar *buf, const uchar *key,
int ha_myisammrg::index_next(uchar * buf)
{
+ DBUG_ASSERT(this->file->children_attached);
ha_statistic_increment(&SSV::ha_read_next_count);
int error=myrg_rnext(file,buf,active_index);
table->status=error ? STATUS_NOT_FOUND: 0;
@@ -245,6 +701,7 @@ int ha_myisammrg::index_next(uchar * buf)
int ha_myisammrg::index_prev(uchar * buf)
{
+ DBUG_ASSERT(this->file->children_attached);
ha_statistic_increment(&SSV::ha_read_prev_count);
int error=myrg_rprev(file,buf, active_index);
table->status=error ? STATUS_NOT_FOUND: 0;
@@ -253,6 +710,7 @@ int ha_myisammrg::index_prev(uchar * buf)
int ha_myisammrg::index_first(uchar * buf)
{
+ DBUG_ASSERT(this->file->children_attached);
ha_statistic_increment(&SSV::ha_read_first_count);
int error=myrg_rfirst(file, buf, active_index);
table->status=error ? STATUS_NOT_FOUND: 0;
@@ -261,6 +719,7 @@ int ha_myisammrg::index_first(uchar * buf)
int ha_myisammrg::index_last(uchar * buf)
{
+ DBUG_ASSERT(this->file->children_attached);
ha_statistic_increment(&SSV::ha_read_last_count);
int error=myrg_rlast(file, buf, active_index);
table->status=error ? STATUS_NOT_FOUND: 0;
@@ -271,6 +730,7 @@ int ha_myisammrg::index_next_same(uchar * buf,
const uchar *key __attribute__((unused)),
uint length __attribute__((unused)))
{
+ DBUG_ASSERT(this->file->children_attached);
ha_statistic_increment(&SSV::ha_read_next_count);
int error=myrg_rnext_same(file,buf);
table->status=error ? STATUS_NOT_FOUND: 0;
@@ -280,12 +740,14 @@ int ha_myisammrg::index_next_same(uchar * buf,
int ha_myisammrg::rnd_init(bool scan)
{
+ DBUG_ASSERT(this->file->children_attached);
return myrg_reset(file);
}
int ha_myisammrg::rnd_next(uchar *buf)
{
+ DBUG_ASSERT(this->file->children_attached);
ha_statistic_increment(&SSV::ha_read_rnd_next_count);
int error=myrg_rrnd(file, buf, HA_OFFSET_ERROR);
table->status=error ? STATUS_NOT_FOUND: 0;
@@ -295,6 +757,7 @@ int ha_myisammrg::rnd_next(uchar *buf)
int ha_myisammrg::rnd_pos(uchar * buf, uchar *pos)
{
+ DBUG_ASSERT(this->file->children_attached);
ha_statistic_increment(&SSV::ha_read_rnd_count);
int error=myrg_rrnd(file, buf, my_get_ptr(pos,ref_length));
table->status=error ? STATUS_NOT_FOUND: 0;
@@ -303,6 +766,7 @@ int ha_myisammrg::rnd_pos(uchar * buf, uchar *pos)
void ha_myisammrg::position(const uchar *record)
{
+ DBUG_ASSERT(this->file->children_attached);
ulonglong row_position= myrg_position(file);
my_store_ptr(ref, ref_length, (my_off_t) row_position);
}
@@ -311,6 +775,7 @@ void ha_myisammrg::position(const uchar *record)
ha_rows ha_myisammrg::records_in_range(uint inx, key_range *min_key,
key_range *max_key)
{
+ DBUG_ASSERT(this->file->children_attached);
return (ha_rows) myrg_records_in_range(file, (int) inx, min_key, max_key);
}
@@ -318,6 +783,7 @@ ha_rows ha_myisammrg::records_in_range(uint inx, key_range *min_key,
int ha_myisammrg::info(uint flag)
{
MYMERGE_INFO mrg_info;
+ DBUG_ASSERT(this->file->children_attached);
(void) myrg_status(file,&mrg_info,flag);
/*
The following fails if one has not compiled MySQL with -DBIG_TABLES
@@ -387,6 +853,23 @@ int ha_myisammrg::info(uint flag)
int ha_myisammrg::extra(enum ha_extra_function operation)
{
+ if (operation == HA_EXTRA_ATTACH_CHILDREN)
+ {
+ int rc= attach_children();
+ if (!rc)
+ (void) extra(HA_EXTRA_NO_READCHECK); // Not needed in SQL
+ return(rc);
+ }
+ else if (operation == HA_EXTRA_DETACH_CHILDREN)
+ {
+ /*
+ Note that detach must not touch the children in any way.
+ They may have been closed at ths point already.
+ */
+ int rc= detach_children();
+ return(rc);
+ }
+
/* As this is just a mapping, we don't have to force the underlying
tables to be closed */
if (operation == HA_EXTRA_FORCE_REOPEN ||
@@ -404,6 +887,7 @@ int ha_myisammrg::reset(void)
int ha_myisammrg::extra_opt(enum ha_extra_function operation, ulong cache_size)
{
+ DBUG_ASSERT(this->file->children_attached);
if ((specialflag & SPECIAL_SAFE_MODE) && operation == HA_EXTRA_WRITE_CACHE)
return 0;
return myrg_extra(file, operation, (void*) &cache_size);
@@ -411,11 +895,13 @@ int ha_myisammrg::extra_opt(enum ha_extra_function operation, ulong cache_size)
int ha_myisammrg::external_lock(THD *thd, int lock_type)
{
+ DBUG_ASSERT(this->file->children_attached);
return myrg_lock_database(file,lock_type);
}
uint ha_myisammrg::lock_count(void) const
{
+ DBUG_ASSERT(this->file->children_attached);
return file->tables;
}
@@ -425,6 +911,7 @@ THR_LOCK_DATA **ha_myisammrg::store_lock(THD *thd,
enum thr_lock_type lock_type)
{
MYRG_TABLE *open_table;
+ DBUG_ASSERT(this->file->children_attached);
for (open_table=file->open_tables ;
open_table != file->end_table ;
@@ -519,47 +1006,50 @@ int ha_myisammrg::create(const char *name, register TABLE *form,
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); /* purecov: inspected */
+
*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),
@@ -642,7 +1132,7 @@ static int myisammrg_init(void *p)
myisammrg_hton->db_type= DB_TYPE_MRG_MYISAM;
myisammrg_hton->create= myisammrg_create_handler;
myisammrg_hton->panic= myisammrg_panic;
- myisammrg_hton->flags= HTON_CAN_RECREATE|HTON_NO_PARTITION;
+ myisammrg_hton->flags= HTON_NO_PARTITION;
return 0;
}
diff --git a/storage/myisammrg/ha_myisammrg.h b/storage/myisammrg/ha_myisammrg.h
index 91aabe277f7..977c45d1435 100644
--- a/storage/myisammrg/ha_myisammrg.h
+++ b/storage/myisammrg/ha_myisammrg.h
@@ -27,8 +27,12 @@ class ha_myisammrg: public handler
MYRG_INFO *file;
public:
+ TABLE_LIST *next_child_attach; /* next child to attach */
+ uint test_if_locked; /* flags from ::open() */
+ bool need_compat_check; /* if need compatibility check */
+
ha_myisammrg(handlerton *hton, TABLE_SHARE *table_arg);
- ~ha_myisammrg() {}
+ ~ha_myisammrg();
const char *table_type() const { return "MRG_MyISAM"; }
const char **bas_ext() const;
const char *index_type(uint key_number);
@@ -53,6 +57,8 @@ class ha_myisammrg: public handler
{ return ulonglong2double(stats.data_file_length) / IO_SIZE + file->tables; }
int open(const char *name, int mode, uint test_if_locked);
+ int attach_children(void);
+ int detach_children(void);
int close(void);
int write_row(uchar * buf);
int update_row(const uchar * old_data, uchar * new_data);
@@ -85,6 +91,7 @@ class ha_myisammrg: public handler
void update_create_info(HA_CREATE_INFO *create_info);
void append_create_info(String *packet);
MYRG_INFO *myrg_info() { return file; }
+ TABLE *table_ptr() { return table; }
bool check_if_incompatible_data(HA_CREATE_INFO *info, uint table_changes);
int check(THD* thd, HA_CHECK_OPT* check_opt);
};
diff --git a/storage/myisammrg/myrg_close.c b/storage/myisammrg/myrg_close.c
index baae24634b3..b402a35a253 100644
--- a/storage/myisammrg/myrg_close.c
+++ b/storage/myisammrg/myrg_close.c
@@ -23,9 +23,37 @@ int myrg_close(MYRG_INFO *info)
MYRG_TABLE *file;
DBUG_ENTER("myrg_close");
- for (file=info->open_tables ; file != info->end_table ; file++)
- if ((new_error=mi_close(file->table)))
- error=new_error;
+ /*
+ Assume that info->children_attached means that this is called from
+ direct use of MERGE, not from a MySQL server. In this case the
+ children must be closed and info->rec_per_key_part is part of the
+ 'info' multi_alloc.
+ If info->children_attached is false, this is called from a MySQL
+ server. Children are closed independently but info->rec_per_key_part
+ must be freed.
+ Just in case of a server panic (myrg_panic()) info->children_attached
+ 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)
+ {
+ for (file= info->open_tables; file != info->end_table; file++)
+ {
+ /* purecov: begin inspected */
+ if ((new_error= mi_close(file->table)))
+ error= new_error;
+ else
+ file->table= NULL;
+ /* purecov: end */
+ }
+ }
+ else
+ my_free((uchar*) info->rec_per_key_part, MYF(MY_ALLOW_ZERO_PTR));
delete_queue(&info->by_key);
pthread_mutex_lock(&THR_LOCK_open);
myrg_open_list=list_delete(myrg_open_list,&info->open_list);
diff --git a/storage/myisammrg/myrg_extra.c b/storage/myisammrg/myrg_extra.c
index a1e6e3f8d4d..3d14f6a56e6 100644
--- a/storage/myisammrg/myrg_extra.c
+++ b/storage/myisammrg/myrg_extra.c
@@ -29,6 +29,8 @@ int myrg_extra(MYRG_INFO *info,enum ha_extra_function function,
DBUG_ENTER("myrg_extra");
DBUG_PRINT("info",("function: %lu", (ulong) function));
+ if (!info->children_attached)
+ DBUG_RETURN(1);
if (function == HA_EXTRA_CACHE)
{
info->cache_in_use=1;
@@ -73,6 +75,8 @@ int myrg_reset(MYRG_INFO *info)
MYRG_TABLE *file;
DBUG_ENTER("myrg_reset");
+ if (!info->children_attached)
+ DBUG_RETURN(1);
info->cache_in_use=0;
info->current_table=0;
info->last_used_table= info->open_tables;
diff --git a/storage/myisammrg/myrg_open.c b/storage/myisammrg/myrg_open.c
index 500d3a29327..6fb1888f84d 100644
--- a/storage/myisammrg/myrg_open.c
+++ b/storage/myisammrg/myrg_open.c
@@ -26,8 +26,14 @@
open a MyISAM MERGE table
if handle_locking is 0 then exit with error if some table is locked
if handle_locking is 1 then wait if table is locked
-*/
+ NOTE: This function is not used in the MySQL server. It is for
+ MERGE use independent from MySQL. Currently there is some code
+ duplication between myrg_open() and myrg_parent_open() +
+ myrg_attach_children(). Please duplicate changes in these
+ functions or make common sub-functions.
+*/
+/* purecov: begin unused */
MYRG_INFO *myrg_open(const char *name, int mode, int handle_locking)
{
@@ -107,13 +113,11 @@ MYRG_INFO *myrg_open(const char *name, int mode, int handle_locking)
key_parts*sizeof(long),
MYF(MY_WME|MY_ZEROFILL))))
goto err;
- if (files)
- {
- m_info->open_tables=(MYRG_TABLE *) (m_info+1);
- m_info->rec_per_key_part=(ulong *) (m_info->open_tables+files);
- m_info->tables= files;
- files= 0;
- }
+ DBUG_ASSERT(files);
+ m_info->open_tables=(MYRG_TABLE *) (m_info+1);
+ m_info->rec_per_key_part=(ulong *) (m_info->open_tables+files);
+ m_info->tables= files;
+ files= 0;
m_info->reclength=isam->s->base.reclength;
min_keys= isam->s->base.keys;
errpos=3;
@@ -163,6 +167,7 @@ MYRG_INFO *myrg_open(const char *name, int mode, int handle_locking)
/* this works ok if the table list is empty */
m_info->end_table=m_info->open_tables+files;
m_info->last_used_table=m_info->open_tables;
+ m_info->children_attached= TRUE;
VOID(my_close(fd,MYF(0)));
end_io_cache(&file);
@@ -189,3 +194,316 @@ err:
my_errno=save_errno;
DBUG_RETURN (NULL);
}
+/* purecov: end */
+
+
+/**
+ @brief Open parent table of a MyISAM MERGE table.
+
+ @detail Open MERGE meta file to get the table name paths for the child
+ tables. Count the children. Allocate and initialize MYRG_INFO
+ structure. Call a callback function for each child table.
+
+ @param[in] parent_name merge table name path as "database/table"
+ @param[in] callback function to call for each child table
+ @param[in] callback_param data pointer to give to the callback
+
+ @return MYRG_INFO pointer
+ @retval != NULL OK
+ @retval NULL Error
+
+ @note: Currently there is some code duplication between myrg_open()
+ and myrg_parent_open() + myrg_attach_children(). Please duplicate
+ changes in these functions or make common sub-functions.
+*/
+
+MYRG_INFO *myrg_parent_open(const char *parent_name,
+ int (*callback)(void*, const char*),
+ void *callback_param)
+{
+ MYRG_INFO *m_info;
+ int rc;
+ int errpos;
+ int save_errno;
+ int insert_method;
+ uint length;
+ uint dir_length;
+ uint child_count;
+ size_t name_buff_length;
+ File fd;
+ IO_CACHE file_cache;
+ char parent_name_buff[FN_REFLEN * 2];
+ char child_name_buff[FN_REFLEN];
+ DBUG_ENTER("myrg_parent_open");
+
+ rc= 1;
+ errpos= 0;
+ bzero((char*) &file_cache, sizeof(file_cache));
+
+ /* Open MERGE meta file. */
+ if ((fd= my_open(fn_format(parent_name_buff, parent_name, "", MYRG_NAME_EXT,
+ MY_UNPACK_FILENAME|MY_APPEND_EXT),
+ O_RDONLY | O_SHARE, MYF(0))) < 0)
+ goto err; /* purecov: inspected */
+ errpos= 1;
+
+ if (init_io_cache(&file_cache, fd, 4 * IO_SIZE, READ_CACHE, 0, 0,
+ MYF(MY_WME | MY_NABP)))
+ goto err; /* purecov: inspected */
+ errpos= 2;
+
+ /* Count children. Determine insert method. */
+ child_count= 0;
+ insert_method= 0;
+ while ((length= my_b_gets(&file_cache, child_name_buff, FN_REFLEN - 1)))
+ {
+ /* Remove line terminator. */
+ if (child_name_buff[length - 1] == '\n')
+ child_name_buff[--length]= '\0';
+
+ /* Skip empty lines. */
+ if (!child_name_buff[0])
+ continue; /* purecov: inspected */
+
+ /* Skip comments, but evaluate insert method. */
+ if (child_name_buff[0] == '#')
+ {
+ if (!strncmp(child_name_buff + 1, "INSERT_METHOD=", 14))
+ {
+ /* Compare buffer with global methods list: merge_insert_method. */
+ insert_method= find_type(child_name_buff + 15,
+ &merge_insert_method, 2);
+ }
+ continue;
+ }
+
+ /* Count the child. */
+ child_count++;
+ }
+
+ /* Allocate MERGE parent table structure. */
+ if (!(m_info= (MYRG_INFO*) my_malloc(sizeof(MYRG_INFO) +
+ child_count * sizeof(MYRG_TABLE),
+ MYF(MY_WME | MY_ZEROFILL))))
+ goto err; /* purecov: inspected */
+ errpos= 3;
+ m_info->open_tables= (MYRG_TABLE*) (m_info + 1);
+ m_info->tables= child_count;
+ 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);
+ my_b_seek(&file_cache, 0);
+ while ((length= my_b_gets(&file_cache, child_name_buff, FN_REFLEN - 1)))
+ {
+ /* Remove line terminator. */
+ if (child_name_buff[length - 1] == '\n')
+ child_name_buff[--length]= '\0';
+
+ /* Skip empty lines and comments. */
+ if (!child_name_buff[0] || (child_name_buff[0] == '#'))
+ continue;
+
+ if (!has_path(child_name_buff))
+ {
+ VOID(strmake(parent_name_buff + dir_length, child_name_buff,
+ sizeof(parent_name_buff) - 1 - dir_length));
+ VOID(cleanup_dirname(child_name_buff, parent_name_buff));
+ }
+ else
+ fn_format(child_name_buff, child_name_buff, "", "", 0);
+ DBUG_PRINT("info", ("child: '%s'", child_name_buff));
+
+ /* Callback registers child with handler table. */
+ if ((rc= (*callback)(callback_param, child_name_buff)))
+ goto err; /* purecov: inspected */
+ }
+
+ end_io_cache(&file_cache);
+ VOID(my_close(fd, MYF(0)));
+
+ m_info->open_list.data= (void*) m_info;
+ pthread_mutex_lock(&THR_LOCK_open);
+ myrg_open_list= list_add(myrg_open_list, &m_info->open_list);
+ pthread_mutex_unlock(&THR_LOCK_open);
+
+ DBUG_RETURN(m_info);
+
+ /* purecov: begin inspected */
+ err:
+ save_errno= my_errno;
+ switch (errpos) {
+ case 3:
+ my_free((char*) m_info, MYF(0));
+ /* Fall through */
+ case 2:
+ end_io_cache(&file_cache);
+ /* Fall through */
+ case 1:
+ VOID(my_close(fd, MYF(0)));
+ }
+ my_errno= save_errno;
+ DBUG_RETURN (NULL);
+ /* purecov: end */
+}
+
+
+/**
+ @brief Attach children to a MyISAM MERGE parent table.
+
+ @detail Call a callback function for each child table.
+ The callback returns the MyISAM table handle of the child table.
+ Check table definition match.
+
+ @param[in] m_info MERGE parent table structure
+ @param[in] handle_locking if contains HA_OPEN_FOR_REPAIR, warn about
+ incompatible child tables, but continue
+ @param[in] callback function to call for each child table
+ @param[in] callback_param data pointer to give to the callback
+
+ @return status
+ @retval 0 OK
+ @retval != 0 Error
+
+ @note: Currently there is some code duplication between myrg_open()
+ and myrg_parent_open() + myrg_attach_children(). Please duplicate
+ changes in these functions or make common sub-functions.
+*/
+
+int myrg_attach_children(MYRG_INFO *m_info, int handle_locking,
+ MI_INFO *(*callback)(void*),
+ void *callback_param)
+{
+ ulonglong file_offset;
+ MI_INFO *myisam;
+ int rc;
+ int errpos;
+ int save_errno;
+ uint idx;
+ uint child_nr;
+ uint key_parts;
+ uint min_keys;
+ DBUG_ENTER("myrg_attach_children");
+ DBUG_PRINT("myrg", ("handle_locking: %d", handle_locking));
+
+ rc= 1;
+ errpos= 0;
+ file_offset= 0;
+ LINT_INIT(key_parts);
+ min_keys= 0;
+ 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. */
+ if (!child_nr)
+ {
+ m_info->reclength= myisam->s->base.reclength;
+ min_keys= myisam->s->base.keys;
+ key_parts= myisam->s->base.key_parts;
+ if (!m_info->rec_per_key_part)
+ {
+ if(!(m_info->rec_per_key_part= (ulong*)
+ my_malloc(key_parts * sizeof(long), MYF(MY_WME|MY_ZEROFILL))))
+ goto err; /* purecov: inspected */
+ errpos= 1;
+ }
+ }
+
+ /* Add MyISAM table info. */
+ m_info->open_tables[child_nr].table= myisam;
+ m_info->open_tables[child_nr].file_offset= (my_off_t) file_offset;
+ file_offset+= myisam->state->data_file_length;
+
+ /* 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)
+ {
+ myrg_print_wrong_table(myisam->filename);
+ continue;
+ }
+ goto err;
+ }
+
+ m_info->options|= myisam->s->options;
+ m_info->records+= myisam->state->records;
+ m_info->del+= myisam->state->del;
+ m_info->data_file_length+= myisam->state->data_file_length;
+ if (min_keys > myisam->s->base.keys)
+ min_keys= myisam->s->base.keys; /* purecov: inspected */
+ for (idx= 0; idx < key_parts; idx++)
+ m_info->rec_per_key_part[idx]+= (myisam->s->state.rec_per_key_part[idx] /
+ m_info->tables);
+ child_nr++;
+ }
+
+ if (my_errno == HA_ERR_WRONG_MRG_TABLE_DEF)
+ goto err;
+ if (sizeof(my_off_t) == 4 && file_offset > (ulonglong) (ulong) ~0L)
+ {
+ my_errno= HA_ERR_RECORD_FILE_FULL;
+ goto err;
+ }
+ /* Don't mark table readonly, for ALTER TABLE ... UNION=(...) to work */
+ m_info->options&= ~(HA_OPTION_COMPRESS_RECORD | HA_OPTION_READ_ONLY_DATA);
+ m_info->keys= min_keys;
+ m_info->last_used_table= m_info->open_tables;
+ m_info->children_attached= TRUE;
+ DBUG_RETURN(0);
+
+err:
+ save_errno= my_errno;
+ 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);
+}
+
+
+/**
+ @brief Detach children from a MyISAM MERGE parent table.
+
+ @param[in] m_info MERGE parent table structure
+
+ @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.
+
+ @return status
+ @retval 0 OK
+*/
+
+int myrg_detach_children(MYRG_INFO *m_info)
+{
+ DBUG_ENTER("myrg_detach_children");
+ 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;
+ m_info->options= 0;
+ DBUG_RETURN(0);
+}
+