Patch changing how ALTER TABLE implementation handles table locking

and invalidation in the most general case (non-temporary table and
not simple RENAME or ENABLE/DISABLE KEYS or partitioning command).

See comment for sql/sql_table.cc for more information.

These changes are prerequisite for 5.1 version of fix for bug #23667
"CREATE TABLE LIKE is not isolated from alteration by other connections"
parent e4a3189c
...@@ -762,4 +762,33 @@ alter table t2 modify i int default 4, rename t1; ...@@ -762,4 +762,33 @@ alter table t2 modify i int default 4, rename t1;
unlock tables; unlock tables;
drop table t1; drop table t1;
#
# Some more tests for ALTER TABLE and LOCK TABLES for transactional tables.
#
# Table which is altered under LOCK TABLES should stay in list of locked
# tables and be available after alter takes place unless ALTER contains
# RENAME clause. We should see the new definition of table, of course.
# Before 5.1 this behavior was inconsistent across the platforms and
# different engines. See also tests in alter_table.test
#
--disable_warnings
drop table if exists t1;
--enable_warnings
create table t1 (i int);
insert into t1 values ();
lock table t1 write;
# Example of so-called 'fast' ALTER TABLE
alter table t1 modify i int default 1;
insert into t1 values ();
select * from t1;
# And now full-blown ALTER TABLE
alter table t1 change i c char(10) default "Two";
insert into t1 values ();
select * from t1;
unlock tables;
select * from t1;
drop tables t1;
--echo End of 5.1 tests --echo End of 5.1 tests
...@@ -5,14 +5,53 @@ key (n2, n3, n1), ...@@ -5,14 +5,53 @@ key (n2, n3, n1),
key (n3, n1, n2)); key (n3, n1, n2));
create table t2 (i int); create table t2 (i int);
alter table t1 disable keys; alter table t1 disable keys;
insert into t1 values (RAND()*1000, RAND()*1000, RAND()*1000);
reset master; reset master;
set session debug="+d,sleep_alter_enable_indexes";
alter table t1 enable keys;; alter table t1 enable keys;;
insert into t2 values (1); insert into t2 values (1);
insert into t1 values (1, 1, 1); insert into t1 values (1, 1, 1);
show binlog events in 'master-bin.000001' from 102; set session debug="-d,sleep_alter_enable_indexes";
show binlog events in 'master-bin.000001' from 106;
Log_name Pos Event_type Server_id End_log_pos Info Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Query 1 # use `test`; insert into t2 values (1) master-bin.000001 # Query 1 # use `test`; insert into t2 values (1)
master-bin.000001 # Query 1 # use `test`; alter table t1 enable keys master-bin.000001 # Query 1 # use `test`; alter table t1 enable keys
master-bin.000001 # Query 1 # use `test`; insert into t1 values (1, 1, 1) master-bin.000001 # Query 1 # use `test`; insert into t1 values (1, 1, 1)
drop tables t1, t2; drop tables t1, t2;
End of 5.0 tests End of 5.0 tests
drop table if exists t1, t2, t3;
create table t1 (i int);
reset master;
set session debug="+d,sleep_alter_before_main_binlog";
alter table t1 change i c char(10) default 'Test1';;
insert into t1 values ();
select * from t1;
c
Test1
alter table t1 change c vc varchar(100) default 'Test2';;
rename table t1 to t2;
drop table t2;
create table t1 (i int);
alter table t1 change i c char(10) default 'Test3', rename to t2;;
insert into t2 values ();
select * from t2;
c
Test3
alter table t2 change c vc varchar(100) default 'Test2', rename to t1;;
rename table t1 to t3;
drop table t3;
set session debug="-d,sleep_alter_before_main_binlog";
show binlog events in 'master-bin.000001' from 106;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Query 1 # use `test`; alter table t1 change i c char(10) default 'Test1'
master-bin.000001 # Query 1 # use `test`; insert into t1 values ()
master-bin.000001 # Query 1 # use `test`; alter table t1 change c vc varchar(100) default 'Test2'
master-bin.000001 # Query 1 # use `test`; rename table t1 to t2
master-bin.000001 # Query 1 # use `test`; drop table t2
master-bin.000001 # Query 1 # use `test`; create table t1 (i int)
master-bin.000001 # Query 1 # use `test`; alter table t1 change i c char(10) default 'Test3', rename to t2
master-bin.000001 # Query 1 # use `test`; insert into t2 values ()
master-bin.000001 # Query 1 # use `test`; alter table t2 change c vc varchar(100) default 'Test2', rename to t1
master-bin.000001 # Query 1 # use `test`; rename table t1 to t3
master-bin.000001 # Query 1 # use `test`; drop table t3
End of 5.1 tests
...@@ -977,6 +977,59 @@ SELECT * FROM t1; ...@@ -977,6 +977,59 @@ SELECT * FROM t1;
v b v b
abc 5 abc 5
DROP TABLE t1; DROP TABLE t1;
End of 5.0 tests
drop table if exists t1, t2, t3;
create table t1 (i int);
create table t3 (j int);
insert into t1 values ();
insert into t3 values ();
lock table t1 write, t3 read;
alter table t1 modify i int default 1;
insert into t1 values ();
select * from t1;
i
NULL
1
alter table t1 change i c char(10) default "Two";
insert into t1 values ();
select * from t1;
c
NULL
1
Two
alter table t1 modify c char(10) default "Three", rename to t2;
select * from t1;
ERROR HY000: Table 't1' was not locked with LOCK TABLES
select * from t2;
ERROR HY000: Table 't2' was not locked with LOCK TABLES
select * from t3;
j
NULL
unlock tables;
insert into t2 values ();
select * from t2;
c
NULL
1
Three
lock table t2 write, t3 read;
alter table t2 change c vc varchar(100) default "Four", rename to t1;
select * from t1;
ERROR HY000: Table 't1' was not locked with LOCK TABLES
select * from t2;
ERROR HY000: Table 't2' was not locked with LOCK TABLES
select * from t3;
j
NULL
unlock tables;
insert into t1 values ();
select * from t1;
vc
NULL
1
Three
Four
drop tables t1, t3;
DROP TABLE IF EXISTS `t+1`, `t+2`; DROP TABLE IF EXISTS `t+1`, `t+2`;
CREATE TABLE `t+1` (c1 INT); CREATE TABLE `t+1` (c1 INT);
ALTER TABLE `t+1` RENAME `t+2`; ALTER TABLE `t+1` RENAME `t+2`;
......
...@@ -796,4 +796,28 @@ lock table t2 write; ...@@ -796,4 +796,28 @@ lock table t2 write;
alter table t2 modify i int default 4, rename t1; alter table t2 modify i int default 4, rename t1;
unlock tables; unlock tables;
drop table t1; drop table t1;
drop table if exists t1;
create table t1 (i int);
insert into t1 values ();
lock table t1 write;
alter table t1 modify i int default 1;
insert into t1 values ();
select * from t1;
i
NULL
1
alter table t1 change i c char(10) default "Two";
insert into t1 values ();
select * from t1;
c
NULL
1
Two
unlock tables;
select * from t1;
c
NULL
1
Two
drop tables t1;
End of 5.1 tests End of 5.1 tests
# In order to be more or less robust test for bug#25044 has to take #
# significant time (e.g. about 9 seconds on my (Dmitri's) computer) # Tests for various concurrency-related aspects of ALTER TABLE implemetation
# so we probably want execute it only in --big-test mode. #
# This test takes rather long time so let us run it only in --big-test mode
--source include/big_test.inc --source include/big_test.inc
# We are using some debug-only features in this test
--source include/have_debug.inc
# Also we are using SBR to check that statements are executed
# in proper order.
--source include/have_binlog_format_mixed_or_statement.inc --source include/have_binlog_format_mixed_or_statement.inc
...@@ -22,27 +27,20 @@ create table t1 (n1 int, n2 int, n3 int, ...@@ -22,27 +27,20 @@ create table t1 (n1 int, n2 int, n3 int,
key (n3, n1, n2)); key (n3, n1, n2));
create table t2 (i int); create table t2 (i int);
# Populating 't1' table with keys disabled, so ALTER TABLE .. ENABLE KEYS # Starting from 5.1 we have runtime settable @@debug variable,
# will run for some time # which can be used for introducing delays at certain points of
# statement execution, so we don't need many rows in 't1' to make
# this test repeatable.
alter table t1 disable keys; alter table t1 disable keys;
--disable_query_log insert into t1 values (RAND()*1000, RAND()*1000, RAND()*1000);
insert into t1 values (RAND()*1000,RAND()*1000,RAND()*1000);
let $1=19;
while ($1)
{
eval insert into t1 select RAND()*1000,RAND()*1000,RAND()*1000 from t1;
dec $1;
}
--enable_query_log
# Later we use binlog to check the order in which statements are # Later we use binlog to check the order in which statements are
# executed so let us reset it first. # executed so let us reset it first.
reset master; reset master;
set session debug="+d,sleep_alter_enable_indexes";
--send alter table t1 enable keys; --send alter table t1 enable keys;
connection addconroot; connection addconroot;
let $show_type= PROCESSLIST; --sleep 2
let $show_pattern= '%Repair by sorting%alter table t1 enable keys%';
--source include/wait_show_pattern.inc
# This statement should not be blocked by in-flight ALTER and therefore # This statement should not be blocked by in-flight ALTER and therefore
# should be executed and written to binlog before ALTER TABLE ... ENABLE KEYS # should be executed and written to binlog before ALTER TABLE ... ENABLE KEYS
# finishes. # finishes.
...@@ -51,12 +49,68 @@ insert into t2 values (1); ...@@ -51,12 +49,68 @@ insert into t2 values (1);
insert into t1 values (1, 1, 1); insert into t1 values (1, 1, 1);
connection default; connection default;
--reap --reap
set session debug="-d,sleep_alter_enable_indexes";
# Check that statements were executed/binlogged in correct order. # Check that statements were executed/binlogged in correct order.
--replace_column 2 # 5 # --replace_column 2 # 5 #
show binlog events in 'master-bin.000001' from 102; show binlog events in 'master-bin.000001' from 106;
# Clean up # Clean up
drop tables t1, t2; drop tables t1, t2;
--echo End of 5.0 tests --echo End of 5.0 tests
#
# Additional coverage for the main ALTER TABLE case
#
# We should be sure that table being altered is properly
# locked during statement execution and in particular that
# no DDL or DML statement can sneak in and get access to
# the table when real operation has already taken place
# but this fact has not been noted in binary log yet.
--disable_warnings
drop table if exists t1, t2, t3;
--enable_warnings
create table t1 (i int);
# We are going to check that statements are logged in correct order
reset master;
set session debug="+d,sleep_alter_before_main_binlog";
--send alter table t1 change i c char(10) default 'Test1';
connection addconroot;
--sleep 2
insert into t1 values ();
select * from t1;
connection default;
--reap
--send alter table t1 change c vc varchar(100) default 'Test2';
connection addconroot;
--sleep 2
rename table t1 to t2;
connection default;
--reap
drop table t2;
# And now tests for ALTER TABLE with RENAME clause. In this
# case target table name should be properly locked as well.
create table t1 (i int);
--send alter table t1 change i c char(10) default 'Test3', rename to t2;
connection addconroot;
--sleep 2
insert into t2 values ();
select * from t2;
connection default;
--reap
--send alter table t2 change c vc varchar(100) default 'Test2', rename to t1;
connection addconroot;
--sleep 2
rename table t1 to t3;
connection default;
--reap
drop table t3;
set session debug="-d,sleep_alter_before_main_binlog";
# Check that all statements were logged in correct order
--replace_column 2 # 5 #
show binlog events in 'master-bin.000001' from 106;
--echo End of 5.1 tests
...@@ -727,7 +727,58 @@ ALTER TABLE t1 MODIFY COLUMN v VARCHAR(4); ...@@ -727,7 +727,58 @@ ALTER TABLE t1 MODIFY COLUMN v VARCHAR(4);
SELECT * FROM t1; SELECT * FROM t1;
DROP TABLE t1; DROP TABLE t1;
# End of 5.0 tests --echo End of 5.0 tests
#
# Extended test coverage for ALTER TABLE behaviour under LOCK TABLES
# It should be consistent across all platforms and for all engines
# (Before 5.1 this was not true as behavior was different between
# Unix/Windows and transactional/non-transactional tables).
# See also innodb_mysql.test
#
--disable_warnings
drop table if exists t1, t2, t3;
--enable_warnings
create table t1 (i int);
create table t3 (j int);
insert into t1 values ();
insert into t3 values ();
# Table which is altered under LOCK TABLES it should stay in list of locked
# tables and be available after alter takes place unless ALTER contains RENAME
# clause. We should see the new definition of table, of course.
lock table t1 write, t3 read;
# Example of so-called 'fast' ALTER TABLE
alter table t1 modify i int default 1;
insert into t1 values ();
select * from t1;
# And now full-blown ALTER TABLE
alter table t1 change i c char(10) default "Two";
insert into t1 values ();
select * from t1;
# If table is renamed then it should be removed from the list
# of locked tables. 'Fast' ALTER TABLE with RENAME clause:
alter table t1 modify c char(10) default "Three", rename to t2;
--error ER_TABLE_NOT_LOCKED
select * from t1;
--error ER_TABLE_NOT_LOCKED
select * from t2;
select * from t3;
unlock tables;
insert into t2 values ();
select * from t2;
lock table t2 write, t3 read;
# Full ALTER TABLE with RENAME
alter table t2 change c vc varchar(100) default "Four", rename to t1;
--error ER_TABLE_NOT_LOCKED
select * from t1;
--error ER_TABLE_NOT_LOCKED
select * from t2;
select * from t3;
unlock tables;
insert into t1 values ();
select * from t1;
drop tables t1, t3;
# #
# Bug#18775 - Temporary table from alter table visible to other threads # Bug#18775 - Temporary table from alter table visible to other threads
......
...@@ -1032,8 +1032,11 @@ TABLE *table_cache_insert_placeholder(THD *thd, const char *key, ...@@ -1032,8 +1032,11 @@ TABLE *table_cache_insert_placeholder(THD *thd, const char *key,
bool lock_table_name_if_not_cached(THD *thd, const char *db, bool lock_table_name_if_not_cached(THD *thd, const char *db,
const char *table_name, TABLE **table); const char *table_name, TABLE **table);
TABLE *find_locked_table(THD *thd, const char *db,const char *table_name); TABLE *find_locked_table(THD *thd, const char *db,const char *table_name);
bool reopen_table(TABLE *table);
bool reopen_tables(THD *thd,bool get_locks,bool in_refresh); bool reopen_tables(THD *thd,bool get_locks,bool in_refresh);
bool close_data_tables(THD *thd,const char *db, const char *table_name); void close_data_files_and_morph_locks(THD *thd, const char *db,
const char *table_name);
void close_handle_and_leave_table_as_lock(TABLE *table);
bool wait_for_tables(THD *thd); bool wait_for_tables(THD *thd);
bool table_is_used(TABLE *table, bool wait_for_name_lock); bool table_is_used(TABLE *table, bool wait_for_name_lock);
TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name); TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name);
......
...@@ -99,7 +99,6 @@ static bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, ...@@ -99,7 +99,6 @@ static bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias,
TABLE_LIST *table_desc, MEM_ROOT *mem_root); TABLE_LIST *table_desc, MEM_ROOT *mem_root);
static void close_old_data_files(THD *thd, TABLE *table, bool morph_locks, static void close_old_data_files(THD *thd, TABLE *table, bool morph_locks,
bool send_refresh); bool send_refresh);
static bool reopen_table(TABLE *table);
static bool static bool
has_two_write_locked_tables_with_auto_increment(TABLE_LIST *tables); has_two_write_locked_tables_with_auto_increment(TABLE_LIST *tables);
...@@ -681,7 +680,7 @@ TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name) ...@@ -681,7 +680,7 @@ TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name)
*/ */
static void close_handle_and_leave_table_as_lock(TABLE *table) void close_handle_and_leave_table_as_lock(TABLE *table)
{ {
TABLE_SHARE *share, *old_share= table->s; TABLE_SHARE *share, *old_share= table->s;
char *key_buff; char *key_buff;
...@@ -2705,7 +2704,7 @@ TABLE *find_locked_table(THD *thd, const char *db,const char *table_name) ...@@ -2705,7 +2704,7 @@ TABLE *find_locked_table(THD *thd, const char *db,const char *table_name)
1 error. The old table object is not changed. 1 error. The old table object is not changed.
*/ */
static bool reopen_table(TABLE *table) bool reopen_table(TABLE *table)
{ {
TABLE tmp; TABLE tmp;
bool error= 1; bool error= 1;
...@@ -2788,27 +2787,55 @@ static bool reopen_table(TABLE *table) ...@@ -2788,27 +2787,55 @@ static bool reopen_table(TABLE *table)
} }
/* /**
Used with ALTER TABLE: @brief Close all instances of a table open by this thread and replace
Close all instanses of table when LOCK TABLES is in used; them with exclusive name-locks.
Close first all instances of table and then reopen them
@param thd Thread context
@param db Database name for the table to be closed
@param table_name Name of the table to be closed
@note This function assumes that if we are not under LOCK TABLES,
then there is only one table open and locked. This means that
the function probably has to be adjusted before it can be used
anywhere outside ALTER TABLE.
*/ */
bool close_data_tables(THD *thd,const char *db, const char *table_name) void close_data_files_and_morph_locks(THD *thd, const char *db,
const char *table_name)
{ {
TABLE *table; TABLE *table;
DBUG_ENTER("close_data_tables"); DBUG_ENTER("close_data_files_and_morph_locks");
safe_mutex_assert_owner(&LOCK_open);
if (thd->lock)
{
/*
If we are not under LOCK TABLES we should have only one table
open and locked so it makes sense to remove the lock at once.
*/
mysql_unlock_tables(thd, thd->lock);
thd->lock= 0;
}
/*
Note that open table list may contain a name-lock placeholder
for target table name if we process ALTER TABLE ... RENAME.
So loop below makes sense even if we are not under LOCK TABLES.
*/
for (table=thd->open_tables; table ; table=table->next) for (table=thd->open_tables; table ; table=table->next)
{ {
if (!strcmp(table->s->table_name.str, table_name) && if (!strcmp(table->s->table_name.str, table_name) &&
!strcmp(table->s->db.str, db)) !strcmp(table->s->db.str, db))
{ {
mysql_lock_remove(thd, thd->locked_tables,table); if (thd->locked_tables)
mysql_lock_remove(thd, thd->locked_tables, table);
table->open_placeholder= 1;
close_handle_and_leave_table_as_lock(table); close_handle_and_leave_table_as_lock(table);
} }
} }
DBUG_RETURN(0); // For the future DBUG_VOID_RETURN;
} }
......
...@@ -5406,7 +5406,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, ...@@ -5406,7 +5406,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
HA_CREATE_INFO *create_info; HA_CREATE_INFO *create_info;
frm_type_enum frm_type; frm_type_enum frm_type;
uint need_copy_table= 0; uint need_copy_table= 0;
bool no_table_reopen= FALSE, varchar= FALSE; bool varchar= FALSE;
#ifdef WITH_PARTITION_STORAGE_ENGINE #ifdef WITH_PARTITION_STORAGE_ENGINE
uint fast_alter_partition= 0; uint fast_alter_partition= 0;
bool partition_changed= FALSE; bool partition_changed= FALSE;
...@@ -5665,6 +5665,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, ...@@ -5665,6 +5665,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
VOID(pthread_mutex_lock(&LOCK_open)); VOID(pthread_mutex_lock(&LOCK_open));
wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
VOID(pthread_mutex_unlock(&LOCK_open)); VOID(pthread_mutex_unlock(&LOCK_open));
DBUG_EXECUTE_IF("sleep_alter_enable_indexes", my_sleep(6000000););
error= table->file->enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE); error= table->file->enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
/* COND_refresh will be signaled in close_thread_tables() */ /* COND_refresh will be signaled in close_thread_tables() */
break; break;
...@@ -6580,9 +6581,19 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, ...@@ -6580,9 +6581,19 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
} }
/* /*
Data is copied. Now we rename the old table to a temp name, Data is copied. Now we:
rename the new one to the old name, remove all entries about the old table 1) Wait until all other threads close old version of table.
from the cache, free all locks, close the old table and remove it. 2) Close instances of table open by this thread and replace them
with exclusive name-locks.
3) Rename the old table to a temp name, rename the new one to the
old name.
4) If we are under LOCK TABLES and don't do ALTER TABLE ... RENAME
we reopen new version of table.
5) Write statement to the binary log.
6) If we are under LOCK TABLES and do ALTER TABLE ... RENAME we
remove name-locks from list of open tables and table cache.
7) If we are not not under LOCK TABLES we rely on close_thread_tables()
call to remove name-locks from table cache and list of open table.
*/ */
thd->proc_info="rename result table"; thd->proc_info="rename result table";
...@@ -6591,38 +6602,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, ...@@ -6591,38 +6602,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
if (lower_case_table_names) if (lower_case_table_names)
my_casedn_str(files_charset_info, old_name); my_casedn_str(files_charset_info, old_name);
#if !defined( __WIN__) wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_DELETE);
if (table->file->has_transactions()) close_data_files_and_morph_locks(thd, db, table_name);
#endif
{
/*
Win32 and InnoDB can't drop a table that is in use, so we must
close the original table before doing the rename
*/
close_cached_table(thd, table);
table=0; // Marker that table is closed
no_table_reopen= TRUE;
}
#if !defined( __WIN__)
else
table->file->extra(HA_EXTRA_FORCE_REOPEN); // Don't use this file anymore
#endif
if (new_name != table_name || new_db != db)
{
/*
Check that there is no table with target name. See the
comment describing code for 'simple' ALTER TABLE ... RENAME.
*/
if (!access(new_name_buff,F_OK))
{
error=1;
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name_buff);
VOID(quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP));
VOID(pthread_mutex_unlock(&LOCK_open));
goto err;
}
}
error=0; error=0;
save_old_db_type= old_db_type; save_old_db_type= old_db_type;
...@@ -6667,121 +6648,64 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, ...@@ -6667,121 +6648,64 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
if (error) if (error)
{ {
/* /* This shouldn't happen. But let us play it safe. */
This shouldn't happen. We solve this the safe way by goto err_with_placeholders;
closing the locked table.
*/
if (table)
{
close_cached_table(thd,table);
}
VOID(pthread_mutex_unlock(&LOCK_open));
goto err;
} }
if (! need_copy_table) if (! need_copy_table)
{ {
bool needs_unlink= FALSE; /*
if (! table) 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.
if (new_name != table_name || new_db != db) */
{ TABLE *t_table;
table_list->alias= new_name; if (new_name != table_name || new_db != db)
table_list->table_name= new_name; {
table_list->table_name_length= strlen(new_name); table_list->alias= new_name;
table_list->db= new_db; table_list->table_name= new_name;
table_list->db_length= strlen(new_db); table_list->table_name_length= strlen(new_name);
} table_list->db= new_db;
else table_list->db_length= strlen(new_db);
{
/*
TODO: Creation of name-lock placeholder here is a temporary
work-around. Long term we should change close_cached_table() call
which we invoke before table renaming operation in such way that
it will leave placeholders for table in table cache/THD::open_tables
list. By doing this we will be able easily reopen and relock these
tables later and therefore behave under LOCK TABLES in the same way
on all platforms.
*/
char key[MAX_DBKEY_LENGTH];
uint key_length;
key_length= create_table_def_key(thd, key, table_list, 0);
if (!(name_lock= table_cache_insert_placeholder(thd, key,
key_length)))
{
VOID(pthread_mutex_unlock(&LOCK_open));
goto err;
}
name_lock->next= thd->open_tables;
thd->open_tables= name_lock;
}
table_list->table= name_lock; table_list->table= name_lock;
if (reopen_name_locked_table(thd, table_list, FALSE)) if (reopen_name_locked_table(thd, table_list, FALSE))
{ goto err_with_placeholders;
VOID(pthread_mutex_unlock(&LOCK_open)); t_table= table_list->table;
goto err;
}
table= table_list->table;
/*
We can't rely on later close_cached_table() calls to close
this instance of the table since it was not properly locked.
*/
needs_unlink= TRUE;
} }
/* Tell the handler that a new frm file is in place. */ else
if (table->file->create_handler_files(path, NULL, CHF_INDEX_FLAG,
create_info))
{ {
VOID(pthread_mutex_unlock(&LOCK_open)); if (reopen_table(table))
goto err; goto err_with_placeholders;
t_table= table;
} }
if (needs_unlink) /* Tell the handler that a new frm file is in place. */
if (t_table->file->create_handler_files(path, NULL, CHF_INDEX_FLAG,
create_info))
goto err_with_placeholders;
if (thd->locked_tables && new_name == table_name && new_db == db)
{ {
unlink_open_table(thd, table, FALSE); /*
table= name_lock= 0; We are going to reopen table down on the road, so we have to restore
state of the TABLE object which we used for obtaining of handler
object to make it suitable for reopening.
*/
DBUG_ASSERT(t_table == table);
table->open_placeholder= 1;
close_handle_and_leave_table_as_lock(table);
} }
} }
if (thd->lock || new_name != table_name || no_table_reopen) // True if WIN32 VOID(quick_rm_table(old_db_type, db, old_name, FN_IS_TMP));
{
/* if (thd->locked_tables && new_name == table_name && new_db == db)
Not table locking or alter table with rename.
Free locks and remove old table
*/
if (table)
{
close_cached_table(thd,table);
}
VOID(quick_rm_table(old_db_type, db, old_name, FN_IS_TMP));
}
else
{ {
/* thd->in_lock_tables= 1;
Using LOCK TABLES without rename. error= reopen_tables(thd, 1, 0);
This code is never executed on WIN32! thd->in_lock_tables= 0;
Remove old renamed table, reopen table and get new locks if (error)
*/ goto err_with_placeholders;
if (table)
{
VOID(table->file->extra(HA_EXTRA_FORCE_REOPEN)); // Use new file
/* Mark in-use copies old */
remove_table_from_cache(thd,db,table_name,RTFC_NO_FLAG);
/* end threads waiting on lock */
mysql_lock_abort(thd,table, TRUE);
}
VOID(quick_rm_table(old_db_type, db, old_name, FN_IS_TMP));
if (close_data_tables(thd,db,table_name) ||
reopen_tables(thd,1,0))
{ // This shouldn't happen
if (table)
{
close_cached_table(thd,table); // Remove lock for table
}
VOID(pthread_mutex_unlock(&LOCK_open));
goto err;
}
} }
VOID(pthread_mutex_unlock(&LOCK_open)); VOID(pthread_mutex_unlock(&LOCK_open));
broadcast_refresh();
/* /*
The ALTER TABLE is always in its own transaction. The ALTER TABLE is always in its own transaction.
Commit must not be called while LOCK_open is locked. It could call Commit must not be called while LOCK_open is locked. It could call
...@@ -6798,6 +6722,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, ...@@ -6798,6 +6722,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
} }
thd->proc_info="end"; thd->proc_info="end";
DBUG_EXECUTE_IF("sleep_alter_before_main_binlog", my_sleep(6000000););
ha_binlog_log_query(thd, create_info->db_type, LOGCOM_ALTER_TABLE, ha_binlog_log_query(thd, create_info->db_type, LOGCOM_ALTER_TABLE,
thd->query, thd->query_length, thd->query, thd->query_length,
db, table_name); db, table_name);
...@@ -6815,12 +6741,13 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, ...@@ -6815,12 +6741,13 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
shutdown. shutdown.
*/ */
char path[FN_REFLEN]; char path[FN_REFLEN];
TABLE *t_table;
build_table_filename(path, sizeof(path), new_db, table_name, "", 0); build_table_filename(path, sizeof(path), new_db, table_name, "", 0);
table=open_temporary_table(thd, path, new_db, tmp_name,0); t_table= open_temporary_table(thd, path, new_db, tmp_name, 0);
if (table) if (t_table)
{ {
intern_close_table(table); intern_close_table(t_table);
my_free((char*) table, MYF(0)); my_free((char*) t_table, MYF(0));
} }
else else
sql_print_warning("Could not open table %s.%s after rename\n", sql_print_warning("Could not open table %s.%s after rename\n",
...@@ -6830,9 +6757,16 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, ...@@ -6830,9 +6757,16 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
table_list->table=0; // For query cache table_list->table=0; // For query cache
query_cache_invalidate3(thd, table_list, 0); query_cache_invalidate3(thd, table_list, 0);
if (name_lock) if (thd->locked_tables && (new_name != table_name || new_db != db))
{ {
/*
If are we under LOCK TABLES and did ALTER TABLE with RENAME we need
to remove placeholders for the old table and for the target table
from the list of open tables and table cache. If we are not under
LOCK TABLES we can rely on close_thread_tables() doing this job.
*/
pthread_mutex_lock(&LOCK_open); pthread_mutex_lock(&LOCK_open);
unlink_open_table(thd, table, FALSE);
unlink_open_table(thd, name_lock, FALSE); unlink_open_table(thd, name_lock, FALSE);
pthread_mutex_unlock(&LOCK_open); pthread_mutex_unlock(&LOCK_open);
} }
...@@ -6863,6 +6797,18 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, ...@@ -6863,6 +6797,18 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
pthread_mutex_unlock(&LOCK_open); pthread_mutex_unlock(&LOCK_open);
} }
DBUG_RETURN(TRUE); DBUG_RETURN(TRUE);
err_with_placeholders:
/*
An error happened while we were holding exclusive name-lock on table
being altered. To be safe under LOCK TABLES we should remove placeholders
from list of open tables list and table cache.
*/
unlink_open_table(thd, table, FALSE);
if (name_lock)
unlink_open_table(thd, name_lock, FALSE);
VOID(pthread_mutex_unlock(&LOCK_open));
DBUG_RETURN(TRUE);
} }
/* mysql_alter_table */ /* mysql_alter_table */
......
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