diff options
author | unknown <istruewing@stella.local> | 2007-11-15 20:25:43 +0100 |
---|---|---|
committer | unknown <istruewing@stella.local> | 2007-11-15 20:25:43 +0100 |
commit | c8450b278d7f049e0afa49624c52e0f61b541126 (patch) | |
tree | 69cf3030a364a25215179c5f289ef3804553bfc8 /sql | |
parent | 62d3c3d3a1c7ceed7170913189dd0e4437f2d37e (diff) | |
download | mariadb-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
Diffstat (limited to 'sql')
-rw-r--r-- | sql/ha_ndbcluster_binlog.cc | 2 | ||||
-rw-r--r-- | sql/handler.cc | 11 | ||||
-rw-r--r-- | sql/mysql_priv.h | 24 | ||||
-rw-r--r-- | sql/slave.cc | 6 | ||||
-rw-r--r-- | sql/sql_base.cc | 1072 | ||||
-rw-r--r-- | sql/sql_insert.cc | 14 | ||||
-rw-r--r-- | sql/sql_parse.cc | 73 | ||||
-rw-r--r-- | sql/sql_partition.cc | 2 | ||||
-rw-r--r-- | sql/sql_select.cc | 2 | ||||
-rw-r--r-- | sql/sql_table.cc | 80 | ||||
-rw-r--r-- | sql/sql_trigger.cc | 44 | ||||
-rw-r--r-- | sql/table.cc | 29 | ||||
-rw-r--r-- | sql/table.h | 33 |
13 files changed, 1258 insertions, 134 deletions
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, ¬_used))) + MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN, ¬_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; |