summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mysql-test/r/myisam-metadata.result12
-rw-r--r--mysql-test/t/myisam-metadata.test49
-rw-r--r--sql/mysqld.cc2
-rw-r--r--sql/sql_admin.cc8
-rw-r--r--sql/sql_base.cc27
-rw-r--r--sql/sql_base.h8
-rw-r--r--sql/sql_table.cc19
-rw-r--r--sql/sql_truncate.cc3
-rw-r--r--sql/table.cc2
-rw-r--r--sql/table.h23
10 files changed, 131 insertions, 22 deletions
diff --git a/mysql-test/r/myisam-metadata.result b/mysql-test/r/myisam-metadata.result
new file mode 100644
index 00000000000..5192253d5d1
--- /dev/null
+++ b/mysql-test/r/myisam-metadata.result
@@ -0,0 +1,12 @@
+DROP TABLE IF EXISTS t1;
+CREATE TABLE t1 (
+id INT PRIMARY KEY,
+a VARCHAR(100),
+INDEX(a)
+) ENGINE=MyISAM;
+ALTER TABLE t1 DISABLE KEYS;
+ALTER TABLE t1 ENABLE KEYS;
+SHOW TABLE STATUS LIKE 't1';
+Name Engine Version Row_format Rows Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment Create_time Update_time Check_time Collation Checksum Create_options Comment
+t1 MyISAM 10 Dynamic 100000 27 # # # 0 NULL # # # latin1_swedish_ci NULL
+DROP TABLE t1;
diff --git a/mysql-test/t/myisam-metadata.test b/mysql-test/t/myisam-metadata.test
new file mode 100644
index 00000000000..c5327aa3a71
--- /dev/null
+++ b/mysql-test/t/myisam-metadata.test
@@ -0,0 +1,49 @@
+#
+# Test bugs in MyISAM that may cause problems for metadata
+#
+
+--source include/big_test.inc
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+#
+# LP:989055 - Querying myisam table metadata may corrupt the table
+#
+
+CREATE TABLE t1 (
+ id INT PRIMARY KEY,
+ a VARCHAR(100),
+ INDEX(a)
+) ENGINE=MyISAM;
+ALTER TABLE t1 DISABLE KEYS;
+
+let $1=100000;
+--disable_query_log
+while ($1)
+{
+ eval insert into t1 values($1, "line number $1");
+ dec $1;
+}
+--enable_query_log
+
+--connect(con1,localhost,root,,)
+--send
+ ALTER TABLE t1 ENABLE KEYS;
+
+--connection default
+--let $wait_timeout=10
+--let $show_statement= SHOW PROCESSLIST
+--let $field= State
+--let $condition= = 'Repair by sorting'
+--source include/wait_show_condition.inc
+
+--replace_column 7 # 8 # 9 # 12 # 13 # 14 #
+SHOW TABLE STATUS LIKE 't1';
+
+--connection con1
+--reap
+--connection default
+--disconnect con1
+DROP TABLE t1;
diff --git a/sql/mysqld.cc b/sql/mysqld.cc
index 14737cd69bb..c4bc8c62391 100644
--- a/sql/mysqld.cc
+++ b/sql/mysqld.cc
@@ -7300,7 +7300,7 @@ static int mysql_init_variables(void)
log_error_file_ptr= log_error_file;
protocol_version= PROTOCOL_VERSION;
what_to_log= ~ (1L << (uint) COM_TIME);
- refresh_version= 1L; /* Increments on each reload */
+ refresh_version= 2L; /* Increments on each reload. 0 and 1 are reserved */
executed_events= 0;
global_query_id= thread_id= 1L;
my_atomic_rwlock_init(&global_query_id_lock);
diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc
index c65e56edbe0..9f6fe9ee62b 100644
--- a/sql/sql_admin.cc
+++ b/sql/sql_admin.cc
@@ -87,6 +87,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
const char **ext;
MY_STAT stat_info;
Open_table_context ot_ctx(thd, (MYSQL_OPEN_IGNORE_FLUSH |
+ MYSQL_OPEN_FOR_REPAIR |
MYSQL_OPEN_HAS_MDL_LOCK |
MYSQL_LOCK_IGNORE_TIMEOUT));
DBUG_ENTER("prepare_for_repair");
@@ -197,7 +198,8 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
*/
pos_in_locked_tables= table->pos_in_locked_tables;
if (wait_while_table_is_used(thd, table,
- HA_EXTRA_PREPARE_FOR_FORCED_CLOSE))
+ HA_EXTRA_PREPARE_FOR_FORCED_CLOSE,
+ TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE))
goto end;
/* Close table but don't remove from locked list */
close_all_tables_for_name(thd, table_list->table->s,
@@ -589,8 +591,10 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
*/
if (lock_type == TL_WRITE && !table->table->s->tmp_table)
{
+ table->table->s->protect_against_usage();
if (wait_while_table_is_used(thd, table->table,
- HA_EXTRA_PREPARE_FOR_RENAME))
+ HA_EXTRA_PREPARE_FOR_RENAME,
+ TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE))
goto err;
DEBUG_SYNC(thd, "after_admin_flush");
/* Flush entries in the query cache involving this table. */
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index c76f2d43279..327e08ff92f 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -1074,7 +1074,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables,
if (share)
{
kill_delayed_threads_for_table(share);
- /* tdc_remove_table() also sets TABLE_SHARE::version to 0. */
+ /* tdc_remove_table() calls share->remove_from_cache_at_close() */
tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, table->db,
table->table_name, TRUE);
found=1;
@@ -2333,7 +2333,8 @@ bool rename_temporary_table(THD* thd, TABLE *table, const char *db,
*/
bool wait_while_table_is_used(THD *thd, TABLE *table,
- enum ha_extra_function function)
+ enum ha_extra_function function,
+ enum_tdc_remove_table_type remove_type)
{
DBUG_ENTER("wait_while_table_is_used");
DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu",
@@ -2344,7 +2345,7 @@ bool wait_while_table_is_used(THD *thd, TABLE *table,
table->mdl_ticket, thd->variables.lock_wait_timeout))
DBUG_RETURN(TRUE);
- tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN,
+ tdc_remove_table(thd, remove_type,
table->s->db.str, table->s->table_name.str,
FALSE);
/* extra() call must come only after all instances above are closed */
@@ -3090,7 +3091,9 @@ retry_share:
goto err_unlock;
}
- if (!(flags & MYSQL_OPEN_IGNORE_FLUSH))
+ if (!(flags & MYSQL_OPEN_IGNORE_FLUSH) ||
+ (share->protected_against_usage() &&
+ !(flags & MYSQL_OPEN_FOR_REPAIR)))
{
if (share->has_old_version())
{
@@ -9371,6 +9374,7 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
TABLE *table;
TABLE_SHARE *share;
DBUG_ENTER("tdc_remove_table");
+ DBUG_PRINT("enter",("name: %s remove_type: %d", table_name, remove_type));
if (! has_lock)
mysql_mutex_lock(&LOCK_open);
@@ -9396,7 +9400,8 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
{
DBUG_ASSERT(share->used_tables.is_empty());
}
- else if (remove_type == TDC_RT_REMOVE_NOT_OWN)
+ else if (remove_type == TDC_RT_REMOVE_NOT_OWN ||
+ remove_type == TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE)
{
I_P_List_iterator<TABLE, TABLE_share> it2(share->used_tables);
while ((table= it2++))
@@ -9407,8 +9412,8 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
}
#endif
/*
- Set share's version to zero in order to ensure that it gets
- automatically deleted once it is no longer referenced.
+ Mark share to ensure that it gets automatically deleted once
+ it is no longer referenced.
Note that code in TABLE_SHARE::wait_for_old_version() assumes
that marking share as old and removal of its unused tables
@@ -9417,7 +9422,13 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
TDC does not contain old shares which don't have any tables
used.
*/
- share->version= 0;
+ if (remove_type == TDC_RT_REMOVE_NOT_OWN)
+ share->remove_from_cache_at_close();
+ else
+ {
+ /* Ensure that no can open the table while it's used */
+ share->protect_against_usage();
+ }
while ((table= it++))
free_cache_entry(table);
diff --git a/sql/sql_base.h b/sql/sql_base.h
index 45d41777fea..2a39c67b350 100644
--- a/sql/sql_base.h
+++ b/sql/sql_base.h
@@ -60,7 +60,8 @@ enum find_item_error_report_type {REPORT_ALL_ERRORS, REPORT_EXCEPT_NOT_FOUND,
IGNORE_EXCEPT_NON_UNIQUE};
enum enum_tdc_remove_table_type {TDC_RT_REMOVE_ALL, TDC_RT_REMOVE_NOT_OWN,
- TDC_RT_REMOVE_UNUSED};
+ TDC_RT_REMOVE_UNUSED,
+ TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE};
/* bits for last argument to remove_table_from_cache() */
#define RTFC_NO_FLAG 0x0000
@@ -128,6 +129,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update,
*/
#define MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK 0x1000
#define MYSQL_LOCK_NOT_TEMPORARY 0x2000
+#define MYSQL_OPEN_FOR_REPAIR 0x4000
/** Please refer to the internals manual. */
#define MYSQL_OPEN_REOPEN (MYSQL_OPEN_IGNORE_FLUSH |\
@@ -229,7 +231,9 @@ bool setup_tables_and_check_access(THD *thd,
ulong want_access,
bool full_table_list);
bool wait_while_table_is_used(THD *thd, TABLE *table,
- enum ha_extra_function function);
+ enum ha_extra_function function,
+ enum_tdc_remove_table_type remove_type=
+ TDC_RT_REMOVE_NOT_OWN);
void drop_open_table(THD *thd, TABLE *table, const char *db_name,
const char *table_name);
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
index 330c28ebbd8..fed05c5902e 100644
--- a/sql/sql_table.cc
+++ b/sql/sql_table.cc
@@ -2190,7 +2190,8 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists,
if (thd->locked_tables_mode)
{
- if (wait_while_table_is_used(thd, table->table, HA_EXTRA_NOT_USED))
+ if (wait_while_table_is_used(thd, table->table, HA_EXTRA_NOT_USED,
+ TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE))
{
error= -1;
goto err;
@@ -6237,13 +6238,15 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
case LEAVE_AS_IS:
break;
case ENABLE:
- if (wait_while_table_is_used(thd, table, extra_func))
+ if (wait_while_table_is_used(thd, table, extra_func,
+ TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE))
goto err;
DEBUG_SYNC(thd,"alter_table_enable_indexes");
error= table->file->ha_enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
break;
case DISABLE:
- if (wait_while_table_is_used(thd, table, extra_func))
+ if (wait_while_table_is_used(thd, table, extra_func,
+ TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE))
goto err;
error=table->file->ha_disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
break;
@@ -6271,7 +6274,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
simple rename did nothing and therefore we can safely return
without additional clean-up.
*/
- if (wait_while_table_is_used(thd, table, extra_func))
+ if (wait_while_table_is_used(thd, table, extra_func,
+ TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE))
goto err;
close_all_tables_for_name(thd, table->s, HA_EXTRA_PREPARE_FOR_RENAME);
/*
@@ -6704,6 +6708,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
if (table->s->tmp_table)
{
Open_table_context ot_ctx(thd, (MYSQL_OPEN_IGNORE_FLUSH |
+ MYSQL_OPEN_FOR_REPAIR |
MYSQL_LOCK_IGNORE_TIMEOUT));
TABLE_LIST tbl;
bzero((void*) &tbl, sizeof(tbl));
@@ -6776,7 +6781,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
table->file->indexes_are_disabled())
need_lock_for_indexes= true;
if (!table->s->tmp_table && need_lock_for_indexes &&
- wait_while_table_is_used(thd, table, extra_func))
+ wait_while_table_is_used(thd, table, extra_func,
+ TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE))
goto err_new_table_cleanup;
thd_proc_info(thd, "manage keys");
DEBUG_SYNC(thd, "alter_table_manage_keys");
@@ -6996,7 +7002,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
if (lower_case_table_names)
my_casedn_str(files_charset_info, old_name);
- if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME))
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME,
+ TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE))
{
if (pending_inplace_add_index)
{
diff --git a/sql/sql_truncate.cc b/sql/sql_truncate.cc
index 4b77344c042..d47fb24eaba 100644
--- a/sql/sql_truncate.cc
+++ b/sql/sql_truncate.cc
@@ -364,7 +364,8 @@ bool Truncate_statement::lock_table(THD *thd, TABLE_LIST *table_ref,
{
DEBUG_SYNC(thd, "upgrade_lock_for_truncate");
/* To remove the table from the cache we need an exclusive lock. */
- if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_DROP))
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_DROP,
+ TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE))
DBUG_RETURN(TRUE);
m_ticket_downgrade= table->mdl_ticket;
/* Close if table is going to be recreated. */
diff --git a/sql/table.cc b/sql/table.cc
index fba45f421ed..4215f667618 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -323,7 +323,7 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key,
share->normalized_path.str= share->path.str;
share->normalized_path.length= path_length;
- share->version= refresh_version;
+ share->set_refresh_version();
/*
Since alloc_table_share() can be called without any locking (for
diff --git a/sql/table.h b/sql/table.h
index c21b01f53f7..eaea0fe65e5 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -793,12 +793,33 @@ struct TABLE_SHARE
return table_map_id;
}
-
/** Is this table share being expelled from the table definition cache? */
inline bool has_old_version() const
{
return version != refresh_version;
}
+ inline bool protected_against_usage() const
+ {
+ return version == 0;
+ }
+ inline void protect_against_usage()
+ {
+ version= 0;
+ }
+ /*
+ Remove from table definition cache at close.
+ Table can still be opened by SHOW
+ */
+ inline void remove_from_cache_at_close()
+ {
+ if (version != 0) /* Don't remove protection */
+ version= 1;
+ }
+ inline void set_refresh_version()
+ {
+ version= refresh_version;
+ }
+
/**
Convert unrelated members of TABLE_SHARE to one enum
representing its type.