Commit 95d91c0f authored by Magne Mahre's avatar Magne Mahre

Bug #46941 crash with lower_case_table_names=2 and foreign key

           data dictionary confusion

On file systems with case insensitive file names, and
lower_case_table_names set to '2', the server could crash
due to a table definition cache inconsistency.  This is 
the default setting on MacOSX, but may also be set and
used on MS Windows.

The bug is caused by using two different strategies for
creating the hash key for the table definition cache, resulting
in failure to look up an entry which is present in the cache,
or failure to delete an existing entry.  One strategy was to
use the real table name (with case preserved), and the other
to use a normalized table name (i.e a lower case version).

This is manifested in two cases.  One is  during 'DROP DATABASE', 
where all known files are removed.  The removal from
the table definition cache is done via a generated list of
TABLE_LIST with keys (wrongly) created using the case preserved 
name.  The other is during CREATE TABLE, where the cache lookup
is also (wrongly) based on the case preserved name.
   
The fix was to use only the normalized table name when
creating hash keys.


sql/sql_db.cc:
  Normalize table name (i.e lower case it)
sql/sql_table.cc:
  table_name contains the normalized name
  alias contains the real table name
parent 4776282b
#
# Bug#46941 crash with lower_case_table_names=2 and
# foreign data dictionary confusion
#
CREATE DATABASE XY;
USE XY;
DROP DATABASE XY;
--source include/have_case_insensitive_file_system.inc
--source include/have_innodb.inc
--echo #
--echo # Bug#46941 crash with lower_case_table_names=2 and
--echo # foreign data dictionary confusion
--echo #
CREATE DATABASE XY;
USE XY;
#
# Logs are disabled, since the number of creates tables
# and subsequent select statements may vary between
# versions
#
--disable_query_log
--disable_result_log
let $tcs = `SELECT @@table_open_cache + 1`;
let $i = $tcs;
while ($i)
{
eval CREATE TABLE XY.T_$i (a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, d INT,
primary key(a, b), unique(b)) ENGINE=InnoDB;
dec $i;
}
eval ALTER TABLE XY.T_$tcs ADD INDEX I1 (c, b),
ADD CONSTRAINT C1 FOREIGN KEY (c, b) REFERENCES XY.T_1 (a, b);
eval ALTER TABLE XY.T_$tcs ADD INDEX I2 (b),
ADD CONSTRAINT C2 FOREIGN KEY (b) REFERENCES XY.T_1(a);
let $i = $tcs;
while ($i)
{
eval SELECT * FROM XY.T_$i LIMIT 1;
dec $i;
}
DROP DATABASE XY;
CREATE DATABASE XY;
USE XY;
eval CREATE TABLE XY.T_$tcs (a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, d INT,
PRIMARY KEY(a, b), UNIQUE(b)) ENGINE=InnoDB;
#
# The bug causes this SELECT to err
eval SELECT * FROM XY.T_$tcs LIMIT 1;
--enable_query_log
--enable_result_log
DROP DATABASE XY;
...@@ -1197,6 +1197,12 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db, ...@@ -1197,6 +1197,12 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db,
VOID(filename_to_tablename(file->name, table_list->table_name, VOID(filename_to_tablename(file->name, table_list->table_name,
MYSQL50_TABLE_NAME_PREFIX_LENGTH + MYSQL50_TABLE_NAME_PREFIX_LENGTH +
strlen(file->name) + 1)); strlen(file->name) + 1));
/* To be able to correctly look up the table in the table cache. */
if (lower_case_table_names)
table_list->table_name_length= my_casedn_str(files_charset_info,
table_list->table_name);
table_list->alias= table_list->table_name; // If lower_case_table_names=2 table_list->alias= table_list->table_name; // If lower_case_table_names=2
table_list->internal_tmp_table= is_prefix(file->name, tmp_file_prefix); table_list->internal_tmp_table= is_prefix(file->name, tmp_file_prefix);
/* Link into list */ /* Link into list */
......
...@@ -3896,7 +3896,7 @@ bool mysql_create_table_no_lock(THD *thd, ...@@ -3896,7 +3896,7 @@ bool mysql_create_table_no_lock(THD *thd,
Then she could create the table. This case is pretty obscure and Then she could create the table. This case is pretty obscure and
therefore we don't introduce a new error message only for it. therefore we don't introduce a new error message only for it.
*/ */
if (get_cached_table_share(db, alias)) if (get_cached_table_share(db, table_name))
{ {
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name); my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name);
goto unlock_and_end; goto unlock_and_end;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment