* Jon Olav Hauglid <Jon.Hauglid@stripped> [09/06/23 16:30]:
> 2804 Jon Olav Hauglid 2009-06-23
> Bug#42546 Backup: RESTORE fails, thinking it finds an existing table
>
> The problem occured when a MDL locking conflict happened for a non-existent
> table between a CREATE and a INSERT statement. The code for CREATE
> interpreted this lock conflict to mean that the table existed,
> which meant that the statement failed when it should not have.
There are many things that still need to be done, and
I will have fairly radical suggestions.
First of all, there are 3 use cases susceptible to the bug:
1) CREATE TABLE [IF NOT EXISTS]
2) CREATE TABLE LIKE
3) ALTER TABLE ... RENAME
Each case will require a different fix.
1) CREATE TABLE [IF NOT EXISTS].
In the foreign keys tree we prohibit CREATE TABLE
under LOCK TABLES. Since we prohibit other dangerous operations
under LOCK TABLES in 5.4, it's better to do all incompatible
changes related to LOCK TABLES in one release, rather than
span multiple releases.
Once this is done, we can use the normal open_and_lock_tables()
function for CREATE TABLE, which internally will use
acquire_exclusive_locks().
For an example how it is done, please look at CREATE TABLE .. SELECT
implementation in 5.4, and CREATE TABLE implementation in
6.1-fk-stage:
sql_parse.cc, line ~2631:
if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE))
{
lex->link_first_table_back(create_table, link_to_local);
/* Set strategies: reset default or 'prepared' values. */
create_table->open_strategy= TABLE_LIST::OPEN_IF_EXISTS;
create_table->lock_strategy= TABLE_LIST::EXCLUSIVE_DOWNGRADABLE_MDL;
if (!thd->locked_tables_mode)
global_schema_lock_guard.lock();
}
if (!(res= open_and_lock_tables(thd, lex->query_tables)))
{
/*
In other words, we should use open_strategy and lock_strategy
mechanism to open the table.
This change is already implemented in 6.1-fk-stage and it
would be ideal if you just backport it from there. You need
to be looking at 6.1-fk-stage since I will request a backport
of class Prelocking_strategy from this tree for Bug#30977.
Please ask me and Dmitri for pointers what bits exactly need
to be backported.
2) CREATE TABLE LIKE
Once we prohibit CREATE TABLE (all forms, including CREATE TABLE.. LIKE)
under LOCK TABLES, we can
significantly simplify CREATE TABLE .. LIKE implementation,
and make it use the path of CREATE TABLE .. SELECT.
Once it's done, we solve the problem for CREATE TABLE ... LIKE,
since CREATE TABLE ... SELECT is using open_strategy/lock_strategy,
and thus is not susceptible to the bug.
Note, that CREATE TABLE ... SELECT has been prohibited under LOCK TABLES
since long time ago.
CREATE TABLE ... LIKE has been rewritten in 6.1-fk-stage to use
CREATE TABLE ... SELECT implementation, so please backport the change
from there.
3) ALTER TABLE ... RENAME.
This case is difficult. ALTER TABLE .. RENAME is allowed under LOCK TABLES,
and we don't plan to change it yet -- there are many people using
this feature (we've been getting bug reports about it :-)).
So we need to keep try_acquire_exclusive_lock() under LOCK TABLES
in ALTER TABLE ... RENAME, but use normal open_and_lock_tables()
otherwise.
Note, that this change is also already done in 6.1-fk-stage.
Please, again, backport it.
Now, to the patch itself.
I will post my patch, and will explain how it differs, and what
other issues I found with your patch.
1) Please add a test case for CREATE TABLE LIKE and ALTER TABLE RENAME.
2) Please demonstrate that under LOCK TABLES (ALTER TABLE .. RENAME)
we still have this bug.
3) Please kill lock_table_name_if_not_exists(). (I did it in my patch).
Some comments about your patch that are irrelevant since I request
some radically different things:
4) In your patch, you try to release the target mdl ticket in some
cases when it has not been taken (you release it if it the request
was allocated successfully, but acquire_exclusive_locks() may fail).
5) There is some code duplication when producing an error message
if the table exists.
6) Calling acquire_exclusive_locks() to get an
exclusive lock in CREATE TABLE .. LIKE, and then doing
open_tables() is still dangerous, since open_tables() may fall into waiting
while holding the exclusive lock.
It's better to call open_tables() for both tables
altogether, but set the right open_strategy and lock_strategy for
the first (allegedly non-existent) table. This is how it's done
in CREATE TABLE .. SELECT.
Since doing everything I request in this review may be difficult,
please do it one step at a time:
- first check that the patch I am sending to you below works. It
solves the problem for CREATE TABLE, but is incomplete.
- then fix CREATE TABLE ... LIKE by backporting changes from 6.1-fk-stage,
and commit.
- then rewrite CREATE TABLE [IF NOT EXISTS] to use open_tables().
Again by backporting 6.1-fk-stage stages. Commit again.
- finally, fix ALTER TABLE.
If you still think it's too difficult, please don't hesitate
to bring this up.
=== modified file 'mysql-test/r/create.result'
--- mysql-test/r/create.result 2009-05-28 21:29:31 +0000
+++ mysql-test/r/create.result 2009-07-27 14:20:07 +0000
@@ -371,7 +371,7 @@ ERROR 42S01: Table 't3' already exists
create table non_existing_database.t1 like t1;
ERROR 42000: Unknown database 'non_existing_database'
create table t3 like non_existing_table;
-ERROR 42S02: Table 'test.non_existing_table' doesn't exist
+ERROR 42S01: Table 't3' already exists
create temporary table t3 like t1;
ERROR 42S01: Table 't3' already exists
drop table t1, t2, t3;
@@ -1934,3 +1934,29 @@ END ;|
ERROR 42000: This version of MySQL doesn't yet support 'multiple triggers with the same
action time and event for one table'
DROP TABLE t1;
DROP TABLE B;
+#
+# Bug#42546 - Backup: RESTORE fails, thinking it finds an existing table
+#
+DROP TABLE IF EXISTS t1;
+# Start insert on the not-yet existing table
+# Wait after taking the MDL
+SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish
+ EXECUTE 2';
+INSERT INTO t1 VALUES(1,"def");
+# Need to sync twice. Number 1 is consumed by General Log.
+SET DEBUG_SYNC= 'now WAIT_FOR locked';
+SET DEBUG_SYNC= 'now SIGNAL finish';
+SET DEBUG_SYNC= 'now WAIT_FOR locked';
+# Now INSERT has a MDL on the non-existent table t1.
+#
+# Continue the INSERT once CREATE waits for exclusive lock
+SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish';
+# Try to create that table.
+CREATE TABLE t1 (c1 INT, c2 VARCHAR(100), KEY(c1)) ENGINE=MyISAM;
+# Insert fails
+ERROR 42S02: Table 'test.t1' doesn't exist
+SET DEBUG_SYNC= 'RESET';
+SHOW TABLES;
+DROP TABLE IF EXISTS t1;
+Tables_in_test
+t1
=== modified file 'mysql-test/t/create.test'
--- mysql-test/t/create.test 2009-05-28 21:29:31 +0000
+++ mysql-test/t/create.test 2009-07-27 14:20:07 +0000
@@ -1,3 +1,5 @@
+--source include/have_debug_sync.inc
+
#
# Check some special create statements.
#
@@ -306,7 +308,7 @@ create table t3 like t1;
create table t3 like mysqltest.t3;
--error 1049
create table non_existing_database.t1 like t1;
---error ER_NO_SUCH_TABLE
+--error ER_TABLE_EXISTS_ERROR
create table t3 like non_existing_table;
--error 1050
create temporary table t3 like t1;
@@ -1600,3 +1602,51 @@ END ;|
DROP TABLE t1;
DROP TABLE B;
+
+###########################################################################
+
+--echo #
+--echo # Bug#42546 - Backup: RESTORE fails, thinking it finds an existing table
+--echo #
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
+
+connect(con1, localhost, root,,);
+connection con1;
+
+--echo # Start insert on the not-yet existing table
+--echo # Wait after taking the MDL
+SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish
+ EXECUTE 2';
+--send INSERT INTO t1 VALUES(1,"def")
+
+connection default;
+
+--echo # Need to sync twice. Number 1 is consumed by General Log.
+SET DEBUG_SYNC= 'now WAIT_FOR locked';
+SET DEBUG_SYNC= 'now SIGNAL finish';
+SET DEBUG_SYNC= 'now WAIT_FOR locked';
+--echo # Now INSERT has a MDL on the non-existent table t1.
+
+--echo #
+--echo # Continue the INSERT once CREATE waits for exclusive lock
+SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish';
+--echo # Try to create that table.
+--send CREATE TABLE t1 (c1 INT, c2 VARCHAR(100), KEY(c1)) ENGINE=MyISAM
+
+--echo # Insert fails
+connection con1;
+--error ER_NO_SUCH_TABLE
+--reap
+
+connection default;
+
+SET DEBUG_SYNC= 'RESET';
+
+SHOW TABLES;
+
+--disable_warnings
+DROP TABLE IF EXISTS t1;
+--enable_warnings
=== modified file 'sql/mdl.cc'
--- sql/mdl.cc 2009-03-31 13:00:58 +0000
+++ sql/mdl.cc 2009-07-27 17:21:20 +0000
@@ -1163,17 +1163,16 @@ MDL_ticket::upgrade_shared_lock_to_exclu
@param mdl_request [in] The lock request
@param conflict [out] Indicates that conflicting lock exists
- @retval TRUE Failure either conflicting lock exists or some error
- occurred (probably OOM).
- @retval FALSE Success, lock was acquired.
-
- FIXME: Compared to lock_table_name_if_not_cached()
- it gives slightly more false negatives.
+ @retval TRUE Failure: some error occurred (probably OOM).
+ @retval FALSE Success: the lock might have not been acquired,
+ check request.ticket to find out.
+ If and only if the lock was acquired
+ the request was added to the list of
+ requests in this context.
*/
bool
-MDL_context::try_acquire_exclusive_lock(MDL_request *mdl_request,
- bool *conflict)
+MDL_context::try_acquire_exclusive_lock(MDL_request *mdl_request)
{
MDL_lock *lock;
MDL_ticket *ticket;
@@ -1184,8 +1183,6 @@ MDL_context::try_acquire_exclusive_lock(
safe_mutex_assert_not_owner(&LOCK_open);
- *conflict= FALSE;
-
pthread_mutex_lock(&LOCK_mdl);
if (!(lock= (MDL_lock*) my_hash_search(&mdl_locks,
@@ -1197,8 +1194,15 @@ MDL_context::try_acquire_exclusive_lock(
{
MDL_ticket::destroy(ticket);
MDL_lock::destroy(lock);
- goto err;
+ pthread_mutex_unlock(&LOCK_mdl);
+ return TRUE;
}
+ /*
+ Upon successful acquisition, add the request to the list of the
+ requests of this context. The caller didn't do that.
+ Use a public API method to simplify debugging.
+ */
+ add_request(mdl_request);
mdl_request->ticket= ticket;
lock->type= MDL_lock::MDL_LOCK_EXCLUSIVE;
lock->granted.push_front(ticket);
@@ -1206,16 +1210,9 @@ MDL_context::try_acquire_exclusive_lock(
ticket->m_state= MDL_ACQUIRED;
ticket->m_lock= lock;
global_lock.active_intention_exclusive++;
- pthread_mutex_unlock(&LOCK_mdl);
- return FALSE;
}
-
- /* There is some lock for the object. */
- *conflict= TRUE;
-
-err:
pthread_mutex_unlock(&LOCK_mdl);
- return TRUE;
+ return FALSE;
}
=== modified file 'sql/mdl.h'
--- sql/mdl.h 2009-03-04 20:29:16 +0000
+++ sql/mdl.h 2009-07-27 17:26:40 +0000
@@ -285,7 +285,7 @@ public:
bool acquire_shared_lock(MDL_request *mdl_request, bool *retry);
bool acquire_exclusive_locks();
- bool try_acquire_exclusive_lock(MDL_request *mdl_request, bool *conflict);
+ bool try_acquire_exclusive_lock(MDL_request *mdl_request);
bool acquire_global_shared_lock();
bool wait_for_locks();
=== modified file 'sql/sql_base.cc'
--- sql/sql_base.cc 2009-07-24 20:28:43 +0000
+++ sql/sql_base.cc 2009-07-27 14:20:07 +0000
@@ -2596,6 +2596,7 @@ bool open_table(THD *thd, TABLE_LIST *ta
DEBUG_SYNC(thd, "before_open_table_wait_refresh");
DBUG_RETURN(TRUE);
}
+ DEBUG_SYNC(thd, "after_open_table_mdl_shared");
}
/*
=== modified file 'sql/sql_table.cc'
--- sql/sql_table.cc 2009-07-24 14:04:20 +0000
+++ sql/sql_table.cc 2009-07-27 18:42:31 +0000
@@ -1863,14 +1863,25 @@ int mysql_rm_table_part2(THD *thd, TABLE
if (find_temporary_table(thd, table->db, table->table_name))
{
/*
- Since we don't acquire metadata lock if we have found temporary
- table, we should do something to avoid releasing it at the end.
+ A temporary table.
+
+ Don't try to find a corresponding MDL lock or assign it
+ to table->mdl_request. There can't be metadata
+ locks for temporary tables: they are local to the session.
+
+ Later in this function we release the MDL lock only if
+ table->mdl_requeset is not NULL. Thus here we
+ ensure that we won't release the metadata lock on the base
+ table locked with LOCK TABLES as a side effect of temporary
+ table drop.
*/
table->mdl_request= NULL;
}
else
{
/*
+ Not a temporary table.
+
Since 'tables' list can't contain duplicates (this is ensured
by parser) it is safe to cache pointer to the TABLE instances
in its elements.
@@ -2102,7 +2113,7 @@ err:
Under LOCK TABLES we should release meta-data locks on the tables
which were dropped. Otherwise we can rely on close_thread_tables()
doing this. Unfortunately in this case we are likely to get more
- false positives in lock_table_name_if_not_cached() function. So
+ false positives in try_acquire_exclusive_lock() function. So
it makes sense to remove exclusive meta-data locks in all cases.
Leave LOCK TABLES mode if we managed to drop all tables which were
@@ -3989,48 +4000,12 @@ warn:
}
-/**
- Auxiliary function which obtains exclusive meta-data lock on the
- table if there are no shared or exclusive on it already.
-
- See mdl_try_acquire_exclusive_lock() function for more info.
-
- TODO: This function is here mostly to simplify current patch
- and probably should be removed.
- TODO: Investigate if it is kosher to leave lock request in the
- context in the case when we fail to obtain the lock.
-*/
-
-static bool lock_table_name_if_not_cached(THD *thd, const char *db,
- const char *table_name,
- MDL_request **mdl_request)
+bool dd_check_table_exists(const char *db, const char *table_name)
{
- bool conflict;
-
- if (!(*mdl_request= MDL_request::create(0, db, table_name, thd->mem_root)))
- return TRUE;
- (*mdl_request)->set_type(MDL_EXCLUSIVE);
- thd->mdl_context.add_request(*mdl_request);
- if (thd->mdl_context.try_acquire_exclusive_lock(*mdl_request, &conflict))
- {
- /*
- To simplify our life under LOCK TABLES we remove unsatisfied
- lock request from the context.
- */
- thd->mdl_context.remove_request(*mdl_request);
- if (!conflict)
- {
- /* Probably OOM. */
- return TRUE;
- }
- else
- *mdl_request= NULL;
- }
- else
- {
- DEBUG_SYNC(thd, "locked_table_name");
- }
- return FALSE;
+ char dst_path[FN_REFLEN];
+ uint dst_path_length= build_table_filename(dst_path, sizeof(dst_path),
+ db, table_name, reg_ext, 0);
+ return !access(dst_path, F_OK);
}
@@ -4045,6 +4020,7 @@ bool mysql_create_table(THD *thd, const
uint select_field_count)
{
MDL_request *target_mdl_request= NULL;
+ bool has_target_mdl_lock= FALSE;
bool result;
Ha_global_schema_lock_guard global_schema_lock_guard(thd);
DBUG_ENTER("mysql_create_table");
@@ -4073,13 +4049,44 @@ bool mysql_create_table(THD *thd, const
if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
{
- if (lock_table_name_if_not_cached(thd, db, table_name, &target_mdl_request))
+ if (!(target_mdl_request= MDL_request::create(0, db, table_name, thd->mem_root)))
{
result= TRUE;
goto unlock;
}
- if (!target_mdl_request)
+ target_mdl_request->set_type(MDL_EXCLUSIVE);
+ /*
+ Take exclusive metadata lock on the table name. If we are in LOCK TABLES
+ mode, we cannot wait in case of lock conflict for fear of deadlock.
+ */
+ if (thd->locked_tables_mode)
+ result= thd->mdl_context.try_acquire_exclusive_lock(target_mdl_request);
+ else
{
+ thd->mdl_context.add_request(target_mdl_request);
+ result= thd->mdl_context.acquire_exclusive_locks();
+ }
+ if (result)
+ goto unlock;
+ /* Got lock. */
+ DEBUG_SYNC(thd, "locked_table_name");
+ has_target_mdl_lock= test(target_mdl_request->ticket);
+
+ /**
+ If ticket is NULL, try_acquire_exclusive_lock()
+ found a conflict -- so the table is likely to already
+ exist. Note that this is not 100% accurate (see
+ Bug#42546 for a test case), but it's the best we can do.
+ If get_cached_object() is not NULL, the share exists,
+ therefore the table surely exists.
+ Finally, the table may exist on disk but not in the cache.
+ We can access disk, because we have an MDL lock.
+ */
+ if (target_mdl_request->ticket == NULL ||
+ target_mdl_request->ticket->get_cached_object() ||
+ dd_check_table_exists(db, table_name))
+ {
+ /* Table exists and is locked by some other thread. */
if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
{
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
@@ -4102,7 +4109,7 @@ bool mysql_create_table(THD *thd, const
select_field_count);
unlock:
- if (target_mdl_request)
+ if (has_target_mdl_lock)
{
thd->mdl_context.release_lock(target_mdl_request->ticket);
thd->mdl_context.remove_request(target_mdl_request);
@@ -5149,6 +5156,7 @@ bool mysql_create_like_table(THD* thd, T
MDL_request *target_mdl_request= NULL;
char src_path[FN_REFLEN + 1], dst_path[FN_REFLEN + 1];
uint dst_path_length;
+ bool has_mdl_lock= FALSE;
char *db= table->db;
char *table_name= table->table_name;
int err;
@@ -5180,7 +5188,7 @@ bool mysql_create_like_table(THD* thd, T
DBUG_EXECUTE_IF("sleep_create_like_before_check_if_exists", my_sleep(6000000););
- /*
+ /*
Check that destination tables does not exist. Note that its name
was already checked when it was added to the table list.
*/
@@ -5193,10 +5201,17 @@ bool mysql_create_like_table(THD* thd, T
}
else
{
- if (lock_table_name_if_not_cached(thd, db, table_name, &target_mdl_request))
+ if (! (target_mdl_request= MDL_request::create(0, db, table_name, thd->mem_root)))
+ goto err;
+ target_mdl_request->set_type(MDL_EXCLUSIVE);
+ if (thd->mdl_context.try_acquire_exclusive_lock(target_mdl_request))
goto err;
- if (!target_mdl_request)
+ if (target_mdl_request->ticket == NULL)
goto table_exists;
+
+ DEBUG_SYNC(thd, "locked_table_name");
+ has_mdl_lock= TRUE;
+
dst_path_length= build_table_filename(dst_path, sizeof(dst_path) - 1,
db, table_name, reg_ext, 0);
if (!access(dst_path, F_OK))
@@ -5369,7 +5384,7 @@ table_exists:
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name);
err:
- if (target_mdl_request)
+ if (has_mdl_lock)
{
thd->mdl_context.release_lock(target_mdl_request->ticket);
thd->mdl_context.remove_request(target_mdl_request);
@@ -6838,6 +6853,7 @@ bool mysql_alter_table(THD *thd,char *ne
TABLE *table, *new_table= 0;
MDL_ticket *mdl_ticket;
MDL_request *target_mdl_request= NULL;
+ bool has_target_mdl_lock= FALSE;
int error= 0;
char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN + 1];
char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias;
@@ -7077,14 +7093,21 @@ view_err:
}
else
{
- if (lock_table_name_if_not_cached(thd, new_db, new_name,
- &target_mdl_request))
+ if (! (target_mdl_request= MDL_request::create(0, new_db, new_name,
thd->mem_root)))
+ DBUG_RETURN(TRUE);
+ target_mdl_request->set_type(MDL_EXCLUSIVE);
+ if (thd->mdl_context.try_acquire_exclusive_lock(target_mdl_request))
DBUG_RETURN(TRUE);
- if (!target_mdl_request)
+
+ /* Table exists and is locked by some thread. */
+ if (target_mdl_request->ticket == NULL)
{
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias);
DBUG_RETURN(TRUE);
}
+ DEBUG_SYNC(thd, "locked_table_name");
+ has_target_mdl_lock= TRUE;
+
build_table_filename(new_name_buff, sizeof(new_name_buff) - 1,
new_db, new_name_buff, reg_ext, 0);
@@ -7741,7 +7764,7 @@ err:
alter_info->datetime_field->field_name);
thd->abort_on_warning= save_abort_on_warning;
}
- if (target_mdl_request)
+ if (has_target_mdl_lock)
{
thd->mdl_context.release_lock(target_mdl_request->ticket);
thd->mdl_context.remove_request(target_mdl_request);
> modified:
> mysql-test/r/create.result
> mysql-test/t/create.test
> sql/sql_base.cc
> sql/sql_table.cc
> === modified file 'mysql-test/r/create.result'
> --- a/mysql-test/r/create.result 2009-05-28 21:29:31 +0000
> +++ b/mysql-test/r/create.result 2009-06-23 12:08:59 +0000
> @@ -371,7 +371,7 @@ ERROR 42S01: Table 't3' already exists
> create table non_existing_database.t1 like t1;
> ERROR 42000: Unknown database 'non_existing_database'
> create table t3 like non_existing_table;
> -ERROR 42S02: Table 'test.non_existing_table' doesn't exist
> +ERROR 42S01: Table 't3' already exists
> create temporary table t3 like t1;
> ERROR 42S01: Table 't3' already exists
> drop table t1, t2, t3;
> @@ -1934,3 +1934,29 @@ END ;|
> ERROR 42000: This version of MySQL doesn't yet support 'multiple triggers with the
> same action time and event for one table'
> DROP TABLE t1;
> DROP TABLE B;
> +#
> +# Bug#42546 - Backup: RESTORE fails, thinking it finds an existing table
> +#
> +DROP TABLE IF EXISTS t1;
> +# Start insert on the not-yet existing table
> +# Wait after taking the MDL
> +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish
> + EXECUTE 2';
> +INSERT INTO t1 VALUES(1,"def");
> +# Need to sync twice. Number 1 is consumed by General Log.
> +SET DEBUG_SYNC= 'now WAIT_FOR locked';
> +SET DEBUG_SYNC= 'now SIGNAL finish';
> +SET DEBUG_SYNC= 'now WAIT_FOR locked';
> +# Now INSERT has a MDL on the non-existent table t1.
> +#
> +# Continue the INSERT once CREATE waits for exclusive lock
> +SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish';
> +# Try to create that table.
> +CREATE TABLE t1 (c1 INT, c2 VARCHAR(100), KEY(c1)) ENGINE=MyISAM;
> +# Insert fails
> +ERROR 42S02: Table 'test.t1' doesn't exist
> +SET DEBUG_SYNC= 'RESET';
> +SHOW TABLES;
> +DROP TABLE IF EXISTS t1;
> +Tables_in_test
> +t1
>
> === modified file 'mysql-test/t/create.test'
> --- a/mysql-test/t/create.test 2009-05-28 21:29:31 +0000
> +++ b/mysql-test/t/create.test 2009-06-23 12:08:59 +0000
> @@ -1,3 +1,5 @@
> +--source include/have_debug_sync.inc
> +
> #
> # Check some special create statements.
> #
> @@ -306,7 +308,7 @@ create table t3 like t1;
> create table t3 like mysqltest.t3;
> --error 1049
> create table non_existing_database.t1 like t1;
> ---error ER_NO_SUCH_TABLE
> +--error ER_TABLE_EXISTS_ERROR
> create table t3 like non_existing_table;
> --error 1050
> create temporary table t3 like t1;
> @@ -1600,3 +1602,51 @@ END ;|
>
> DROP TABLE t1;
> DROP TABLE B;
> +
> +###########################################################################
> +
> +--echo #
> +--echo # Bug#42546 - Backup: RESTORE fails, thinking it finds an existing table
> +--echo #
> +
> +--disable_warnings
> +DROP TABLE IF EXISTS t1;
> +--enable_warnings
> +
> +connect(con1, localhost, root,,);
> +connection con1;
> +
> +--echo # Start insert on the not-yet existing table
> +--echo # Wait after taking the MDL
> +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish
> + EXECUTE 2';
> +--send INSERT INTO t1 VALUES(1,"def")
> +
> +connection default;
> +
> +--echo # Need to sync twice. Number 1 is consumed by General Log.
> +SET DEBUG_SYNC= 'now WAIT_FOR locked';
> +SET DEBUG_SYNC= 'now SIGNAL finish';
> +SET DEBUG_SYNC= 'now WAIT_FOR locked';
> +--echo # Now INSERT has a MDL on the non-existent table t1.
> +
> +--echo #
> +--echo # Continue the INSERT once CREATE waits for exclusive lock
> +SET DEBUG_SYNC= 'mdl_acquire_exclusive_locks_wait SIGNAL finish';
> +--echo # Try to create that table.
> +--send CREATE TABLE t1 (c1 INT, c2 VARCHAR(100), KEY(c1)) ENGINE=MyISAM
> +
> +--echo # Insert fails
> +connection con1;
> +--error ER_NO_SUCH_TABLE
> +--reap
> +
> +connection default;
> +
> +SET DEBUG_SYNC= 'RESET';
> +
> +SHOW TABLES;
> +
> +--disable_warnings
> +DROP TABLE IF EXISTS t1;
> +--enable_warnings
>
> === modified file 'sql/sql_base.cc'
> --- a/sql/sql_base.cc 2009-06-17 07:30:19 +0000
> +++ b/sql/sql_base.cc 2009-06-23 12:08:59 +0000
> @@ -2570,6 +2570,7 @@ bool open_table(THD *thd, TABLE_LIST *ta
> DEBUG_SYNC(thd, "before_open_table_wait_refresh");
> DBUG_RETURN(TRUE);
> }
> + DEBUG_SYNC(thd, "after_open_table_mdl_shared");
> }
>
> /*
>
> === modified file 'sql/sql_table.cc'
> --- a/sql/sql_table.cc 2009-06-02 11:46:19 +0000
> +++ b/sql/sql_table.cc 2009-06-23 12:08:59 +0000
> @@ -4001,7 +4001,7 @@ static bool lock_table_name_if_not_cache
> const char *table_name,
> MDL_request **mdl_request)
> {
> - bool conflict;
> + bool conflict;
>
> if (!(*mdl_request= MDL_request::create(0, db, table_name, thd->mem_root)))
> return TRUE;
> @@ -4019,7 +4019,7 @@ static bool lock_table_name_if_not_cache
> /* Probably OOM. */
> return TRUE;
> }
> - else
> + else
> *mdl_request= NULL;
> }
> else
> @@ -4069,27 +4069,58 @@ bool mysql_create_table(THD *thd, const
>
> if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
> {
> - if (lock_table_name_if_not_cached(thd, db, table_name,
> &target_mdl_request))
> + /*
> + Take exclusive metadata lock on the table name. If we are in LOCK TABLES
> mode,
> + we cannot wait in case of lock conflict for fear of deadlock.
> + */
> + if (thd->locked_tables_mode)
> {
> - result= TRUE;
> - goto unlock;
> + if (lock_table_name_if_not_cached(thd, db, table_name,
> &target_mdl_request))
> + {
> + result= TRUE;
> + goto unlock;
> + }
> }
> - if (!target_mdl_request)
> + else
> {
> - if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
> + if (!(target_mdl_request= MDL_request::create(0, db, table_name,
> thd->mem_root)))
> {
> - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
> - ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
> - table_name);
> - create_info->table_existed= 1;
> - result= FALSE;
> + result= TRUE;
> + goto unlock;
> }
> - else
> + target_mdl_request->set_type(MDL_EXCLUSIVE);
> + thd->mdl_context.add_request(target_mdl_request);
> + if (thd->mdl_context.acquire_exclusive_locks())
> {
> - my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name);
> - result= TRUE;
> + result= TRUE;
> + goto unlock;
> + }
> + DEBUG_SYNC(thd, "locked_table_name");
> + }
> +
> + /* Check if the table exists already */
> + if (!target_mdl_request->ticket->get_cached_object())
> + {
> + char dst_path[FN_REFLEN];
> + uint dst_path_length= build_table_filename(dst_path, sizeof(dst_path),
> + db, table_name, reg_ext, 0);
> + if (!access(dst_path, F_OK))
> + {
> + if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
> + {
> + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
> + ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
> + table_name);
> + create_info->table_existed= 1;
> + result= FALSE;
> + }
> + else
> + {
> + my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name);
> + result= TRUE;
> + }
> + goto unlock;
> }
> - goto unlock;
> }
> }
> result= mysql_create_table_no_lock(thd, db, table_name, create_info,
> @@ -5159,24 +5190,12 @@ bool mysql_create_like_table(THD* thd, T
> if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
> global_schema_lock_guard.lock();
>
> - /*
> - By opening source table and thus acquiring shared metadata lock on it
> - we guarantee that it exists and no concurrent DDL operation will mess
> - with it. Later we also take an exclusive metadata lock on target table
> - name, which makes copying of .frm file, call to ha_create_table() and
> - binlogging atomic against concurrent DML and DDL operations on target
> - table. Thus by holding both these "locks" we ensure that our statement
> - is properly isolated from all concurrent operations which matter.
> - */
> - if (open_tables(thd, &src_table, ¬_used, 0))
> - DBUG_RETURN(TRUE);
> + /* Used by create-big.test */
> + DBUG_EXECUTE_IF("sleep_create_like_before_check_if_exists", my_sleep(6000000););
>
> - strxmov(src_path, src_table->table->s->path.str, reg_ext, NullS);
> -
> - DBUG_EXECUTE_IF("sleep_create_like_before_check_if_exists", my_sleep(6000000););
> -
> - /*
> - Check that destination tables does not exist. Note that its name
> + /*
> + Get exclusive metadata lock on the target table and
> + check that destination table does not exist. Note that its name
> was already checked when it was added to the table list.
> */
> if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
> @@ -5188,14 +5207,35 @@ bool mysql_create_like_table(THD* thd, T
> }
> else
> {
> - if (lock_table_name_if_not_cached(thd, db, table_name,
> &target_mdl_request))
> - goto err;
> - if (!target_mdl_request)
> - goto table_exists;
> - dst_path_length= build_table_filename(dst_path, sizeof(dst_path),
> - db, table_name, reg_ext, 0);
> - if (!access(dst_path, F_OK))
> - goto table_exists;
> + /*
> + Take exclusive metadata lock on the table name. If we are in LOCK TABLES
> mode,
> + we cannot wait in case of lock conflict for fear of deadlock.
> + */
> + if (thd->locked_tables_mode)
> + {
> + if (lock_table_name_if_not_cached(thd, db, table_name,
> &target_mdl_request))
> + goto err;
> + }
> + else
> + {
> + if (!(target_mdl_request= MDL_request::create(0, db, table_name,
> thd->mem_root)))
> + goto err;
> + target_mdl_request->set_type(MDL_EXCLUSIVE);
> + thd->mdl_context.add_request(target_mdl_request);
> + if (thd->mdl_context.acquire_exclusive_locks())
> + goto err;
> + DEBUG_SYNC(thd, "locked_table_name");
> + }
> +
> + /* Check if the table exists already */
> + if (!target_mdl_request->ticket->get_cached_object())
> + {
> + dst_path_length= build_table_filename(dst_path, sizeof(dst_path),
> + db, table_name, reg_ext, 0);
> + if (!access(dst_path, F_OK))
> + goto table_exists;
> + }
> +
> /*
> Make the metadata lock available to open_table() called to
> reopen the table down the road.
> @@ -5203,6 +5243,20 @@ bool mysql_create_like_table(THD* thd, T
> table->mdl_request= target_mdl_request;
> }
>
> + /*
> + By opening source table and thus acquiring shared metadata lock on it
> + we guarantee that it exists and no concurrent DDL operation will mess
> + with it. We already have an exclusive metadata lock on target table
> + name, which makes copying of .frm file, call to ha_create_table() and
> + binlogging atomic against concurrent DML and DDL operations on target
> + table. Thus by holding both these "locks" we ensure that our statement
> + is properly isolated from all concurrent operations which matter.
> + */
> + if (open_tables(thd, &src_table, ¬_used, 0))
> + DBUG_RETURN(TRUE);
> +
> + strxmov(src_path, src_table->table->s->path.str, reg_ext, NullS);
> +
> DBUG_EXECUTE_IF("sleep_create_like_before_copy", my_sleep(6000000););
>
> /*
>
--