List:Commits« Previous MessageNext Message »
From:Dmitry Lenev Date:June 23 2008 1:26pm
Subject:bzr commit into mysql-6.1-fk branch (dlenev:2674) WL#148, WL#3726
View as plain text  
#At file:///home/dlenev/src/bzr/mysql-6.1-fk-mrg-3726/

 2674 Dmitry Lenev	2008-06-23 [merge]
      Merged patches implementing WL#3726 "DDL locking for all
      metadata objects" (aka 3rd milestone of WL#148 "Foreign keys")
      into mysql-6.1-fk tree.
removed:
  .bzr-mysql/
  .bzr-mysql/default.conf
added:
  .bzr-mysql/
  .bzr-mysql/default.conf
  mysql-test/r/drop-no_root.result
  mysql-test/suite/rpl/r/rpl_view_multi.result
  mysql-test/suite/rpl/t/rpl_view_multi.test
  mysql-test/t/drop-no_root.test
  sql/mdl.cc
  sql/mdl.h
  sql/sql_plist.h
modified:
  libmysqld/CMakeLists.txt
  libmysqld/Makefile.am
  mysql-test/include/handler.inc
  mysql-test/include/locktrans.inc
  mysql-test/include/query_cache_sql_prepare.inc
  mysql-test/r/create.result
  mysql-test/r/flush.result
  mysql-test/r/flush_table.result
  mysql-test/r/handler_innodb.result
  mysql-test/r/handler_myisam.result
  mysql-test/r/information_schema.result
  mysql-test/r/kill.result
  mysql-test/r/lock.result
  mysql-test/r/locktrans_innodb.result
  mysql-test/r/locktrans_myisam.result
  mysql-test/r/merge.result
  mysql-test/r/ps_ddl.result
  mysql-test/r/query_cache_ps_no_prot.result
  mysql-test/r/query_cache_ps_ps_prot.result
  mysql-test/r/sp.result
  mysql-test/r/view.result
  mysql-test/r/view_grant.result
  mysql-test/suite/rpl/r/rpl_locktrans_innodb.result
  mysql-test/suite/rpl/r/rpl_locktrans_myisam.result
  mysql-test/suite/rpl/r/rpl_trigger.result
  mysql-test/t/create.test
  mysql-test/t/disabled.def
  mysql-test/t/flush.test
  mysql-test/t/flush_table.test
  mysql-test/t/information_schema.test
  mysql-test/t/kill.test
  mysql-test/t/lock.test
  mysql-test/t/lock_multi.test
  mysql-test/t/merge.test
  mysql-test/t/ps_ddl.test
  mysql-test/t/sp.test
  mysql-test/t/trigger_notembedded.test
  mysql-test/t/view.test
  mysql-test/t/view_grant.test
  sql/CMakeLists.txt
  sql/Makefile.am
  sql/backup/backup_progress.cc
  sql/backup/be_default.cc
  sql/backup/be_default.h
  sql/backup/be_thread.cc
  sql/backup/be_thread.h
  sql/backup/kernel.cc
  sql/event_db_repository.cc
  sql/field.h
  sql/ha_ndbcluster.cc
  sql/ha_ndbcluster_binlog.cc
  sql/ha_partition.cc
  sql/ha_partition.h
  sql/handler.cc
  sql/lock.cc
  sql/log.cc
  sql/log_event.cc
  sql/log_event_old.cc
  sql/mysql_priv.h
  sql/mysqld.cc
  sql/rpl_rli.cc
  sql/rpl_rli.h
  sql/set_var.cc
  sql/si_objects.cc
  sql/sp.cc
  sql/sp_head.cc
  sql/sql_acl.cc
  sql/sql_base.cc
  sql/sql_binlog.cc
  sql/sql_cache.cc
  sql/sql_class.cc
  sql/sql_class.h
  sql/sql_cursor.cc
  sql/sql_db.cc
  sql/sql_delete.cc
  sql/sql_handler.cc
  sql/sql_insert.cc
  sql/sql_load.cc
  sql/sql_parse.cc
  sql/sql_partition.cc
  sql/sql_plugin.cc
  sql/sql_prepare.cc
  sql/sql_rename.cc
  sql/sql_select.cc
  sql/sql_servers.cc
  sql/sql_show.cc
  sql/sql_table.cc
  sql/sql_test.cc
  sql/sql_trigger.cc
  sql/sql_udf.cc
  sql/sql_update.cc
  sql/sql_view.cc
  sql/table.cc
  sql/table.h
  storage/csv/ha_tina.cc
  storage/csv/ha_tina.h
  storage/myisam/ha_myisam.cc
  storage/myisammrg/ha_myisammrg.cc
  tests/mysql_client_test.c

=== added directory '.bzr-mysql'
=== removed directory '.bzr-mysql'
=== added file '.bzr-mysql/default.conf'
--- a/.bzr-mysql/default.conf	1970-01-01 00:00:00 +0000
+++ b/.bzr-mysql/default.conf	2008-06-23 13:26:17 +0000
@@ -0,0 +1,3 @@
+[MYSQL]
+post_commit_to = commits@stripped
+tree_name = "mysql-6.1-fk"

=== removed file '.bzr-mysql/default.conf'
--- a/.bzr-mysql/default.conf	2008-06-22 16:00:17 +0000
+++ b/.bzr-mysql/default.conf	1970-01-01 00:00:00 +0000
@@ -1,3 +0,0 @@
-[MYSQL]
-post_commit_to = "commits@stripped"
-tree_name = "mysql-6.1-fk"

=== modified file 'libmysqld/CMakeLists.txt'
--- a/libmysqld/CMakeLists.txt	2008-06-23 07:14:00 +0000
+++ b/libmysqld/CMakeLists.txt	2008-06-23 13:26:17 +0000
@@ -195,7 +195,7 @@ SET(LIBMYSQLD_SOURCES emb_qcache.cc libm
            ../sql/partition_info.cc ../sql/sql_connect.cc 
            ../sql/scheduler.cc ../sql/sql_audit.cc ../sql/stacktrace.c
            ../sql/ddl_blocker.cc ../sql/si_objects.cc
-           ../sql/event_parse_data.cc ../sql/fk_dd.cc
+           ../sql/event_parse_data.cc ../sql/mdl.cc ../sql/fk_dd.cc
            ${GEN_SOURCES}
            ${LIB_SOURCES})
 

=== modified file 'libmysqld/Makefile.am'
--- a/libmysqld/Makefile.am	2008-06-23 07:14:00 +0000
+++ b/libmysqld/Makefile.am	2008-06-23 13:26:17 +0000
@@ -81,7 +81,7 @@ sqlsources = derror.cc field.cc field_co
 	sql_tablespace.cc \
 	rpl_injector.cc my_user.c partition_info.cc \
 	sql_servers.cc ddl_blocker.cc si_objects.cc sql_audit.cc \
-        event_parse_data.cc fk_dd.cc
+        event_parse_data.cc mdl.cc fk_dd.cc
 
 libmysqld_int_a_SOURCES= $(libmysqld_sources)
 nodist_libmysqld_int_a_SOURCES= $(libmysqlsources) $(sqlsources)

=== modified file 'mysql-test/include/handler.inc'
--- a/mysql-test/include/handler.inc	2007-11-20 17:17:53 +0000
+++ b/mysql-test/include/handler.inc	2008-05-23 13:54:03 +0000
@@ -517,12 +517,15 @@ connect (flush,localhost,root,,);
 connection flush;
 --echo connection: flush
 --send flush tables;
-connection default;
---echo connection: default
+connect (waiter,localhost,root,,);
+connection waiter;
+--echo connection: waiter 
 let $wait_condition=
   select count(*) = 1 from information_schema.processlist
   where state = "Flushing tables";
 --source include/wait_condition.inc
+connection default;
+--echo connection: default
 handler t2 open;
 handler t2 read first;
 handler t1 read next;
@@ -549,12 +552,14 @@ connect (flush,localhost,root,,);
 connection flush;
 --echo connection: flush
 --send rename table t1 to t2;
-connection default;
---echo connection: default
+connection waiter;
+--echo connection: waiter 
 let $wait_condition=
   select count(*) = 1 from information_schema.processlist
   where state = "Waiting for table" and info = "rename table t1 to t2";
 --source include/wait_condition.inc
+connection default;
+--echo connection: default
 handler t2 open;
 handler t2 read first;
 --error ER_NO_SUCH_TABLE

=== modified file 'mysql-test/include/locktrans.inc'
--- a/mysql-test/include/locktrans.inc	2007-10-03 00:27:31 +0000
+++ b/mysql-test/include/locktrans.inc	2008-05-23 13:54:03 +0000
@@ -61,6 +61,7 @@ UNLOCK TABLES;
 CREATE VIEW v1 AS SELECT * FROM t1, t2 WHERE t1.c1 = t2.c2;
 LOCK TABLE v1 IN SHARE MODE;
 LOCK TABLE v1 IN EXCLUSIVE MODE;
+UNLOCK TABLES;
 DROP VIEW v1;
 #
 --echo #

=== modified file 'mysql-test/include/query_cache_sql_prepare.inc'
--- a/mysql-test/include/query_cache_sql_prepare.inc	2007-08-31 16:42:14 +0000
+++ b/mysql-test/include/query_cache_sql_prepare.inc	2008-05-26 12:12:28 +0000
@@ -1,11 +1,13 @@
 ############### include/query_cache_sql_prepare.inc ################
 #
 # This is to see how statements prepared via the PREPARE SQL command
-# go into the query cache: if using parameters they cannot; if not
-# using parameters they can.
+# go into the query cache.
 # Query cache is abbreviated as "QC"
 #
 # Last update:
+# 2008-05-26 Kostja
+#               - Add test coverage for automatic statement reprepare
+#
 # 2007-05-03 ML - Move t/query_cache_sql_prepare.test
 #                 to   include/query_cache_sql_prepare.inc
 #               - Create two toplevel tests sourcing this routine
@@ -490,6 +492,37 @@ use test;
 
 --echo
 --echo ########################################################################
+--echo #
+--echo # Bug#27430 Crash in subquery code when in PS and table DDL changed
+--echo # after PREPARE
+--echo # Check the effect of automatic reprepare on query cache
+--echo #
+--echo ########################################################################
+--disable_warnings
+drop table if exists t1;
+--enable_warnings
+create table t1 (a varchar(255));
+insert into t1 (a) values ("Pack my box with five dozen liquor jugs.");
+flush status;
+prepare stmt from "select a from t1";
+execute stmt;
+set @@global.query_cache_size=0;
+alter table t1 add column b int;
+execute stmt;
+set @@global.query_cache_size=100000;
+execute stmt;
+execute stmt;
+--echo #
+--echo # Sic: ALTER TABLE caused an automatic reprepare 
+--echo # of the prepared statement. Since the query cache was disabled
+--echo # at the time of reprepare, the new prepared statement doesn't
+--echo # work with it.
+--echo # 
+show status like 'Qcache_hits';
+show status like 'Qcache_queries_in_cache';
+--echo # Cleanup
+deallocate prepare stmt;
+drop table t1;
 
 ###############################################################################
 

=== modified file 'mysql-test/r/create.result'
--- a/mysql-test/r/create.result	2008-05-13 15:08:02 +0000
+++ b/mysql-test/r/create.result	2008-05-23 13:54:03 +0000
@@ -785,7 +785,7 @@ drop table t1;
 create table t1 select * from t2;
 ERROR 42S02: Table 'test.t2' doesn't exist
 create table t1 select * from t1;
-ERROR HY000: You can't specify target table 't1' for update in FROM clause
+ERROR 42S02: Table 'test.t1' doesn't exist
 create table t1 select coalesce('a' collate latin1_swedish_ci,'b' collate latin1_bin);
 ERROR HY000: Illegal mix of collations (latin1_swedish_ci,EXPLICIT) and (latin1_bin,EXPLICIT) for operation 'coalesce'
 create table t1 (primary key(a)) select "b" as b;
@@ -802,6 +802,11 @@ ERROR 42S01: Table 't1' already exists
 create table if not exists t1 select 1 as i;
 Warnings:
 Note	1050	Table 't1' already exists
+select * from t1;
+i
+1
+create table if not exists t1 select * from t1;
+ERROR HY000: You can't specify target table 't1' for update in FROM clause
 select * from t1;
 i
 1

=== added file 'mysql-test/r/drop-no_root.result'
--- a/mysql-test/r/drop-no_root.result	1970-01-01 00:00:00 +0000
+++ b/mysql-test/r/drop-no_root.result	2008-06-04 18:10:12 +0000
@@ -0,0 +1,26 @@
+
+# --
+# -- Bug#26704: Failing DROP DATABASE brings mysql-client out of sync.
+# --
+
+DROP DATABASE IF EXISTS mysql_test;
+
+CREATE DATABASE mysql_test;
+CREATE TABLE mysql_test.t1(c INT);
+use mysql_test;
+
+chmod 000 mysql_test/t1.frm
+
+DROP DATABASE mysql_test;
+
+SELECT DATABASE();
+DATABASE()
+mysql_test
+
+rm mysql_test/t1.MYD mysql_test/t1.MYI
+
+DROP DATABASE mysql_test;
+
+use test;
+
+# -- End of Bug#26704.

=== modified file 'mysql-test/r/flush.result'
--- a/mysql-test/r/flush.result	2008-03-23 16:35:00 +0000
+++ b/mysql-test/r/flush.result	2008-05-23 13:54:03 +0000
@@ -33,6 +33,9 @@ flush tables with read lock;
 ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
 lock table t1 read;
 flush tables with read lock;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+unlock tables;
+flush tables with read lock;
 lock table t1 write;
 ERROR HY000: Can't execute the query because you have a conflicting read lock
 lock table t1 read;
@@ -46,6 +49,7 @@ flush tables with read lock;
 ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
 lock table t1 read, t2 read, t3 read;
 flush tables with read lock;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
 unlock tables;
 drop table t1, t2, t3;
 create table t1 (c1 int);
@@ -69,6 +73,7 @@ ERROR HY000: Can't execute the given com
 unlock tables;
 lock tables t1 read;
 flush tables with read lock;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
 unlock tables;
 drop table t1, t2;
 set session low_priority_updates=default;

=== modified file 'mysql-test/r/flush_table.result'
--- a/mysql-test/r/flush_table.result	2006-10-04 21:48:24 +0000
+++ b/mysql-test/r/flush_table.result	2008-05-23 13:54:03 +0000
@@ -3,21 +3,14 @@ create table t1 (a int not null auto_inc
 insert into t1 values(0);
 lock table t1 read;
 flush table t1;
+ERROR HY000: Table 't1' was locked with a READ lock and can't be updated
+unlock tables;
+lock table t1 write;
+flush table t1;
 check table t1;
 Table	Op	Msg_type	Msg_text
 test.t1	check	status	OK
 unlock tables;
-lock table t1 read;
-lock table t1 read;
-flush table t1;
-select * from t1;
-a
-1
-unlock tables;
-select * from t1;
-a
-1
-unlock tables;
 lock table t1 write;
 lock table t1 read;
 flush table t1;
@@ -26,7 +19,7 @@ a
 1
 unlock tables;
 unlock tables;
-lock table t1 read;
+lock table t1 write;
 lock table t1 write;
 flush table t1;
 select * from t1;

=== modified file 'mysql-test/r/handler_innodb.result'
--- a/mysql-test/r/handler_innodb.result	2007-11-20 17:17:53 +0000
+++ b/mysql-test/r/handler_innodb.result	2008-05-23 13:54:03 +0000
@@ -547,6 +547,7 @@ c1
 1
 connection: flush
 flush tables;;
+connection: waiter 
 connection: default
 handler t2 open;
 handler t2 read first;
@@ -566,6 +567,7 @@ handler t1 read first;
 c1
 connection: flush
 rename table t1 to t2;;
+connection: waiter 
 connection: default
 handler t2 open;
 handler t2 read first;

=== modified file 'mysql-test/r/handler_myisam.result'
--- a/mysql-test/r/handler_myisam.result	2007-11-20 17:17:53 +0000
+++ b/mysql-test/r/handler_myisam.result	2008-05-23 13:54:03 +0000
@@ -547,6 +547,7 @@ c1
 1
 connection: flush
 flush tables;;
+connection: waiter 
 connection: default
 handler t2 open;
 handler t2 read first;
@@ -566,6 +567,7 @@ handler t1 read first;
 c1
 connection: flush
 rename table t1 to t2;;
+connection: waiter 
 connection: default
 handler t2 open;
 handler t2 read first;

=== modified file 'mysql-test/r/information_schema.result'
--- a/mysql-test/r/information_schema.result	2008-04-19 20:34:56 +0000
+++ b/mysql-test/r/information_schema.result	2008-05-23 13:54:03 +0000
@@ -1715,3 +1715,37 @@ ERROR 42000: SELECT command denied to us
 DROP TABLE t1,t2;
 DROP DATABASE testdb1;
 DROP USER mysqluser_35955@localhost;
+USE test;
+#
+# Additional test for WL#3726 "DDL locking for all metadata objects"
+# To avoid possible deadlocks process of filling of I_S tables should
+# use high-priority metadata lock requests when opening tables.
+# Below we just test that we really use high-priority lock request
+# since reproducing a deadlock will require much more complex test.
+#
+drop tables if exists t1, t2, t3;
+create table t1 (i int);
+create table t2 (j int primary key auto_increment);
+# Switching to connection 'con3726_1'
+lock table t2 read;
+# Switching to connection 'con3726_2'
+# RENAME below will be blocked by 'lock table t2 read' above but
+# will add two pending requests for exclusive metadata locks.
+rename table t2 to t3;
+# Switching to connection 'default'
+# These statements should not be blocked by pending lock requests
+select table_name, column_name, data_type from information_schema.columns
+where table_schema = 'test' and table_name in ('t1', 't2');
+table_name	column_name	data_type
+t1	i	int
+t2	j	int
+select table_name, auto_increment from information_schema.tables
+where table_schema = 'test' and table_name in ('t1', 't2');
+table_name	auto_increment
+t1	NULL
+t2	1
+# Switching to connection 'con3726_1'
+unlock tables;
+# Switching to connection 'con3726_2'
+# Switching to connection 'default'
+drop tables t1, t3;

=== modified file 'mysql-test/r/kill.result'
--- a/mysql-test/r/kill.result	2008-03-27 19:02:15 +0000
+++ b/mysql-test/r/kill.result	2008-05-23 13:54:03 +0000
@@ -138,4 +138,107 @@ KILL CONNECTION_ID();
 # of close of the connection socket
 SELECT 1;
 Got one of the listed errors
+#
+# Additional test for WL#3726 "DDL locking for all metadata objects"
+# Check that DDL and DML statements waiting for metadata locks can
+# be killed. Note that we don't cover all situations here since it
+# can be tricky to write test case for some of them (e.g. REPAIR or
+# ALTER and other statements under LOCK TABLES).
+#
+drop tables if exists t1, t2, t3;
+create table t1 (i int primary key);
+# Test for RENAME TABLE
+# Switching to connection 'blocker'
+lock table t1 read;
+# Switching to connection 'ddl'
+rename table t1 to t2;
+# Switching to connection 'default'
+kill query ID;
+# Switching to connection 'ddl'
+ERROR 70100: Query execution was interrupted
+# Test for DROP TABLE
+drop table t1;
+# Switching to connection 'default'
+kill query ID;
+# Switching to connection 'ddl'
+ERROR 70100: Query execution was interrupted
+# Test for CREATE TRIGGER
+create trigger t1_bi before insert on t1 for each row set @a:=1;
+# Switching to connection 'default'
+kill query ID;
+# Switching to connection 'ddl'
+ERROR 70100: Query execution was interrupted
+#
+# Tests for various kinds of ALTER TABLE
+#
+# Full-blown ALTER which should copy table
+alter table t1 add column j int;
+# Switching to connection 'default'
+kill query ID;
+# Switching to connection 'ddl'
+ERROR 70100: Query execution was interrupted
+# Two kinds of simple ALTER
+alter table t1 rename to t2;
+# Switching to connection 'default'
+kill query ID;
+# Switching to connection 'ddl'
+ERROR 70100: Query execution was interrupted
+alter table t1 disable keys;
+# Switching to connection 'default'
+kill query ID;
+# Switching to connection 'ddl'
+ERROR 70100: Query execution was interrupted
+# Fast ALTER
+alter table t1 alter column i set default 100;
+# Switching to connection 'default'
+kill query ID;
+# Switching to connection 'ddl'
+ERROR 70100: Query execution was interrupted
+# Special case which is triggered only for MERGE tables.
+# Switching to connection 'blocker'
+unlock tables;
+create table t2 (i int primary key) engine=merge union=(t1);
+lock tables t2 read;
+# Switching to connection 'ddl'
+alter table t2 alter column i set default 100;
+# Switching to connection 'default'
+kill query ID;
+# Switching to connection 'ddl'
+ERROR 70100: Query execution was interrupted
+# Test for DML waiting for meta-data lock
+# Switching to connection 'blocker'
+unlock tables;
+drop table t2;
+create table t2 (k int);
+lock tables t1 read;
+# Switching to connection 'ddl'
+rename tables t1 to t3, t2 to t1;
+# Switching to connection 'dml'
+insert into t2 values (1);
+# Switching to connection 'default'
+kill query ID2;
+# Switching to connection 'dml'
+ERROR 70100: Query execution was interrupted
+# Switching to connection 'blocker'
+unlock tables;
+# Switching to connection 'ddl'
+# Test for DML waiting for tables to be flushed
+# Switching to connection 'blocker'
+lock tables t1 read;
+# Switching to connection 'ddl'
+# Let us mark locked table t1 as old
+flush tables;
+# Switching to connection 'dml'
+select * from t1;
+# Switching to connection 'default'
+kill query ID2;
+# Switching to connection 'dml'
+ERROR 70100: Query execution was interrupted
+# Switching to connection 'blocker'
+unlock tables;
+# Switching to connection 'ddl'
+# Cleanup.
+# Switching to connection 'default'
+drop table t3;
+drop table t1;
 set @@global.concurrent_insert= @old_concurrent_insert;

=== modified file 'mysql-test/r/lock.result'
--- a/mysql-test/r/lock.result	2007-10-03 00:27:31 +0000
+++ b/mysql-test/r/lock.result	2008-06-11 11:49:58 +0000
@@ -128,13 +128,14 @@ select * from v_bug5719;
 1
 1
 drop view v_bug5719;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
 
 sic: did not left LOCK TABLES mode automatically
 
 select * from t1;
 ERROR HY000: Table 't1' was not locked with LOCK TABLES
 unlock tables;
-create view v_bug5719 as select * from t1;
+create or replace view v_bug5719 as select * from t1;
 lock tables v_bug5719 write;
 select * from v_bug5719;
 a
@@ -173,3 +174,48 @@ Cleanup.
 
 drop table t2, t3;
 End of 5.1 tests.
+#
+# Ensure that FLUSH TABLES doesn't substitute a base locked table
+# with a temporary one.
+#
+drop table if exists t1, t2;
+create table t1 (a int);
+create table t2 (a int);
+lock table t1 write, t2 write;
+create temporary table t1 (a int);
+flush table t1;
+drop temporary table t1;
+select * from t1;
+a
+unlock tables;
+drop table t1, t2;
+#
+# Ensure that REPAIR .. USE_FRM works under LOCK TABLES.
+#
+drop table if exists t1, t2;
+create table t1 (a int);
+create table t2 (a int);
+lock table t1 write, t2 write;
+repair table t1 use_frm;
+Table	Op	Msg_type	Msg_text
+test.t1	repair	status	OK
+repair table t1 use_frm;
+Table	Op	Msg_type	Msg_text
+test.t1	repair	status	OK
+select * from t1;
+a
+select * from t2;
+a
+repair table t2 use_frm;
+Table	Op	Msg_type	Msg_text
+test.t2	repair	status	OK
+repair table t2 use_frm;
+Table	Op	Msg_type	Msg_text
+test.t2	repair	status	OK
+select * from t1;
+a
+unlock tables;
+drop table t1, t2;
+#
+# End of 6.0 tests.
+#

=== modified file 'mysql-test/r/locktrans_innodb.result'
--- a/mysql-test/r/locktrans_innodb.result	2008-05-21 19:44:56 +0000
+++ b/mysql-test/r/locktrans_innodb.result	2008-05-23 13:54:03 +0000
@@ -43,6 +43,7 @@ UNLOCK TABLES;
 CREATE VIEW v1 AS SELECT * FROM t1, t2 WHERE t1.c1 = t2.c2;
 LOCK TABLE v1 IN SHARE MODE;
 LOCK TABLE v1 IN EXCLUSIVE MODE;
+UNLOCK TABLES;
 DROP VIEW v1;
 #
 # Locking INFORMATION_SCHEMA fails on missing privileges.

=== modified file 'mysql-test/r/locktrans_myisam.result'
--- a/mysql-test/r/locktrans_myisam.result	2008-05-21 19:44:56 +0000
+++ b/mysql-test/r/locktrans_myisam.result	2008-05-23 13:54:03 +0000
@@ -43,6 +43,7 @@ Warning	1614	Converted to non-transactio
 LOCK TABLE v1 IN EXCLUSIVE MODE;
 Warnings:
 Warning	1614	Converted to non-transactional lock on 'v1'
+UNLOCK TABLES;
 DROP VIEW v1;
 #
 # Locking INFORMATION_SCHEMA fails on missing privileges.

=== modified file 'mysql-test/r/merge.result'
--- a/mysql-test/r/merge.result	2008-05-01 09:34:54 +0000
+++ b/mysql-test/r/merge.result	2008-05-22 22:42:32 +0000
@@ -2022,3 +2022,39 @@ id	select_type	table	type	possible_keys	
 1	SIMPLE	NULL	NULL	NULL	NULL	NULL	NULL	NULL	Select tables optimized away
 DROP TABLE t1, t2, t3, t4;
 End of 5.1 tests
+#
+# An additional test case for Bug#27430 Crash in subquery code
+# when in PS and table DDL changed after PREPARE
+#
+# Test merge table with too many merge children.
+#
+drop table if exists t_parent;
+set @save_table_definition_cache=@@global.table_definition_cache;
+#
+# Set @@global.table_definition_cache to minimum
+#
+set @@global.table_definition_cache=256;
+set @a=null;
+#
+# Create 256 merge children
+#
+set @a=concat("create table t_parent (a int) union(", @a,
+") insert_method=first engine=mrg_myisam");
+prepare stmt from @a;
+execute stmt;
+prepare stmt from "select * from t_parent";
+execute stmt;
+ERROR HY000: Prepared statement needs to be re-prepared
+execute stmt;
+ERROR HY000: Prepared statement needs to be re-prepared
+execute stmt;
+ERROR HY000: Prepared statement needs to be re-prepared
+deallocate prepare stmt;
+#
+# Create merge parent 
+#
+#
+# Cleanup
+#
+drop table t_parent;
+set @@global.table_definition_cache=@save_table_definition_cache;

=== modified file 'mysql-test/r/ps_ddl.result'
--- a/mysql-test/r/ps_ddl.result	2008-05-29 12:56:51 +0000
+++ b/mysql-test/r/ps_ddl.result	2008-06-23 13:26:17 +0000
@@ -1639,7 +1639,7 @@ SUCCESS
 
 drop temporary table t2;
 execute stmt;
-call p_verify_reprepare_count(1);
+call p_verify_reprepare_count(0);
 SUCCESS
 
 drop table t2;

=== modified file 'mysql-test/r/query_cache_ps_no_prot.result'
--- a/mysql-test/r/query_cache_ps_no_prot.result	2007-08-31 16:42:14 +0000
+++ b/mysql-test/r/query_cache_ps_no_prot.result	2008-05-26 12:12:28 +0000
@@ -529,5 +529,46 @@ DROP DATABASE mysqltest1;
 use test;
 
 ########################################################################
+#
+# Bug#27430 Crash in subquery code when in PS and table DDL changed
+# after PREPARE
+# Check the effect of automatic reprepare on query cache
+#
+########################################################################
+drop table if exists t1;
+create table t1 (a varchar(255));
+insert into t1 (a) values ("Pack my box with five dozen liquor jugs.");
+flush status;
+prepare stmt from "select a from t1";
+execute stmt;
+a
+Pack my box with five dozen liquor jugs.
+set @@global.query_cache_size=0;
+alter table t1 add column b int;
+execute stmt;
+a
+Pack my box with five dozen liquor jugs.
+set @@global.query_cache_size=100000;
+execute stmt;
+a
+Pack my box with five dozen liquor jugs.
+execute stmt;
+a
+Pack my box with five dozen liquor jugs.
+#
+# Sic: ALTER TABLE caused an automatic reprepare 
+# of the prepared statement. Since the query cache was disabled
+# at the time of reprepare, the new prepared statement doesn't
+# work with it.
+# 
+show status like 'Qcache_hits';
+Variable_name	Value
+Qcache_hits	0
+show status like 'Qcache_queries_in_cache';
+Variable_name	Value
+Qcache_queries_in_cache	0
+# Cleanup
+deallocate prepare stmt;
+drop table t1;
 set @@global.query_cache_size=@initial_query_cache_size;
 flush status;

=== modified file 'mysql-test/r/query_cache_ps_ps_prot.result'
--- a/mysql-test/r/query_cache_ps_ps_prot.result	2007-08-31 16:42:14 +0000
+++ b/mysql-test/r/query_cache_ps_ps_prot.result	2008-05-26 12:12:28 +0000
@@ -529,5 +529,46 @@ DROP DATABASE mysqltest1;
 use test;
 
 ########################################################################
+#
+# Bug#27430 Crash in subquery code when in PS and table DDL changed
+# after PREPARE
+# Check the effect of automatic reprepare on query cache
+#
+########################################################################
+drop table if exists t1;
+create table t1 (a varchar(255));
+insert into t1 (a) values ("Pack my box with five dozen liquor jugs.");
+flush status;
+prepare stmt from "select a from t1";
+execute stmt;
+a
+Pack my box with five dozen liquor jugs.
+set @@global.query_cache_size=0;
+alter table t1 add column b int;
+execute stmt;
+a
+Pack my box with five dozen liquor jugs.
+set @@global.query_cache_size=100000;
+execute stmt;
+a
+Pack my box with five dozen liquor jugs.
+execute stmt;
+a
+Pack my box with five dozen liquor jugs.
+#
+# Sic: ALTER TABLE caused an automatic reprepare 
+# of the prepared statement. Since the query cache was disabled
+# at the time of reprepare, the new prepared statement doesn't
+# work with it.
+# 
+show status like 'Qcache_hits';
+Variable_name	Value
+Qcache_hits	0
+show status like 'Qcache_queries_in_cache';
+Variable_name	Value
+Qcache_queries_in_cache	0
+# Cleanup
+deallocate prepare stmt;
+drop table t1;
 set @@global.query_cache_size=@initial_query_cache_size;
 flush status;

=== modified file 'mysql-test/r/sp.result'
--- a/mysql-test/r/sp.result	2008-05-22 18:40:15 +0000
+++ b/mysql-test/r/sp.result	2008-06-23 13:26:17 +0000
@@ -1085,11 +1085,9 @@ select f0()|
 f0()
 100
 select * from v0|
-f0()
-100
+ERROR HY000: Table 'v0' was not locked with LOCK TABLES
 select *, f0() from v0, (select 123) as d1|
-f0()	123	f0()
-100	123	100
+ERROR HY000: Table 'v0' was not locked with LOCK TABLES
 select id, f3() from t1|
 ERROR HY000: Table 't1' was not locked with LOCK TABLES
 select f4()|

=== modified file 'mysql-test/r/view.result'
--- a/mysql-test/r/view.result	2008-04-21 07:30:39 +0000
+++ b/mysql-test/r/view.result	2008-06-23 13:26:17 +0000
@@ -1102,10 +1102,8 @@ select * from v1;
 a
 select * from t2;
 ERROR HY000: Table 't2' was not locked with LOCK TABLES
-drop view v1;
-drop table t1, t2;
-ERROR HY000: Table 't1' was locked with a READ lock and can't be updated
 unlock tables;
+drop view v1;
 drop table t1, t2;
 create table t1 (a int);
 create view v1 as select * from t1 where a < 2 with check option;

=== modified file 'mysql-test/r/view_grant.result'
--- a/mysql-test/r/view_grant.result	2008-03-22 08:02:24 +0000
+++ b/mysql-test/r/view_grant.result	2008-05-23 13:54:03 +0000
@@ -779,9 +779,9 @@ GRANT CREATE VIEW ON db26813.v2 TO u2681
 GRANT DROP, CREATE VIEW ON db26813.v3 TO u26813@localhost;
 GRANT SELECT ON db26813.t1 TO u26813@localhost;
 ALTER VIEW v1 AS SELECT f2 FROM t1;
-ERROR 42000: Access denied; you need the SUPER privilege for this operation
+ERROR 42000: CREATE VIEW command denied to user 'u26813'@'localhost' for table 'v1'
 ALTER VIEW v2 AS SELECT f2 FROM t1;
-ERROR 42000: Access denied; you need the SUPER privilege for this operation
+ERROR 42000: DROP command denied to user 'u26813'@'localhost' for table 'v2'
 ALTER VIEW v3 AS SELECT f2 FROM t1;
 ERROR 42000: Access denied; you need the SUPER privilege for this operation
 SHOW CREATE VIEW v3;

=== modified file 'mysql-test/suite/rpl/r/rpl_locktrans_innodb.result'
--- a/mysql-test/suite/rpl/r/rpl_locktrans_innodb.result	2008-05-21 19:44:56 +0000
+++ b/mysql-test/suite/rpl/r/rpl_locktrans_innodb.result	2008-05-23 13:54:03 +0000
@@ -49,6 +49,7 @@ UNLOCK TABLES;
 CREATE VIEW v1 AS SELECT * FROM t1, t2 WHERE t1.c1 = t2.c2;
 LOCK TABLE v1 IN SHARE MODE;
 LOCK TABLE v1 IN EXCLUSIVE MODE;
+UNLOCK TABLES;
 DROP VIEW v1;
 #
 # Locking INFORMATION_SCHEMA fails on missing privileges.

=== modified file 'mysql-test/suite/rpl/r/rpl_locktrans_myisam.result'
--- a/mysql-test/suite/rpl/r/rpl_locktrans_myisam.result	2008-05-21 19:44:56 +0000
+++ b/mysql-test/suite/rpl/r/rpl_locktrans_myisam.result	2008-05-23 13:54:03 +0000
@@ -49,6 +49,7 @@ Warning	1614	Converted to non-transactio
 LOCK TABLE v1 IN EXCLUSIVE MODE;
 Warnings:
 Warning	1614	Converted to non-transactional lock on 'v1'
+UNLOCK TABLES;
 DROP VIEW v1;
 #
 # Locking INFORMATION_SCHEMA fails on missing privileges.

=== modified file 'mysql-test/suite/rpl/r/rpl_trigger.result'
--- a/mysql-test/suite/rpl/r/rpl_trigger.result	2007-12-18 09:07:08 +0000
+++ b/mysql-test/suite/rpl/r/rpl_trigger.result	2008-06-11 11:49:58 +0000
@@ -890,8 +890,6 @@ s
 @
 root@localhost
 DROP TRIGGER trg1;
-Warnings:
-Warning	1454	No definer attribute for trigger 'test'.'trg1'. The trigger will be activated under the authorization of the invoker, which may have insufficient privileges. Please recreate the trigger.
 DROP TABLE t1;
 DROP TABLE t2;
 STOP SLAVE;

=== added file 'mysql-test/suite/rpl/r/rpl_view_multi.result'
--- a/mysql-test/suite/rpl/r/rpl_view_multi.result	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/rpl/r/rpl_view_multi.result	2008-05-26 06:55:16 +0000
@@ -0,0 +1,90 @@
+stop slave;
+drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
+reset master;
+reset slave;
+drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
+start slave;
+#
+# Bug #25144 "replication / binlog with view breaks".
+# Statements that used views didn't ensure that view were not modified
+# during their execution. Indeed this led to incorrect binary log with
+# statement based logging and as result to broken replication.
+#
+drop tables if exists t1, t2;
+drop view if exists v1;
+# Syncing slave with master and switching to connection 'slave'
+# Switching to connection 'master'
+create table t1 (i int);
+create table t2 (i int);
+create view v1 as select * from t1;
+# First we try to concurrently execute statement that uses view
+# and statement that drops it. We use "user" locks as means to
+# suspend execution of first statement once it opens our view.
+select get_lock("lock_bg25144", 1);
+get_lock("lock_bg25144", 1)
+1
+# Switching to connection 'master1'
+insert into v1 values (get_lock("lock_bg25144", 100));
+# Switching to connection 'master2'
+drop view v1;
+# Switching to connection 'master'
+select release_lock("lock_bg25144");
+release_lock("lock_bg25144")
+1
+# Switching to connection 'master1'
+select release_lock("lock_bg25144");
+release_lock("lock_bg25144")
+1
+# Switching to connection 'master2'
+# Switching to connection 'master'
+# Check that insertion through view did happen.
+select * from t1;
+i
+1
+# Syncing slave with master and switching to connection 'slave'
+# Check that slave was able to replicate this sequence
+# which means that we got correct binlog order.
+select * from t1;
+i
+1
+# Switching to connection 'master'
+# Now we will repeat the test by trying concurrently execute
+# statement that uses a view and statement that alters it.
+create view v1 as select * from t1;
+select get_lock("lock_bg25144", 1);
+get_lock("lock_bg25144", 1)
+1
+# Switching to connection 'master1'
+insert into v1 values (get_lock("lock_bg25144", 100));
+# Switching to connection 'master2'
+alter view v1 as select * from t2;
+# Switching to connection 'master'
+select release_lock("lock_bg25144");
+release_lock("lock_bg25144")
+1
+# Switching to connection 'master1'
+select release_lock("lock_bg25144");
+release_lock("lock_bg25144")
+1
+# Switching to connection 'master2'
+# Switching to connection 'master'
+# Second insertion should go to t1 as well.
+select * from t1;
+i
+1
+1
+select * from t2;
+i
+# Syncing slave with master and switching to connection 'slave'
+# Now let us check that statements were logged in proper order
+# So we have same result on slave.
+select * from t1;
+i
+1
+1
+select * from t2;
+i
+# Switching to connection 'master'
+drop table t1, t2;
+drop view v1;
+# Syncing slave with master and switching to connection 'slave'

=== added file 'mysql-test/suite/rpl/t/rpl_view_multi.test'
--- a/mysql-test/suite/rpl/t/rpl_view_multi.test	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/rpl/t/rpl_view_multi.test	2008-05-26 06:55:16 +0000
@@ -0,0 +1,134 @@
+#
+# This file contains test cases for bugs which involve views, several
+# concurren connections and manifest themselves as wrong binary log
+# sequence which results in broken replication. In principle we are
+# mostly interested in SBR here but this test will also work with RBR.
+#
+--source include/master-slave.inc
+
+--echo #
+--echo # Bug #25144 "replication / binlog with view breaks".
+--echo # Statements that used views didn't ensure that view were not modified
+--echo # during their execution. Indeed this led to incorrect binary log with
+--echo # statement based logging and as result to broken replication.
+--echo #
+
+--disable_warnings
+drop tables if exists t1, t2;
+drop view if exists v1;
+--enable_warnings
+--echo # Syncing slave with master and switching to connection 'slave'
+--sync_slave_with_master
+
+connect (master2,127.0.0.1,root,,test,$MASTER_MYPORT,);
+
+--echo # Switching to connection 'master'
+connection master;
+create table t1 (i int);
+create table t2 (i int);
+create view v1 as select * from t1;
+
+--echo # First we try to concurrently execute statement that uses view
+--echo # and statement that drops it. We use "user" locks as means to
+--echo # suspend execution of first statement once it opens our view.
+select get_lock("lock_bg25144", 1);
+
+--echo # Switching to connection 'master1'
+connection master1;
+--send insert into v1 values (get_lock("lock_bg25144", 100))
+
+--echo # Switching to connection 'master2'
+connection master2;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "User lock" and info like "insert into v1 %lock_bg25144%";
+--source include/wait_condition.inc
+--send drop view v1
+
+--echo # Switching to connection 'master'
+connection master;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "Waiting for table" and info = "drop view v1";
+--source include/wait_condition.inc
+
+select release_lock("lock_bg25144");
+
+--echo # Switching to connection 'master1'
+connection master1;
+--reap
+select release_lock("lock_bg25144");
+
+--echo # Switching to connection 'master2'
+connection master2;
+--reap
+
+--echo # Switching to connection 'master'
+connection master;
+--echo # Check that insertion through view did happen.
+select * from t1;
+--echo # Syncing slave with master and switching to connection 'slave'
+--sync_slave_with_master
+--echo # Check that slave was able to replicate this sequence
+--echo # which means that we got correct binlog order.
+select * from t1;
+
+--echo # Switching to connection 'master'
+connection master;
+--echo # Now we will repeat the test by trying concurrently execute
+--echo # statement that uses a view and statement that alters it.
+create view v1 as select * from t1;
+
+select get_lock("lock_bg25144", 1);
+
+--echo # Switching to connection 'master1'
+connection master1;
+--send insert into v1 values (get_lock("lock_bg25144", 100))
+
+--echo # Switching to connection 'master2'
+connection master2;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "User lock" and info like "insert into v1 %lock_bg25144%";
+--source include/wait_condition.inc
+--send alter view v1 as select * from t2
+
+--echo # Switching to connection 'master'
+connection master;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "Waiting for table" and
+  info = "alter view v1 as select * from t2";
+--source include/wait_condition.inc
+
+select release_lock("lock_bg25144");
+
+--echo # Switching to connection 'master1'
+connection master1;
+--reap
+select release_lock("lock_bg25144");
+
+--echo # Switching to connection 'master2'
+connection master2;
+--reap
+
+--echo # Switching to connection 'master'
+connection master;
+
+--echo # Second insertion should go to t1 as well.
+select * from t1;
+select * from t2;
+
+--echo # Syncing slave with master and switching to connection 'slave'
+--sync_slave_with_master
+--echo # Now let us check that statements were logged in proper order
+--echo # So we have same result on slave.
+select * from t1;
+select * from t2;
+
+--echo # Switching to connection 'master'
+connection master;
+drop table t1, t2;
+drop view v1;
+--echo # Syncing slave with master and switching to connection 'slave'
+--sync_slave_with_master

=== modified file 'mysql-test/t/create.test'
--- a/mysql-test/t/create.test	2008-05-13 15:08:02 +0000
+++ b/mysql-test/t/create.test	2008-05-23 13:54:03 +0000
@@ -683,8 +683,8 @@ drop table t1;
 # Error during open_and_lock_tables() of tables
 --error ER_NO_SUCH_TABLE
 create table t1 select * from t2;
-# Rather special error which also caught during open tables pahse
---error ER_UPDATE_TABLE_USED
+# A special case which is also caught during open tables pahse
+--error ER_NO_SUCH_TABLE
 create table t1 select * from t1;
 # Error which happens before select_create::prepare()
 --error ER_CANT_AGGREGATE_2COLLATIONS
@@ -705,6 +705,10 @@ create table t1 (i int);
 --error ER_TABLE_EXISTS_ERROR
 create table t1 select 1 as i;
 create table if not exists t1 select 1 as i;
+select * from t1;
+# Error which is detected after successfull table open.
+--error ER_UPDATE_TABLE_USED
+create table if not exists t1 select * from t1;
 select * from t1;
 # Error before select_create::prepare()
 --error ER_CANT_AGGREGATE_2COLLATIONS

=== modified file 'mysql-test/t/disabled.def'
--- a/mysql-test/t/disabled.def	2008-06-02 15:47:07 +0000
+++ b/mysql-test/t/disabled.def	2008-06-23 13:26:17 +0000
@@ -43,3 +43,4 @@ sort_buffer_size_basic_32   : Bug#36875 
 key_buffer_size_basic_32    : Bug#36876 main.key_buffer_size_basic_32 fails on some systems
 max_heap_table_size_basic_32 : Bug#36877 main.max_heap_table_size_basic_32 fails on some systems
 tmp_table_size_basic_32     : Bug#36878 main.tmp_table_size_basic_32 fails on some systems
+merge                       : Temporarily broken by WL#3726

=== added file 'mysql-test/t/drop-no_root.test'
--- a/mysql-test/t/drop-no_root.test	1970-01-01 00:00:00 +0000
+++ b/mysql-test/t/drop-no_root.test	2008-06-04 18:10:12 +0000
@@ -0,0 +1,52 @@
+# This test uses chmod, can't be run with root permissions
+--source include/not_as_root.inc
+
+###########################################################################
+
+--echo
+--echo # --
+--echo # -- Bug#26704: Failing DROP DATABASE brings mysql-client out of sync.
+--echo # --
+
+--echo
+--disable_warnings
+DROP DATABASE IF EXISTS mysql_test;
+--enable_warnings
+
+--echo
+CREATE DATABASE mysql_test;
+CREATE TABLE mysql_test.t1(c INT);
+
+use mysql_test;
+
+--echo
+--echo chmod 000 mysql_test/t1.frm
+--chmod 0000 $MYSQLTEST_VARDIR/master-data/mysql_test/t1.frm
+
+# NOTE: ER_DB_DROP_RMDIR contains errno, which can be different on different
+# platforms.
+
+--echo
+--disable_result_log
+--error ER_DB_DROP_RMDIR
+DROP DATABASE mysql_test;
+--enable_result_log
+
+--echo
+SELECT DATABASE();
+
+--echo
+--echo rm mysql_test/t1.MYD mysql_test/t1.MYI
+--exec rm $MYSQLTEST_VARDIR/master-data/mysql_test/t1.MYD
+--exec rm $MYSQLTEST_VARDIR/master-data/mysql_test/t1.MYI
+
+--echo
+DROP DATABASE mysql_test;
+
+--echo
+use test;
+
+--echo
+--echo # -- End of Bug#26704.
+
+###########################################################################

=== modified file 'mysql-test/t/flush.test'
--- a/mysql-test/t/flush.test	2008-03-23 16:35:00 +0000
+++ b/mysql-test/t/flush.test	2008-05-23 13:54:03 +0000
@@ -68,10 +68,13 @@ drop table t1;
 create table t1 (c1 int);
 lock table t1 write;
 # Cannot get the global read lock with write locked tables.
---error 1192
+--error ER_LOCK_OR_ACTIVE_TRANSACTION 
 flush tables with read lock;
 lock table t1 read;
-# Can get the global read lock with read locked tables.
+# Cannot get the global read lock with read locked tables.
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+flush tables with read lock;
+unlock tables;
 flush tables with read lock;
 --error 1223
 lock table t1 write;
@@ -84,12 +87,12 @@ create table t2 (c1 int);
 create table t3 (c1 int);
 lock table t1 read, t2 read, t3 write;
 # Cannot get the global read lock with write locked tables.
---error 1192
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
 flush tables with read lock;
 lock table t1 read, t2 read, t3 read;
-# Can get the global read lock with read locked tables.
+# Cannot get the global read lock with read locked tables.
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
 flush tables with read lock;
-# Release all table locks and the global read lock.
 unlock tables;
 drop table t1, t2, t3;
 
@@ -157,6 +160,7 @@ flush tables with read lock;
 unlock tables;
 
 lock tables t1 read;
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
 flush tables with read lock;
 unlock tables;
 

=== modified file 'mysql-test/t/flush_table.test'
--- a/mysql-test/t/flush_table.test	2006-01-04 10:20:28 +0000
+++ b/mysql-test/t/flush_table.test	2008-05-23 13:54:03 +0000
@@ -15,30 +15,21 @@ insert into t1 values(0);
 # Test for with read lock + flush
 
 lock table t1 read;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
 flush table t1;
-check table t1;
 unlock tables;
 
-# Test for with 2 read lock in different thread + flush
+# Test for with write lock + flush
 
-lock table t1 read;
-connect (locker,localhost,root,,test);
-connection locker;
-lock table t1 read;
-connection default;
-send flush table t1;
-connection locker;
---sleep 2
-select * from t1;
-unlock tables;
-connection default;
-reap;
-select * from t1;
+lock table t1 write;
+flush table t1;
+check table t1;
 unlock tables;
 
 # Test for with a write lock and a waiting read lock + flush
 
 lock table t1 write;
+connect (locker,localhost,root,,test);
 connection locker;
 send lock table t1 read;
 connection default;
@@ -51,9 +42,9 @@ reap;
 unlock tables;
 connection default;
 
-# Test for with a read lock and a waiting write lock + flush
+# Test for with a write lock and a waiting write lock + flush
 
-lock table t1 read;
+lock table t1 write;
 connection locker;
 send lock table t1 write;
 connection default;

=== modified file 'mysql-test/t/information_schema.test'
--- a/mysql-test/t/information_schema.test	2008-04-19 20:34:56 +0000
+++ b/mysql-test/t/information_schema.test	2008-05-23 13:54:03 +0000
@@ -1389,3 +1389,47 @@ SELECT * FROM INFORMATION_SCHEMA.tables,
 DROP TABLE t1,t2;
 DROP DATABASE testdb1;
 DROP USER mysqluser_35955@localhost;
+USE test;
+
+--echo #
+--echo # Additional test for WL#3726 "DDL locking for all metadata objects"
+--echo # To avoid possible deadlocks process of filling of I_S tables should
+--echo # use high-priority metadata lock requests when opening tables.
+--echo # Below we just test that we really use high-priority lock request
+--echo # since reproducing a deadlock will require much more complex test.
+--echo #
+--disable_warnings
+drop tables if exists t1, t2, t3;
+--enable_warnings
+create table t1 (i int);
+create table t2 (j int primary key auto_increment);
+connect (con3726_1,localhost,root,,test);
+--echo # Switching to connection 'con3726_1'
+connection con3726_1;
+lock table t2 read;
+connect (con3726_2,localhost,root,,test);
+--echo # Switching to connection 'con3726_2'
+connection con3726_2;
+--echo # RENAME below will be blocked by 'lock table t2 read' above but
+--echo # will add two pending requests for exclusive metadata locks.
+--send rename table t2 to t3
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "Waiting for table" and info like "rename table t2 to t3";
+--source include/wait_condition.inc
+--echo # These statements should not be blocked by pending lock requests
+select table_name, column_name, data_type from information_schema.columns
+  where table_schema = 'test' and table_name in ('t1', 't2');
+select table_name, auto_increment from information_schema.tables
+  where table_schema = 'test' and table_name in ('t1', 't2');
+--echo # Switching to connection 'con3726_1'
+connection con3726_1;
+unlock tables;
+--echo # Switching to connection 'con3726_2'
+connection con3726_2;
+--reap
+--echo # Switching to connection 'default'
+connection default;
+drop tables t1, t3;

=== modified file 'mysql-test/t/kill.test'
--- a/mysql-test/t/kill.test	2008-03-27 19:02:15 +0000
+++ b/mysql-test/t/kill.test	2008-05-25 07:19:02 +0000
@@ -328,6 +328,243 @@ KILL CONNECTION_ID();
 SELECT 1;
 --connection default
 
+--echo #
+--echo # Additional test for WL#3726 "DDL locking for all metadata objects"
+--echo # Check that DDL and DML statements waiting for metadata locks can
+--echo # be killed. Note that we don't cover all situations here since it
+--echo # can be tricky to write test case for some of them (e.g. REPAIR or
+--echo # ALTER and other statements under LOCK TABLES).
+--echo #
+--disable_warnings
+drop tables if exists t1, t2, t3;
+--enable_warnings
+
+create table t1 (i int primary key);
+connect (blocker, localhost, root, , );
+connect (dml, localhost, root, , );
+connect (ddl, localhost, root, , );
+
+--echo # Test for RENAME TABLE
+--echo # Switching to connection 'blocker'
+connection blocker;
+lock table t1 read;
+--echo # Switching to connection 'ddl'
+connection ddl;
+let $ID= `select connection_id()`;
+--send rename table t1 to t2
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--replace_result $ID ID
+eval kill query $ID;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+--echo # Test for DROP TABLE
+--send drop table t1
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "Waiting for table" and
+        info = "drop table t1";
+--source include/wait_condition.inc
+--replace_result $ID ID
+eval kill query $ID;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+--echo # Test for CREATE TRIGGER
+--send create trigger t1_bi before insert on t1 for each row set @a:=1
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "Waiting for table" and
+        info = "create trigger t1_bi before insert on t1 for each row set @a:=1";
+--source include/wait_condition.inc
+--replace_result $ID ID
+eval kill query $ID;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+--echo #
+--echo # Tests for various kinds of ALTER TABLE
+--echo #
+--echo # Full-blown ALTER which should copy table
+--send alter table t1 add column j int
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "Waiting for table" and
+        info = "alter table t1 add column j int";
+--source include/wait_condition.inc
+--replace_result $ID ID
+eval kill query $ID;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+--echo # Two kinds of simple ALTER
+--send alter table t1 rename to t2
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "Waiting for table" and
+        info = "alter table t1 rename to t2";
+--source include/wait_condition.inc
+--replace_result $ID ID
+eval kill query $ID;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+--send alter table t1 disable keys
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "Waiting for table" and
+        info = "alter table t1 disable keys";
+--source include/wait_condition.inc
+--replace_result $ID ID
+eval kill query $ID;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+--echo # Fast ALTER
+--send alter table t1 alter column i set default 100
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "Waiting for table" and
+        info = "alter table t1 alter column i set default 100";
+--source include/wait_condition.inc
+--replace_result $ID ID
+eval kill query $ID;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+--echo # Special case which is triggered only for MERGE tables.
+--echo # Switching to connection 'blocker'
+connection blocker;
+unlock tables;
+create table t2 (i int primary key) engine=merge union=(t1);
+lock tables t2 read;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--send alter table t2 alter column i set default 100
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "Waiting for table" and
+        info = "alter table t2 alter column i set default 100";
+--source include/wait_condition.inc
+--replace_result $ID ID
+eval kill query $ID;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+--echo # Test for DML waiting for meta-data lock
+--echo # Switching to connection 'blocker'
+connection blocker;
+unlock tables;
+drop table t2;
+create table t2 (k int);
+lock tables t1 read;
+--echo # Switching to connection 'ddl'
+connection ddl;
+# Let us add pending exclusive metadata lock on t2
+--send rename tables t1 to t3, t2 to t1
+--echo # Switching to connection 'dml'
+connection dml;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "Waiting for table" and
+        info = "rename tables t1 to t3, t2 to t1";
+--source include/wait_condition.inc
+let $ID2= `select connection_id()`;
+--send insert into t2 values (1)
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "Waiting for table" and
+        info = "insert into t2 values (1)";
+--source include/wait_condition.inc
+--replace_result $ID2 ID2
+eval kill query $ID2;
+--echo # Switching to connection 'dml'
+connection dml;
+--error ER_QUERY_INTERRUPTED
+--reap
+--echo # Switching to connection 'blocker'
+connection blocker;
+unlock tables;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--reap
+
+--echo # Test for DML waiting for tables to be flushed
+--echo # Switching to connection 'blocker'
+connection blocker;
+lock tables t1 read;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--echo # Let us mark locked table t1 as old
+--send flush tables
+--echo # Switching to connection 'dml'
+connection dml;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "Flushing tables" and
+        info = "flush tables";
+--source include/wait_condition.inc
+--send select * from t1
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+  select count(*) = 1 from information_schema.processlist
+  where state = "Waiting for table" and
+        info = "select * from t1";
+--source include/wait_condition.inc
+--replace_result $ID2 ID2
+eval kill query $ID2;
+--echo # Switching to connection 'dml'
+connection dml;
+--error ER_QUERY_INTERRUPTED
+--reap
+--echo # Switching to connection 'blocker'
+connection blocker;
+unlock tables;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--reap
+
+--echo # Cleanup.
+--echo # Switching to connection 'default'
+connection default;
+drop table t3;
+drop table t1;
+
 ###########################################################################
 
 # Restore global concurrent_insert value. Keep in the end of the test file.

=== modified file 'mysql-test/t/lock.test'
--- a/mysql-test/t/lock.test	2007-10-03 00:27:31 +0000
+++ b/mysql-test/t/lock.test	2008-06-11 11:49:58 +0000
@@ -178,6 +178,7 @@ select * from t2;
 --error ER_TABLE_NOT_LOCKED
 select * from t3;
 select * from v_bug5719;
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
 drop view v_bug5719;
 --echo
 --echo sic: did not left LOCK TABLES mode automatically
@@ -185,7 +186,7 @@ drop view v_bug5719;
 --error ER_TABLE_NOT_LOCKED
 select * from t1;
 unlock tables;
-create view v_bug5719 as select * from t1;
+create or replace view v_bug5719 as select * from t1;
 lock tables v_bug5719 write;
 select * from v_bug5719;
 --echo
@@ -221,3 +222,46 @@ create view v_bug5719 as select * from t
 drop table t2, t3;
 
 --echo End of 5.1 tests.
+
+--echo #
+--echo # Ensure that FLUSH TABLES doesn't substitute a base locked table
+--echo # with a temporary one.
+--echo #
+
+--disable_warnings
+drop table if exists t1, t2;
+--enable_warnings
+create table t1 (a int);
+create table t2 (a int);
+lock table t1 write, t2 write;
+create temporary table t1 (a int);
+flush table t1;
+drop temporary table t1;
+select * from t1;
+unlock tables;
+drop table t1, t2;
+
+--echo #
+--echo # Ensure that REPAIR .. USE_FRM works under LOCK TABLES.
+--echo #
+
+--disable_warnings
+drop table if exists t1, t2;
+--enable_warnings
+create table t1 (a int);
+create table t2 (a int);
+lock table t1 write, t2 write;
+repair table t1 use_frm;
+repair table t1 use_frm;
+select * from t1;
+select * from t2;
+repair table t2 use_frm;
+repair table t2 use_frm;
+select * from t1;
+unlock tables;
+drop table t1, t2;
+
+
+--echo #
+--echo # End of 6.0 tests.
+--echo #

=== modified file 'mysql-test/t/lock_multi.test'
--- a/mysql-test/t/lock_multi.test	2008-03-05 10:32:49 +0000
+++ b/mysql-test/t/lock_multi.test	2008-05-23 13:54:03 +0000
@@ -151,7 +151,7 @@ send SELECT user.Select_priv FROM user, 
 connection locker;
 let $wait_condition=
   select count(*) = 1 from information_schema.processlist
-  where state = "Waiting for table" and info = 
+  where state = "Table lock" and info = 
   "SELECT user.Select_priv FROM user, db WHERE user.user = db.user LIMIT 1";
 --source include/wait_condition.inc
 # Make test case independent from earlier grants.
@@ -181,7 +181,7 @@ send FLUSH TABLES WITH READ LOCK;
 connection writer;
 let $wait_condition=
   select count(*) = 1 from information_schema.processlist
-  where state = "Flushing tables" and info = "FLUSH TABLES WITH READ LOCK";
+  where state = "Waiting for table" and info = "FLUSH TABLES WITH READ LOCK";
 --source include/wait_condition.inc
 # This must not block.
 CREATE TABLE t2 (c1 int);
@@ -209,7 +209,7 @@ send FLUSH TABLES WITH READ LOCK;
 connection writer;
 let $wait_condition=
   select count(*) = 1 from information_schema.processlist
-  where state = "Flushing tables" and info = "FLUSH TABLES WITH READ LOCK";
+  where state = "Waiting for table" and info = "FLUSH TABLES WITH READ LOCK";
 --source include/wait_condition.inc
 --error 1100
 CREATE TABLE t2 AS SELECT * FROM t1;
@@ -366,7 +366,7 @@ connection default;
 --echo connection: default
 let $wait_condition=
   select count(*) = 1 from information_schema.processlist
-  where state = "Flushing tables";
+  where state = "Waiting for table";
 --source include/wait_condition.inc
 alter table t1 add column j int;
 connect (insert,localhost,root,,test,,);
@@ -374,7 +374,7 @@ connection insert;
 --echo connection: insert
 let $wait_condition=
   select count(*) = 1 from information_schema.processlist
-  where state = "Flushing tables";
+  where state = "Waiting for table";
 --source include/wait_condition.inc
 --send insert into t1 values (1,2);
 --echo connection: default
@@ -424,18 +424,14 @@ connection default;
 --echo connection: default
 let $wait_condition=
   select count(*) = 1 from information_schema.processlist
-  where state = "Flushing tables";
+  where state = "Waiting for table";
 --source include/wait_condition.inc
 flush tables;
 let $wait_condition=
   select count(*) = 1 from information_schema.processlist
-  where state = "Flushing tables";
+  where state = "Waiting for table";
 --source include/wait_condition.inc
 unlock tables;
-let $wait_condition=
-  select count(*) = 0 from information_schema.processlist
-  where state = "Flushing tables";
---source include/wait_condition.inc
 connection flush;
 --reap
 connection default;
@@ -494,18 +490,14 @@ connection default;
 --echo connection: default
 let $wait_condition=
   select count(*) = 1 from information_schema.processlist
-  where state = "Flushing tables";
+  where state = "Waiting for table";
 --source include/wait_condition.inc
 flush tables;
 let $wait_condition=
   select count(*) = 1 from information_schema.processlist
-  where state = "Flushing tables";
+  where state = "Waiting for table";
 --source include/wait_condition.inc
 drop table t1;
-let $wait_condition=
-  select count(*) = 0 from information_schema.processlist
-  where state = "Flushing tables";
---source include/wait_condition.inc
 connection flush;
 --reap
 connection default;

=== modified file 'mysql-test/t/merge.test'
--- a/mysql-test/t/merge.test	2008-05-01 09:34:54 +0000
+++ b/mysql-test/t/merge.test	2008-05-22 22:42:32 +0000
@@ -1410,3 +1410,64 @@ EXPLAIN SELECT COUNT(*) FROM t4;
 DROP TABLE t1, t2, t3, t4;
 
 --echo End of 5.1 tests
+
+
+--echo #
+--echo # An additional test case for Bug#27430 Crash in subquery code
+--echo # when in PS and table DDL changed after PREPARE
+--echo #
+--echo # Test merge table with too many merge children.
+--echo #
+--disable_warnings
+drop table if exists t_parent;
+--enable_warnings
+set @save_table_definition_cache=@@global.table_definition_cache;
+--echo #
+--echo # Set @@global.table_definition_cache to minimum
+--echo #
+set @@global.table_definition_cache=256;
+set @a=null;
+let $1 = 256;
+--echo #
+--echo # Create 256 merge children
+--echo #
+--disable_query_log
+while ($1)
+{
+--disable_warnings
+  eval drop table if exists t$1;
+--enable_warnings
+  eval create table t$1 (a int) engine=myisam;
+  eval set @a=ifnull(concat(@a, ", ", "t$1"), "t$1");
+  dec $1;
+}
+--enable_query_log
+set @a=concat("create table t_parent (a int) union(", @a,
+              ") insert_method=first engine=mrg_myisam");
+prepare stmt from @a;
+execute stmt;
+prepare stmt from "select * from t_parent";
+--error ER_NEED_REPREPARE
+execute stmt;
+--error ER_NEED_REPREPARE
+execute stmt;
+--error ER_NEED_REPREPARE
+execute stmt;
+deallocate prepare stmt;
+--echo #
+--echo # Create merge parent 
+--echo #
+
+--echo #
+--echo # Cleanup
+--echo #
+let $1 = 256;
+--disable_query_log
+while ($1)
+{
+  eval drop table t$1;
+  dec $1;
+}
+--enable_query_log
+drop table t_parent;
+set @@global.table_definition_cache=@save_table_definition_cache;

=== modified file 'mysql-test/t/ps_ddl.test'
--- a/mysql-test/t/ps_ddl.test	2008-05-29 12:56:51 +0000
+++ b/mysql-test/t/ps_ddl.test	2008-06-23 13:26:17 +0000
@@ -1467,7 +1467,7 @@ execute stmt;
 call p_verify_reprepare_count(0);
 drop temporary table t2;
 execute stmt;
-call p_verify_reprepare_count(1);
+call p_verify_reprepare_count(0);
 drop table t2;
 execute stmt;
 call p_verify_reprepare_count(0);

=== modified file 'mysql-test/t/sp.test'
--- a/mysql-test/t/sp.test	2008-05-22 18:40:15 +0000
+++ b/mysql-test/t/sp.test	2008-06-23 13:26:17 +0000
@@ -1308,12 +1308,14 @@ select f3()|
 select id, f3() from t1 as t11 order by id|
 # Degenerate cases work too :)
 select f0()|
+# But these should not (particularly views should be locked explicitly).
+--error ER_TABLE_NOT_LOCKED
 select * from v0|
+--error ER_TABLE_NOT_LOCKED
 select *, f0() from v0, (select 123) as d1|
-# But these should not !
---error 1100
+--error ER_TABLE_NOT_LOCKED
 select id, f3() from t1|
---error 1100
+--error ER_TABLE_NOT_LOCKED
 select f4()|
 unlock tables|
 
@@ -1323,9 +1325,9 @@ lock tables v2 read, mysql.proc read|
 select * from v2|
 select * from v1|
 # These should not work as we have too little instances of tables locked
---error 1100
+--error ER_TABLE_NOT_LOCKED
 select * from v1, t1|
---error 1100
+--error ER_TABLE_NOT_LOCKED
 select f4()|
 unlock tables|
 

=== modified file 'mysql-test/t/trigger_notembedded.test'
--- a/mysql-test/t/trigger_notembedded.test	2007-12-13 21:19:15 +0000
+++ b/mysql-test/t/trigger_notembedded.test	2008-05-23 13:54:03 +0000
@@ -895,7 +895,7 @@ connection default;
 --echo connection: default
 let $wait_condition=
   select count(*) = 1 from information_schema.processlist
-  where state = "Flushing tables";
+  where state = "Waiting for table";
 --source include/wait_condition.inc
 create trigger t1_bi before insert on t1 for each row begin end;
 unlock tables;

=== modified file 'mysql-test/t/view.test'
--- a/mysql-test/t/view.test	2008-04-14 10:15:04 +0000
+++ b/mysql-test/t/view.test	2008-05-23 13:54:03 +0000
@@ -1016,10 +1016,8 @@ lock tables t1 read, v1 read;
 select * from v1;
 -- error 1100
 select * from t2;
-drop view v1;
---error ER_TABLE_NOT_LOCKED_FOR_WRITE
-drop table t1, t2;
 unlock tables;
+drop view v1;
 drop table t1, t2;
 
 #

=== modified file 'mysql-test/t/view_grant.test'
--- a/mysql-test/t/view_grant.test	2008-03-04 17:35:42 +0000
+++ b/mysql-test/t/view_grant.test	2008-05-23 13:54:03 +0000
@@ -1041,9 +1041,9 @@ GRANT SELECT ON db26813.t1 TO u26813@loc
 
 connect (u1,localhost,u26813,,db26813);
 connection u1;
---error ER_SPECIFIC_ACCESS_DENIED_ERROR
+--error ER_TABLEACCESS_DENIED_ERROR 
 ALTER VIEW v1 AS SELECT f2 FROM t1;
---error ER_SPECIFIC_ACCESS_DENIED_ERROR
+--error ER_TABLEACCESS_DENIED_ERROR
 ALTER VIEW v2 AS SELECT f2 FROM t1;
 --error ER_SPECIFIC_ACCESS_DENIED_ERROR
 ALTER VIEW v3 AS SELECT f2 FROM t1;

=== modified file 'sql/CMakeLists.txt'
--- a/sql/CMakeLists.txt	2008-06-23 07:14:00 +0000
+++ b/sql/CMakeLists.txt	2008-06-23 13:26:17 +0000
@@ -76,8 +76,8 @@ ADD_EXECUTABLE(mysqld
                partition_info.cc rpl_utility.cc rpl_injector.cc sql_locale.cc
                rpl_rli.cc rpl_mi.cc sql_servers.cc sql_audit.cc
                sql_connect.cc scheduler.cc 
-               ddl_blocker.cc si_objects.cc fk_dd.cc
-               sql_profile.cc event_parse_data.cc
+               ddl_blocker.cc si_objects.cc
+               sql_profile.cc event_parse_data.cc mdl.cc fk_dd.cc
                ${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc
                ${PROJECT_SOURCE_DIR}/sql/sql_yacc.h
                ${PROJECT_SOURCE_DIR}/include/mysqld_error.h

=== modified file 'sql/Makefile.am'
--- a/sql/Makefile.am	2008-06-23 07:14:00 +0000
+++ b/sql/Makefile.am	2008-06-23 13:26:17 +0000
@@ -89,7 +89,7 @@ noinst_HEADERS =	item.h item_func.h item
 			sql_partition.h partition_info.h partition_element.h \
 			probes.h sql_audit.h \
 			contributors.h sql_servers.h ddl_blocker.h \
-			si_objects.h fk_dd.h
+			si_objects.h sql_plist.h mdl.h fk_dd.h
 
 mysqld_SOURCES =	sql_lex.cc sql_handler.cc sql_partition.cc \
 			item.cc item_sum.cc item_buff.cc item_func.cc \
@@ -135,7 +135,7 @@ mysqld_SOURCES =	sql_lex.cc sql_handler.
 			sql_builtin.cc sql_tablespace.cc partition_info.cc \
 			sql_servers.cc sql_audit.cc sha2.cc \
 			ddl_blocker.cc si_objects.cc event_parse_data.cc \
-			fk_dd.cc
+			mdl.cc fk_dd.cc
 
 if HAVE_DTRACE
   mysqld_SOURCES += probes.d

=== modified file 'sql/backup/backup_progress.cc'
--- a/sql/backup/backup_progress.cc	2008-03-21 15:13:06 +0000
+++ b/sql/backup/backup_progress.cc	2008-06-12 02:23:08 +0000
@@ -54,7 +54,9 @@ my_bool check_ob_progress_tables(THD *th
   DBUG_ENTER("check_ob_progress_tables");
 
   /* Check mysql.online_backup */
-  tables.init_one_table("mysql", "online_backup", TL_READ);
+  tables.init_one_table("mysql", 5, "online_backup", 13,
+                        "online_backup", TL_READ);
+  alloc_mdl_locks(&tables, thd->mem_root);
   if (simple_open_n_lock_tables(thd, &tables))
   {
     ret= TRUE;
@@ -64,7 +66,9 @@ my_bool check_ob_progress_tables(THD *th
   close_thread_tables(thd);
 
   /* Check mysql.online_backup_progress */
-  tables.init_one_table("mysql", "online_backup_progress", TL_READ);
+  tables.init_one_table("mysql", 5, "online_backup_progress", 13,
+                        "online_backup_progress", TL_READ);
+  alloc_mdl_locks(&tables, thd->mem_root);
   if (simple_open_n_lock_tables(thd, &tables))
   {
     ret= TRUE;
@@ -165,6 +169,8 @@ bool backup_history_log_write(THD *thd, 
   table_list.db= MYSQL_SCHEMA_NAME.str;
   table_list.db_length= MYSQL_SCHEMA_NAME.length;
 
+  alloc_mdl_locks(&table_list, thd->mem_root);
+
   if (!(table= open_performance_schema_table(thd, & table_list,
                                              & open_tables_backup)))
     goto err;
@@ -317,6 +323,8 @@ bool backup_history_log_update(THD *thd,
   table_list.db= MYSQL_SCHEMA_NAME.str;
   table_list.db_length= MYSQL_SCHEMA_NAME.length;
 
+  alloc_mdl_locks(&table_list, thd->mem_root);
+
   if (!(table= open_performance_schema_table(thd, & table_list,
                                              & open_tables_backup)))
     goto err;
@@ -465,6 +473,8 @@ bool backup_progress_log_write(THD *thd,
 
   table_list.db= MYSQL_SCHEMA_NAME.str;
   table_list.db_length= MYSQL_SCHEMA_NAME.length;
+
+  alloc_mdl_locks(&table_list, thd->mem_root);
 
   if (!(table= open_performance_schema_table(thd, & table_list,
                                              & open_tables_backup)))

=== modified file 'sql/backup/be_default.cc'
--- a/sql/backup/be_default.cc	2008-02-08 21:28:03 +0000
+++ b/sql/backup/be_default.cc	2008-05-25 17:31:52 +0000
@@ -128,6 +128,20 @@ Backup::Backup(const Table_list &tables,
   locks_acquired= FALSE;
 }
 
+
+Backup::~Backup()
+{
+  /*
+    Since objects representing metadata lock requests for tables open
+    by locking thread use same chunks of memory as elements of
+    'all_tables' table list it is essential to ensure that locking
+    thread has finished before freeing elements of this table list.
+  */
+  locking_thd->wait_until_locking_thread_dies();
+  backup::free_table_list(all_tables);
+}
+
+
 /**
   * @brief Prelock call to setup locking.
   *

=== modified file 'sql/backup/be_default.h'
--- a/sql/backup/be_default.h	2007-12-03 20:28:32 +0000
+++ b/sql/backup/be_default.h	2008-05-25 17:31:52 +0000
@@ -86,7 +86,7 @@ class Backup: public Backup_thread_drive
   public:
     enum has_data_info { YES, WAIT, EOD };
     Backup(const Table_list &tables, THD *t_thd, thr_lock_type lock_type);
-    virtual ~Backup() { backup::free_table_list(all_tables); }; 
+    virtual ~Backup();
     size_t size()  { return UNKNOWN_SIZE; };
     size_t init_size() { return 0; };
     result_t  begin(const size_t) { return backup::OK; };

=== modified file 'sql/backup/be_thread.cc'
--- a/sql/backup/be_thread.cc	2008-02-15 14:58:00 +0000
+++ b/sql/backup/be_thread.cc	2008-05-25 17:31:52 +0000
@@ -258,19 +258,7 @@ Locking_thread_st::~Locking_thread_st()
     the locking thread won't access them.
   */
   kill_locking_thread();
-  pthread_mutex_lock(&THR_LOCK_caller);
-  if (lock_state != LOCK_DONE)
-  {
-    m_thd->enter_cond(&COND_caller_wait, &THR_LOCK_caller,
-                    "Locking thread: waiting until locking thread is done");
-    while (lock_state != LOCK_DONE)
-      pthread_cond_wait(&COND_caller_wait, &THR_LOCK_caller);
-    m_thd->exit_cond("Locking thread: terminating");
-
-    DBUG_PRINT("info",("Locking thread's locking thread terminated"));
-  }
-  else
-    pthread_mutex_unlock(&THR_LOCK_caller);
+  wait_until_locking_thread_dies();
 
   /*
     Destroy the thread mutexes and cond variables.
@@ -333,3 +321,26 @@ void Locking_thread_st::kill_locking_thr
   DBUG_VOID_RETURN;
 }
 
+
+/**
+   Wait until driver's lock thread finishes.
+
+   @note It is important to use this function before freeing memory
+         or destroying objects to which such thread might access.
+*/
+void Locking_thread_st::wait_until_locking_thread_dies()
+{
+  pthread_mutex_lock(&THR_LOCK_caller);
+  if (lock_state != LOCK_DONE)
+  {
+    m_thd->enter_cond(&COND_caller_wait, &THR_LOCK_caller,
+                    "Locking thread: waiting until locking thread is done");
+    while (lock_state != LOCK_DONE)
+      pthread_cond_wait(&COND_caller_wait, &THR_LOCK_caller);
+    m_thd->exit_cond("Locking thread: terminating");
+
+    DBUG_PRINT("info",("Locking thread's locking thread terminated"));
+  }
+  else
+    pthread_mutex_unlock(&THR_LOCK_caller);
+}

=== modified file 'sql/backup/be_thread.h'
--- a/sql/backup/be_thread.h	2008-01-30 22:28:21 +0000
+++ b/sql/backup/be_thread.h	2008-05-25 17:31:52 +0000
@@ -63,6 +63,7 @@ public:
 
   result_t start_locking_thread(const char *tname);
   void kill_locking_thread();
+  void wait_until_locking_thread_dies();
 
 }; // Locking_thread_st
 

=== modified file 'sql/backup/kernel.cc'
--- a/sql/backup/kernel.cc	2008-04-19 20:45:06 +0000
+++ b/sql/backup/kernel.cc	2008-06-20 13:11:20 +0000
@@ -1108,6 +1108,7 @@ Backup_info::add_table(Db_item &dbi, con
   // FIXME: table/db name mangling
   entry.db= const_cast<char*>(t.db().name().ptr());
   entry.alias= entry.table_name= const_cast<char*>(t.name().ptr());
+  alloc_mdl_locks(&entry, m_thd->mem_root);
 
   uint cnt;
   int res= ::open_tables(m_thd,&tl,&cnt,0);
@@ -2106,7 +2107,13 @@ TABLE_LIST *build_table_list(const Table
 
   for( uint tno=0; tno < tables.count() ; tno++ )
   {
-    TABLE_LIST *ptr= (TABLE_LIST*)my_malloc(sizeof(TABLE_LIST), MYF(MY_WME));
+    TABLE_LIST *ptr;
+    MDL_LOCK_DATA *mdl_lock_data;
+    char *keybuff;
+
+    my_multi_malloc(MYF(MY_WME), &ptr, sizeof(TABLE_LIST),
+                    &mdl_lock_data, sizeof(MDL_LOCK_DATA),
+                    &keybuff, MAX_MDLKEY_LENGTH, NULL, 0);
     DBUG_ASSERT(ptr);  // FIXME: report error instead
     bzero(ptr,sizeof(TABLE_LIST));
 
@@ -2115,6 +2122,8 @@ TABLE_LIST *build_table_list(const Table
     ptr->alias= ptr->table_name= const_cast<char*>(tbl.name().ptr());
     ptr->db= const_cast<char*>(tbl.db().name().ptr());
     ptr->lock_type= lock;
+    mdl_init_lock(mdl_lock_data, keybuff, 0, ptr->db, ptr->table_name);
+    ptr->mdl_lock_data= mdl_lock_data;
 
     // and add it to the list
 

=== modified file 'sql/event_db_repository.cc'
--- a/sql/event_db_repository.cc	2008-03-27 18:40:00 +0000
+++ b/sql/event_db_repository.cc	2008-06-12 02:23:08 +0000
@@ -552,7 +552,8 @@ Event_db_repository::open_event_table(TH
   TABLE_LIST tables;
   DBUG_ENTER("Event_db_repository::open_event_table");
 
-  tables.init_one_table("mysql", "event", lock_type);
+  tables.init_one_table("mysql", 5, "event", 5, "event", lock_type);
+  alloc_mdl_locks(&tables, thd->mem_root);
 
   if (simple_open_n_lock_tables(thd, &tables))
   {
@@ -1094,7 +1095,8 @@ Event_db_repository::check_system_tables
 
 
   /* Check mysql.db */
-  tables.init_one_table("mysql", "db", TL_READ);
+  tables.init_one_table("mysql", 5, "db", 2, "db", TL_READ);
+  alloc_mdl_locks(&tables, thd->mem_root);
 
   if (simple_open_n_lock_tables(thd, &tables))
   {
@@ -1111,7 +1113,8 @@ Event_db_repository::check_system_tables
     close_thread_tables(thd);
   }
   /* Check mysql.user */
-  tables.init_one_table("mysql", "user", TL_READ);
+  tables.init_one_table("mysql", 5, "user", 4, "user", TL_READ);
+  alloc_mdl_locks(&tables, thd->mem_root);
 
   if (simple_open_n_lock_tables(thd, &tables))
   {
@@ -1131,7 +1134,8 @@ Event_db_repository::check_system_tables
     close_thread_tables(thd);
   }
   /* Check mysql.event */
-  tables.init_one_table("mysql", "event", TL_READ);
+  tables.init_one_table("mysql", 5, "event", 5, "event", TL_READ);
+  alloc_mdl_locks(&tables, thd->mem_root);
 
   if (simple_open_n_lock_tables(thd, &tables))
   {

=== modified file 'sql/field.h'
--- a/sql/field.h	2008-05-22 18:40:15 +0000
+++ b/sql/field.h	2008-06-23 13:26:17 +0000
@@ -524,7 +524,6 @@ public:
 
   /* Hash value */
   virtual void hash(ulong *nr, ulong *nr2);
-  friend bool reopen_table(THD *,struct st_table *,bool);
   friend int cre_myisam(char * name, register TABLE *form, uint options,
 			ulonglong auto_increment_value);
   friend class Copy_field;

=== modified file 'sql/ha_ndbcluster.cc'
--- a/sql/ha_ndbcluster.cc	2008-04-25 16:43:25 +0000
+++ b/sql/ha_ndbcluster.cc	2008-05-23 13:54:03 +0000
@@ -509,7 +509,7 @@ int ha_ndbcluster::ndb_err(NdbTransactio
     bzero((char*) &table_list,sizeof(table_list));
     table_list.db= m_dbname;
     table_list.alias= table_list.table_name= m_tabname;
-    close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE);
+    close_cached_tables(thd, &table_list, FALSE, FALSE);
     break;
   }
   default:
@@ -7769,6 +7769,20 @@ int ndbcluster_find_files(handlerton *ht
     }
   }
 
+  /*
+    ndbcluster_find_files() may be called from I_S code and ndbcluster_binlog
+    thread in situations when some tables are already open. This means that
+    code below will try to obtain exclusive metadata lock on some table
+    while holding shared meta-data lock on other tables. This might lead to
+    a deadlock, and therefore is disallowed by assertions of the metadata
+    locking subsystem. In order to temporarily make the code work, we must
+    reset and backup the open tables state, thus hide the existing locks
+    from MDL asserts. But in the essence this is violation of metadata
+    locking protocol which has to be closed ASAP.
+  */
+  Open_tables_state open_tables_state_backup;
+  thd->reset_n_backup_open_tables_state(&open_tables_state_backup);
+
   if (!global_read_lock)
   {
     // Delete old files
@@ -7792,8 +7806,11 @@ int ndbcluster_find_files(handlerton *ht
     }
   }
 
+  thd->restore_backup_open_tables_state(&open_tables_state_backup);
+
+  /* Lock mutex before creating .FRM files. */
   pthread_mutex_lock(&LOCK_open);
-  // Create new files
+  /* Create new files. */
   List_iterator_fast<char> it2(create_list);
   while ((file_name_str=it2++))
   {  
@@ -8650,7 +8667,7 @@ int handle_trailing_share(THD *thd, NDB_
     safe_mutex_assert_owner(&LOCK_open);
   else
     pthread_mutex_lock(&LOCK_open);    
-  close_cached_tables(thd, &table_list, TRUE, FALSE, FALSE);
+  close_cached_tables(thd, &table_list, TRUE, FALSE);
   if (!have_lock_open)
     pthread_mutex_unlock(&LOCK_open);    
 

=== modified file 'sql/ha_ndbcluster_binlog.cc'
--- a/sql/ha_ndbcluster_binlog.cc	2008-03-27 18:40:00 +0000
+++ b/sql/ha_ndbcluster_binlog.cc	2008-06-20 13:11:20 +0000
@@ -145,6 +145,8 @@ static void ndb_free_schema_object(NDB_S
 */
 static TABLE *ndb_binlog_index= 0;
 static TABLE_LIST binlog_tables;
+static MDL_LOCK_DATA binlog_mdl_lock_data;
+static char binlog_mdlkey[MAX_MDLKEY_LENGTH];
 
 /*
   Helper functions
@@ -268,7 +270,7 @@ static void run_query(THD *thd, char *bu
   DBUG_PRINT("query", ("%s", thd->query));
 
   DBUG_ASSERT(!thd->in_sub_stmt);
-  DBUG_ASSERT(!thd->prelocked_mode);
+  DBUG_ASSERT(!thd->locked_tables_mode);
 
   mysql_parse(thd, thd->query, thd->query_length, &found_semicolon);
 
@@ -288,7 +290,13 @@ static void run_query(THD *thd, char *bu
                       thd_ndb->m_error_code,
                       (int) thd->is_error(), thd->is_slave_error);
   }
+
+  /*
+    After executing statement we should unlock and close tables open
+    by it as well as release meta-data locks obtained by it.
+  */
   close_thread_tables(thd);
+
   /*
     XXX: this code is broken. mysql_parse()/mysql_reset_thd_for_next_command()
     can not be called from within a statement, and
@@ -911,7 +919,7 @@ int ndbcluster_setup_binlog_table_shares
     {
       if (ndb_extra_logging)
         sql_print_information("NDB Binlog: ndb tables writable");
-      close_cached_tables(NULL, NULL, TRUE, FALSE, FALSE);
+      close_cached_tables(NULL, NULL, TRUE, FALSE);
     }
     pthread_mutex_unlock(&LOCK_open);
     /* Signal injector thread that all is setup */
@@ -1729,7 +1737,7 @@ ndb_handle_schema_change(THD *thd, Ndb *
     bzero((char*) &table_list,sizeof(table_list));
     table_list.db= (char *)dbname;
     table_list.alias= table_list.table_name= (char *)tabname;
-    close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE);
+    close_cached_tables(thd, &table_list, FALSE, FALSE);
     /* ndb_share reference create free */
     DBUG_PRINT("NDB_SHARE", ("%s create free  use_count: %u",
                              share->key, share->use_count));
@@ -1862,7 +1870,7 @@ ndb_binlog_thread_handle_schema_event(TH
             bzero((char*) &table_list,sizeof(table_list));
             table_list.db= schema->db;
             table_list.alias= table_list.table_name= schema->name;
-            close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE);
+            close_cached_tables(thd, &table_list, FALSE, FALSE);
           }
           /* ndb_share reference temporary free */
           if (share)
@@ -1997,7 +2005,7 @@ ndb_binlog_thread_handle_schema_event(TH
       pthread_mutex_unlock(&ndb_schema_share_mutex);
       /* end protect ndb_schema_share */
 
-      close_cached_tables(NULL, NULL, FALSE, FALSE, FALSE);
+      close_cached_tables(NULL, NULL, FALSE, FALSE);
       // fall through
     case NDBEVENT::TE_ALTER:
       ndb_handle_schema_change(thd, ndb, pOp, event_data);
@@ -2145,7 +2153,7 @@ ndb_binlog_thread_handle_schema_event_po
           bzero((char*) &table_list,sizeof(table_list));
           table_list.db= schema->db;
           table_list.alias= table_list.table_name= schema->name;
-          close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE);
+          close_cached_tables(thd, &table_list, FALSE, FALSE);
         }
         break;
       case SOT_RENAME_TABLE:
@@ -2163,7 +2171,7 @@ ndb_binlog_thread_handle_schema_event_po
           bzero((char*) &table_list,sizeof(table_list));
           table_list.db= schema->db;
           table_list.alias= table_list.table_name= schema->name;
-          close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE);
+          close_cached_tables(thd, &table_list, FALSE, FALSE);
         }
         {
           if (ndb_extra_logging > 9)
@@ -2205,7 +2213,7 @@ ndb_binlog_thread_handle_schema_event_po
           bzero((char*) &table_list,sizeof(table_list));
           table_list.db= schema->db;
           table_list.alias= table_list.table_name= schema->name;
-          close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE);
+          close_cached_tables(thd, &table_list, FALSE, FALSE);
         }
         if (share)
         {
@@ -2282,7 +2290,7 @@ ndb_binlog_thread_handle_schema_event_po
         bzero((char*) &table_list,sizeof(table_list));
         table_list.db= (char *)schema->db;
         table_list.alias= table_list.table_name= (char *)schema->name;
-        close_cached_tables(thd, &table_list, TRUE, FALSE, FALSE);
+        close_cached_tables(thd, &table_list, TRUE, FALSE);
 
         if (schema->node_id != g_ndb_cluster_connection->node_id())
         {
@@ -2494,17 +2502,20 @@ struct ndb_binlog_index_row {
 /*
   Open the ndb_binlog_index table
 */
-static int open_ndb_binlog_index(THD *thd, TABLE_LIST *tables,
-                                 TABLE **ndb_binlog_index)
+static int open_ndb_binlog_index(THD *thd, TABLE **ndb_binlog_index)
 {
   static char repdb[]= NDB_REP_DB;
   static char reptable[]= NDB_REP_TABLE;
   const char *save_proc_info= thd->proc_info;
+  TABLE_LIST *tables= &binlog_tables;
 
   bzero((char*) tables, sizeof(*tables));
   tables->db= repdb;
   tables->alias= tables->table_name= reptable;
   tables->lock_type= TL_WRITE;
+  mdl_init_lock(&binlog_mdl_lock_data, binlog_mdlkey, 0, tables->db,
+                tables->table_name);
+  tables->mdl_lock_data= &binlog_mdl_lock_data;
   THD_SET_PROC_INFO(thd, "Opening " NDB_REP_DB "." NDB_REP_TABLE);
   tables->required_type= FRMTYPE_TABLE;
   uint counter;
@@ -2546,18 +2557,18 @@ ndb_add_ndb_binlog_index(THD *thd, ndb_b
 
   for ( ; ; ) /* loop for need_reopen */
   {
-    if (!ndb_binlog_index && open_ndb_binlog_index(thd, &binlog_tables, &ndb_binlog_index))
+    if (!ndb_binlog_index && open_ndb_binlog_index(thd, &ndb_binlog_index))
     {
       error= -1;
       goto add_ndb_binlog_index_err;
     }
 
-    if (lock_tables(thd, &binlog_tables, 1, &need_reopen))
+    if (lock_tables(thd, &binlog_tables, 1, 0, &need_reopen))
     {
       if (need_reopen)
       {
         TABLE_LIST *p_binlog_tables= &binlog_tables;
-        close_tables_for_reopen(thd, &p_binlog_tables);
+        close_tables_for_reopen(thd, &p_binlog_tables, FALSE);
         ndb_binlog_index= 0;
         continue;
       }
@@ -2614,8 +2625,12 @@ ndb_add_ndb_binlog_index(THD *thd, ndb_b
     }
   } while (row);
 
-  mysql_unlock_tables(thd, thd->lock);
-  thd->lock= 0;
+
+  if (! thd->locked_tables_mode)                /* Is always TRUE */
+  {
+    mysql_unlock_tables(thd, thd->lock);
+    thd->lock= 0;
+  }
   thd->options= saved_options;
   return 0;
 add_ndb_binlog_index_err:
@@ -4444,13 +4459,13 @@ restart:
 
   if (ndb_extra_logging)
     sql_print_information("NDB Binlog: ndb tables writable");
-  close_cached_tables((THD*) 0, (TABLE_LIST*) 0, FALSE, FALSE, FALSE);
+  close_cached_tables((THD*) 0, 0, (TABLE_LIST*) 0, FALSE);
 
   {
     static char db[]= "";
     thd->db= db;
     if (ndb_binlog_running)
-      open_ndb_binlog_index(thd, &binlog_tables, &ndb_binlog_index);
+      open_ndb_binlog_index(thd, &ndb_binlog_index);
     thd->db= db;
   }
   do_ndbcluster_binlog_close_connection= BCCC_running;

=== modified file 'sql/ha_partition.cc'
--- a/sql/ha_partition.cc	2008-05-14 13:49:41 +0000
+++ b/sql/ha_partition.cc	2008-05-27 12:15:44 +0000
@@ -2402,7 +2402,7 @@ int ha_partition::open(const char *name,
     for the same table.
   */
   if (is_not_tmp_table)
-    pthread_mutex_lock(&table_share->mutex);
+    pthread_mutex_lock(&table_share->LOCK_ha_data);
   if (!table_share->ha_data)
   {
     HA_DATA_PARTITION *ha_data;
@@ -2416,7 +2416,7 @@ int ha_partition::open(const char *name,
     bzero(ha_data, sizeof(HA_DATA_PARTITION));
   }
   if (is_not_tmp_table)
-    pthread_mutex_unlock(&table_share->mutex);
+    pthread_mutex_unlock(&table_share->LOCK_ha_data);
   /*
     Some handlers update statistics as part of the open call. This will in
     some cases corrupt the statistics of the partition handler and thus

=== modified file 'sql/ha_partition.h'
--- a/sql/ha_partition.h	2008-05-08 13:01:30 +0000
+++ b/sql/ha_partition.h	2008-05-27 12:15:44 +0000
@@ -861,7 +861,7 @@ private:
     if(table_share->tmp_table == NO_TMP_TABLE)
     {
       auto_increment_lock= TRUE;
-      pthread_mutex_lock(&table_share->mutex);
+      pthread_mutex_lock(&table_share->LOCK_ha_data);
     }
   }
   virtual void unlock_auto_increment()
@@ -874,7 +874,7 @@ private:
     */
     if(auto_increment_lock && !auto_increment_safe_stmt_log_lock)
     {
-      pthread_mutex_unlock(&table_share->mutex);
+      pthread_mutex_unlock(&table_share->LOCK_ha_data);
       auto_increment_lock= FALSE;
     }
   }

=== modified file 'sql/handler.cc'
--- a/sql/handler.cc	2008-06-23 07:14:00 +0000
+++ b/sql/handler.cc	2008-06-23 13:26:17 +0000
@@ -2836,20 +2836,13 @@ static bool update_frm_version(TABLE *ta
   if ((file= my_open(path, O_RDWR|O_BINARY, MYF(MY_WME))) >= 0)
   {
     uchar version[4];
-    char *key= table->s->table_cache_key.str;
-    uint key_length= table->s->table_cache_key.length;
-    TABLE *entry;
-    HASH_SEARCH_STATE state;
 
     int4store(version, MYSQL_VERSION_ID);
 
     if ((result= my_pwrite(file,(uchar*) version,4,51L,MYF_RW)))
       goto err;
 
-    for (entry=(TABLE*) hash_first(&open_cache,(uchar*) key,key_length, &state);
-         entry;
-         entry= (TABLE*) hash_next(&open_cache,(uchar*) key,key_length, &state))
-      entry->s->mysql_version= MYSQL_VERSION_ID;
+    table->s->mysql_version= MYSQL_VERSION_ID;
   }
 err:
   if (file >= 0)
@@ -3260,8 +3253,8 @@ handler::ha_create_handler_files(const c
 int
 handler::ha_change_partitions(HA_CREATE_INFO *create_info,
                      const char *path,
-                     ulonglong *copied,
-                     ulonglong *deleted,
+                     ulonglong * const copied,
+                     ulonglong * const deleted,
                      const uchar *pack_frm_data,
                      size_t pack_frm_len)
 {
@@ -5174,9 +5167,7 @@ static bool check_table_binlog_row_based
    to the binary log.
 
    This function will generate and write table maps for all tables
-   that are locked by the thread 'thd'.  Either manually locked
-   (stored in THD::locked_tables) and automatically locked (stored
-   in THD::lock) are considered.
+   that are locked by the thread 'thd'.
 
    @param thd     Pointer to THD structure
 
@@ -5185,23 +5176,19 @@ static bool check_table_binlog_row_based
 
    @sa
        THD::lock
-       THD::locked_tables
 */
 
 static int write_locked_table_maps(THD *thd)
 {
   DBUG_ENTER("write_locked_table_maps");
-  DBUG_PRINT("enter", ("thd: %p  thd->lock: %p  thd->locked_tables: %p  "
-                       "thd->extra_lock: %p",
-                       thd, thd->lock,
-                       thd->locked_tables, thd->extra_lock));
+  DBUG_PRINT("enter", ("thd: %p  thd->lock: %p thd->extra_lock: %p",
+                       thd, thd->lock, thd->extra_lock));
 
   if (thd->get_binlog_table_maps() == 0)
   {
-    MYSQL_LOCK *locks[3];
+    MYSQL_LOCK *locks[2];
     locks[0]= thd->extra_lock;
     locks[1]= thd->lock;
-    locks[2]= thd->locked_tables;
     for (uint i= 0 ; i < sizeof(locks)/sizeof(*locks) ; ++i )
     {
       MYSQL_LOCK const *const lock= locks[i];

=== modified file 'sql/lock.cc'
--- a/sql/lock.cc	2008-05-08 20:43:28 +0000
+++ b/sql/lock.cc	2008-06-19 12:39:58 +0000
@@ -94,31 +94,6 @@ static int lock_external(THD *thd, TABLE
 static int unlock_external(THD *thd, TABLE **table,uint count);
 static void print_lock_error(int error, const char *);
 
-/*
-  Lock tables.
-
-  SYNOPSIS
-    mysql_lock_tables()
-    thd                         The current thread.
-    tables                      An array of pointers to the tables to lock.
-    count                       The number of tables to lock.
-    flags                       Options:
-      MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK      Ignore a global read lock
-      MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY      Ignore SET GLOBAL READ_ONLY
-      MYSQL_LOCK_IGNORE_FLUSH                 Ignore a flush tables.
-      MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN        Instead of reopening altered
-                                              or dropped tables by itself,
-                                              mysql_lock_tables() should
-                                              notify upper level and rely
-                                              on caller doing this.
-    need_reopen                 Out parameter, TRUE if some tables were altered
-                                or deleted and should be reopened by caller.
-
-  RETURN
-    A lock structure pointer on success.
-    NULL on error or if some tables should be reopen.
-*/
-
 /* Map the return value of thr_lock to an error from errmsg.txt */
 static int thr_lock_errno_to_mysql[]=
 { 0, 1, ER_LOCK_WAIT_TIMEOUT, ER_LOCK_DEADLOCK };
@@ -232,6 +207,28 @@ static void reset_lock_data_and_free(MYS
 }
 
 
+/**
+   Lock tables.
+
+   @param thd          The current thread.
+   @param tables       An array of pointers to the tables to lock.
+   @param count        The number of tables to lock.
+   @param flags        Options:
+                 MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK Ignore a global read lock
+                 MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY Ignore SET GLOBAL READ_ONLY
+                 MYSQL_LOCK_IGNORE_FLUSH            Ignore a flush tables.
+   @param need_reopen  Out parameter, TRUE if some tables were altered
+                       or deleted and should be reopened by caller.
+
+   @note Caller of this function should always be ready to handle request to
+         reopen table unless there are external invariants which guarantee
+         that such thing won't be needed (for example we are obtaining lock
+         on table on which we already have exclusive metadata lock).
+
+   @retval  A lock structure pointer on success.
+   @retval  NULL on error or if some tables should be reopen.
+*/
+
 MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count,
                               uint flags, bool *need_reopen)
 {
@@ -315,7 +312,7 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, 
       my_error(rc, MYF(0));
       break;
     }
-    else if (rc == 1)                           /* aborted */
+    else if (rc == 1)                           /* aborted or killed */
     {
       thd->some_tables_deleted=1;		// Try again
       sql_lock->lock_count= 0;                  // Locks are already freed
@@ -324,8 +321,9 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, 
     else if (!thd->some_tables_deleted || (flags & MYSQL_LOCK_IGNORE_FLUSH))
     {
       /*
-        Thread was killed or lock aborted. Let upper level close all
-        used tables and retry or give error.
+        Success and nobody set thd->some_tables_deleted to force reopen
+        or we were called with MYSQL_LOCK_IGNORE_FLUSH so such attempts
+        should be ignored.
       */
       break;
     }
@@ -351,13 +349,9 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, 
     */
     reset_lock_data_and_free(&sql_lock);
 retry:
-    if (flags & MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN)
-    {
-      *need_reopen= TRUE;
-      break;
-    }
-    if (wait_for_tables(thd))
-      break;					// Couldn't open tables
+    /* Let upper level close all used tables and retry or give error. */
+    *need_reopen= TRUE;
+    break;
   }
   thd_proc_info(thd, 0);
   if (thd->killed)
@@ -503,28 +497,15 @@ void mysql_unlock_read_tables(THD *thd, 
 /**
   Try to find the table in the list of locked tables.
   In case of success, unlock the table and remove it from this list.
-
-  @note This function has a legacy side effect: the table is
-  unlocked even if it is not found in the locked list.
-  It's not clear if this side effect is intentional or still
-  desirable. It might lead to unmatched calls to
-  unlock_external(). Moreover, a discrepancy can be left
-  unnoticed by the storage engine, because in
-  unlock_external() we call handler::external_lock(F_UNLCK) only
-  if table->current_lock is not F_UNLCK.
+  If a table has more than one lock instance, removes them all.
 
   @param  thd             thread context
   @param  locked          list of locked tables
   @param  table           the table to unlock
-  @param  always_unlock   specify explicitly if the legacy side
-                          effect is desired.
 */
 
-void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table,
-                       bool always_unlock)
+void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table)
 {
-  if (always_unlock == TRUE)
-    mysql_unlock_some_tables(thd, &table, /* table count */ 1);
   if (locked)
   {
     reg1 uint i;
@@ -538,9 +519,8 @@ void mysql_lock_remove(THD *thd, MYSQL_L
 
         DBUG_ASSERT(table->lock_position == i);
 
-        /* Unlock if not yet unlocked */
-        if (always_unlock == FALSE)
-          mysql_unlock_some_tables(thd, &table, /* table count */ 1);
+        /* Unlock the table. */
+        mysql_unlock_some_tables(thd, &table, /* table count */ 1);
 
         /* Decrement table_count in advance, making below expressions easier */
         old_tables= --locked->table_count;
@@ -745,7 +725,7 @@ TABLE_LIST *mysql_lock_have_duplicate(TH
     goto end;
 
   /* Get command lock or LOCK TABLES lock. Maybe empty for INSERT DELAYED. */
-  if (! (mylock= thd->lock ? thd->lock : thd->locked_tables))
+  if (! (mylock= thd->lock))
     goto end;
 
   /* If we have less than two tables, we cannot have duplicates. */
@@ -940,361 +920,58 @@ static MYSQL_LOCK *get_lock_data(THD *th
 *****************************************************************************/
 
 /**
-  Lock and wait for the named lock.
-
-  @param thd			Thread handler
-  @param table_list		Lock first table in this list
-
-
-  @note
-    Works together with global read lock.
-
-  @retval
-    0	ok
-  @retval
-    1	error
-*/
-
-int lock_and_wait_for_table_name(THD *thd, TABLE_LIST *table_list)
-{
-  int lock_retcode;
-  int error= -1;
-  DBUG_ENTER("lock_and_wait_for_table_name");
-
-  if (wait_if_global_read_lock(thd, 0, 1))
-    DBUG_RETURN(1);
-  pthread_mutex_lock(&LOCK_open);
-  if ((lock_retcode = lock_table_name(thd, table_list, TRUE)) < 0)
-    goto end;
-  if (lock_retcode && wait_for_locked_table_names(thd, table_list))
-  {
-    unlock_table_name(thd, table_list);
-    goto end;
-  }
-  error=0;
-
-end:
-  pthread_mutex_unlock(&LOCK_open);
-  start_waiting_global_read_lock(thd);
-  DBUG_RETURN(error);
-}
-
-
-/**
-  Put a not open table with an old refresh version in the table cache.
+   Obtain exclusive metadata locks on the list of tables.
 
-  @param thd			Thread handler
-  @param table_list		Lock first table in this list
-  @param check_in_use           Do we need to check if table already in use by us
+   @param thd         Thread handle
+   @param table_list  List of tables to lock
 
-  @note
-    One must have a lock on LOCK_open!
-
-  @warning
-    If you are going to update the table, you should use
-    lock_and_wait_for_table_name instead of this function as this works
-    together with 'FLUSH TABLES WITH READ LOCK'
+   @note This function assumes that no metadata locks were acquired
+         before calling it. Also it cannot be called while holding
+         LOCK_open mutex. Both these invariants are enforced by asserts
+         in mdl_acquire_exclusive_locks() functions.
 
-  @note
-    This will force any other threads that uses the table to release it
-    as soon as possible.
-
-  @return
-    < 0 error
-  @return
-    == 0 table locked
-  @return
-    > 0  table locked, but someone is using it
-*/
-
-int lock_table_name(THD *thd, TABLE_LIST *table_list, bool check_in_use)
-{
-  TABLE *table;
-  char  key[MAX_DBKEY_LENGTH];
-  char *db= table_list->db;
-  uint  key_length;
-  bool  found_locked_table= FALSE;
-  HASH_SEARCH_STATE state;
-  DBUG_ENTER("lock_table_name");
-  DBUG_PRINT("enter",("db: %s  name: %s", db, table_list->table_name));
-
-  key_length= create_table_def_key(thd, key, table_list, 0);
-
-  if (check_in_use)
-  {
-    /* Only insert the table if we haven't insert it already */
-    for (table=(TABLE*) hash_first(&open_cache, (uchar*)key,
-                                   key_length, &state);
-         table ;
-         table = (TABLE*) hash_next(&open_cache,(uchar*) key,
-                                    key_length, &state))
-    {
-      if (table->reginfo.lock_type < TL_WRITE)
-      {
-        if (table->in_use == thd)
-          found_locked_table= TRUE;
-        continue;
-      }
-
-      if (table->in_use == thd)
-      {
-        DBUG_PRINT("info", ("Table is in use"));
-        table->s->version= 0;                  // Ensure no one can use this
-        table->locked_by_name= 1;
-        DBUG_RETURN(0);
-      }
-    }
-  }
-
-  if (thd->locked_tables && thd->locked_tables->table_count &&
-      ! find_temporary_table(thd, table_list->db, table_list->table_name))
-  {
-    if (found_locked_table)
-      my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_list->alias);
-    else
-      my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_list->alias);
-
-    DBUG_RETURN(-1);
-  }
-
-  if (!(table= table_cache_insert_placeholder(thd, key, key_length)))
-    DBUG_RETURN(-1);
-
-  table_list->table=table;
-
-  /* Return 1 if table is in use */
-  DBUG_RETURN(test(remove_table_from_cache(thd, db, table_list->table_name,
-             check_in_use ? RTFC_NO_FLAG : RTFC_WAIT_OTHER_THREAD_FLAG)));
-}
-
-
-void unlock_table_name(THD *thd, TABLE_LIST *table_list)
-{
-  if (table_list->table)
-  {
-    hash_delete(&open_cache, (uchar*) table_list->table);
-    broadcast_refresh();
-  }
-}
-
-
-static bool locked_named_table(THD *thd, TABLE_LIST *table_list)
-{
-  for (; table_list ; table_list=table_list->next_local)
-  {
-    TABLE *table= table_list->table;
-    if (table)
-    {
-      TABLE *save_next= table->next;
-      bool result;
-      table->next= 0;
-      result= table_is_used(table_list->table, 0);
-      table->next= save_next;
-      if (result)
-        return 1;
-    }
-  }
-  return 0;					// All tables are locked
-}
-
-
-bool wait_for_locked_table_names(THD *thd, TABLE_LIST *table_list)
-{
-  bool result=0;
-  DBUG_ENTER("wait_for_locked_table_names");
-
-  safe_mutex_assert_owner(&LOCK_open);
-
-  while (locked_named_table(thd,table_list))
-  {
-    if (thd->killed)
-    {
-      result=1;
-      break;
-    }
-    wait_for_condition(thd, &LOCK_open, &COND_refresh);
-    pthread_mutex_lock(&LOCK_open);
-  }
-  DBUG_RETURN(result);
-}
-
-
-/**
-  Lock all tables in list with a name lock.
-
-  REQUIREMENTS
-  - One must have a lock on LOCK_open when calling this
-
-  @param thd			Thread handle
-  @param table_list		Names of tables to lock
-
-  @note
-    If you are just locking one table, you should use
-    lock_and_wait_for_table_name().
-
-  @retval
-    0	ok
-  @retval
-    1	Fatal error (end of memory ?)
+   @retval FALSE  Success.
+   @retval TRUE   Failure (OOM or thread was killed).
 */
 
 bool lock_table_names(THD *thd, TABLE_LIST *table_list)
 {
-  bool got_all_locks=1;
   TABLE_LIST *lock_table;
+  MDL_LOCK_DATA *mdl_lock_data;
 
   for (lock_table= table_list; lock_table; lock_table= lock_table->next_local)
   {
-    int got_lock;
-    if ((got_lock=lock_table_name(thd,lock_table, TRUE)) < 0)
-      goto end;					// Fatal error
-    if (got_lock)
-      got_all_locks=0;				// Someone is using table
+    if (!(mdl_lock_data= mdl_alloc_lock(0, lock_table->db,
+                                        lock_table->table_name,
+                                        thd->mem_root)))
+      goto end;
+    mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE);
+    mdl_add_lock(&thd->mdl_context, mdl_lock_data);
+    lock_table->mdl_lock_data= mdl_lock_data;
   }
-
-  /* If some table was in use, wait until we got the lock */
-  if (!got_all_locks && wait_for_locked_table_names(thd, table_list))
+  if (mdl_acquire_exclusive_locks(&thd->mdl_context))
     goto end;
   return 0;
 
 end:
-  unlock_table_names(thd, table_list, lock_table);
+  mdl_remove_all_locks(&thd->mdl_context);
   return 1;
 }
 
 
 /**
-  Unlock all tables in list with a name lock.
+   Release all metadata locks previously obtained by lock_table_names().
 
-  @param thd        Thread handle.
-  @param table_list Names of tables to lock.
+   @param thd  Thread handle.
 
-  @note 
-    This function needs to be protected by LOCK_open. If we're 
-    under LOCK TABLES, this function does not work as advertised. Namely,
-    it does not exclude other threads from using this table and does not
-    put an exclusive name lock on this table into the table cache.
-
-  @see lock_table_names
-  @see unlock_table_names
-
-  @retval TRUE An error occured.
-  @retval FALSE Name lock successfully acquired.
+   @note Cannot be called while holding LOCK_open mutex.
 */
 
-bool lock_table_names_exclusively(THD *thd, TABLE_LIST *table_list)
-{
-  if (lock_table_names(thd, table_list))
-    return TRUE;
-
-  /*
-    Upgrade the table name locks from semi-exclusive to exclusive locks.
-  */
-  for (TABLE_LIST *table= table_list; table; table= table->next_global)
-  {
-    if (table->table)
-      table->table->open_placeholder= 1;
-  }
-  return FALSE;
-}
-
-
-/**
-  Test is 'table' is protected by an exclusive name lock.
-
-  @param[in] thd        The current thread handler
-  @param[in] table_list Table container containing the single table to be
-                        tested
-
-  @note Needs to be protected by LOCK_open mutex.
-
-  @return Error status code
-    @retval TRUE Table is protected
-    @retval FALSE Table is not protected
-*/
-
-bool
-is_table_name_exclusively_locked_by_this_thread(THD *thd,
-                                                TABLE_LIST *table_list)
-{
-  char  key[MAX_DBKEY_LENGTH];
-  uint  key_length;
-
-  key_length= create_table_def_key(thd, key, table_list, 0);
-
-  return is_table_name_exclusively_locked_by_this_thread(thd, (uchar *)key,
-                                                         key_length);
-}
-
-
-/**
-  Test is 'table key' is protected by an exclusive name lock.
-
-  @param[in] thd        The current thread handler.
-  @param[in] key
-  @param[in] key_length
-
-  @note Needs to be protected by LOCK_open mutex
-
-  @retval TRUE Table is protected
-  @retval FALSE Table is not protected
- */
-
-bool
-is_table_name_exclusively_locked_by_this_thread(THD *thd, uchar *key,
-                                                int key_length)
-{
-  HASH_SEARCH_STATE state;
-  TABLE *table;
-
-  for (table= (TABLE*) hash_first(&open_cache, key,
-                                  key_length, &state);
-       table ;
-       table= (TABLE*) hash_next(&open_cache, key,
-                                 key_length, &state))
-  {
-    if (table->in_use == thd &&
-        table->open_placeholder == 1 &&
-        table->s->version == 0)
-      return TRUE;
-  }
-
-  return FALSE;
-}
-
-/**
-  Unlock all tables in list with a name lock.
-
-  @param
-    thd			Thread handle
-  @param
-    table_list		Names of tables to unlock
-  @param
-    last_table		Don't unlock any tables after this one.
-			        (default 0, which will unlock all tables)
-
-  @note
-    One must have a lock on LOCK_open when calling this.
-
-  @note
-    This function will broadcast refresh signals to inform other threads
-    that the name locks are removed.
-
-  @retval
-    0	ok
-  @retval
-    1	Fatal error (end of memory ?)
-*/
-
-void unlock_table_names(THD *thd, TABLE_LIST *table_list,
-			TABLE_LIST *last_table)
+void unlock_table_names(THD *thd)
 {
   DBUG_ENTER("unlock_table_names");
-  for (TABLE_LIST *table= table_list;
-       table != last_table;
-       table= table->next_local)
-    unlock_table_name(thd,table);
-  broadcast_refresh();
+  mdl_release_locks(&thd->mdl_context);
+  mdl_remove_all_locks(&thd->mdl_context);
   DBUG_VOID_RETURN;
 }
 
@@ -1440,6 +1117,33 @@ bool lock_global_read_lock(THD *thd)
     thd->global_read_lock= GOT_GLOBAL_READ_LOCK;
     global_read_lock++;
     thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock
+    /*
+      When we perform FLUSH TABLES or ALTER TABLE under LOCK TABLES,
+      tables being reopened are protected only by meta-data locks at
+      some point. To avoid sneaking in with our global read lock at
+      this moment we have to take global shared meta data lock.
+
+      TODO: We should change this code to acquire global shared metadata
+            lock before acquiring global read lock. But in order to do
+            this we have to get rid of all those places in which
+            wait_if_global_read_lock() is called before acquiring
+            metadata locks first. Also long-term we should get rid of
+            redundancy between metadata locks, global read lock and DDL
+            blocker (see WL#4399 and WL#4400).
+    */
+    if (mdl_acquire_global_shared_lock(&thd->mdl_context))
+    {
+      /* Our thread was killed -- return back to initial state. */
+      pthread_mutex_lock(&LOCK_global_read_lock);
+      if (!(--global_read_lock))
+      {
+        DBUG_PRINT("signal", ("Broadcasting COND_global_read_lock"));
+        pthread_cond_broadcast(&COND_global_read_lock);
+      }
+      pthread_mutex_unlock(&LOCK_global_read_lock);
+      thd->global_read_lock= 0;
+      DBUG_RETURN(1);
+    }
   }
   /*
     We DON'T set global_read_lock_blocks_commit now, it will be set after
@@ -1461,6 +1165,8 @@ void unlock_global_read_lock(THD *thd)
              ("global_read_lock: %u  global_read_lock_blocks_commit: %u",
               global_read_lock, global_read_lock_blocks_commit));
 
+  mdl_release_global_shared_lock(&thd->mdl_context);
+
   pthread_mutex_lock(&LOCK_global_read_lock);
   tmp= --global_read_lock;
   if (thd->global_read_lock == MADE_GLOBAL_READ_LOCK_BLOCK_COMMIT)
@@ -1677,7 +1383,7 @@ int try_transactional_lock(THD *thd, TAB
   /* We need to explicitly commit if autocommit mode is active. */
   (void) ha_autocommit_or_rollback(thd, 0);
   /* Close the tables. The locks (if taken) persist in the storage engines. */
-  close_tables_for_reopen(thd, &table_list);
+  close_tables_for_reopen(thd, &table_list, FALSE);
   thd->in_lock_tables= FALSE;
   DBUG_PRINT("lock_info", ("result: %d", result));
   DBUG_RETURN(result);

=== modified file 'sql/log.cc'
--- a/sql/log.cc	2008-05-23 21:39:20 +0000
+++ b/sql/log.cc	2008-06-23 13:26:17 +0000
@@ -3850,7 +3850,7 @@ bool MYSQL_BIN_LOG::write(Log_event *eve
     this will close all tables on the slave.
   */
   bool const end_stmt=
-    thd->prelocked_mode && thd->lex->requires_prelocking();
+    thd->locked_tables_mode && thd->lex->requires_prelocking();
   thd->binlog_flush_pending_rows_event(end_stmt);
 
   pthread_mutex_lock(&LOCK_log);

=== modified file 'sql/log_event.cc'
--- a/sql/log_event.cc	2008-05-16 10:30:46 +0000
+++ b/sql/log_event.cc	2008-06-20 13:11:20 +0000
@@ -2312,7 +2312,7 @@ int Query_log_event::do_apply_event(Rela
   DBUG_PRINT("info", ("log_pos: %lu", (ulong) log_pos));
 
   clear_all_errors(thd, const_cast<Relay_log_info*>(rli));
-  const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+  const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
 
   /*
     Note:   We do not need to execute reset_one_shot_variables() if this
@@ -6418,8 +6418,7 @@ int Rows_log_event::do_apply_event(Relay
      */
     DBUG_ASSERT(get_flags(STMT_END_F));
 
-    const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
-    close_thread_tables(thd);
+    const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
     thd->clear_error();
     DBUG_RETURN(0);
   }
@@ -6495,7 +6494,7 @@ int Rows_log_event::do_apply_event(Relay
                      "unexpected success or fatal error"));
         thd->is_slave_error= 1;
       }
-      const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+      const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
       DBUG_RETURN(actual_error);
     }
 
@@ -6516,7 +6515,7 @@ int Rows_log_event::do_apply_event(Relay
           mysql_unlock_tables(thd, thd->lock);
           thd->lock= 0;
           thd->is_slave_error= 1;
-          const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+          const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
           DBUG_RETURN(ERR_BAD_TABLE_DEF);
         }
       }
@@ -6715,12 +6714,6 @@ int Rows_log_event::do_apply_event(Relay
     }
   } // if (table)
 
-  /*
-    We need to delay this clear until here bacause unpack_current_row() uses
-    master-side table definitions stored in rli.
-  */
-  if (rli->tables_to_lock && get_flags(STMT_END_F))
-    const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
   /* reset OPTION_ALLOW_BATCH as not affect later events */
   thd->options&= ~OPTION_ALLOW_BATCH;
   
@@ -7261,7 +7254,8 @@ Table_map_log_event::~Table_map_log_even
 int Table_map_log_event::do_apply_event(Relay_log_info const *rli)
 {
   RPL_TABLE_LIST *table_list;
-  char *db_mem, *tname_mem;
+  char *db_mem, *tname_mem, *mdlkey;
+  MDL_LOCK_DATA *mdl_lock_data;
   size_t dummy_len;
   void *memory;
   DBUG_ENTER("Table_map_log_event::do_apply_event(Relay_log_info*)");
@@ -7276,6 +7270,8 @@ int Table_map_log_event::do_apply_event(
                                 &table_list, (uint) sizeof(RPL_TABLE_LIST),
                                 &db_mem, (uint) NAME_LEN + 1,
                                 &tname_mem, (uint) NAME_LEN + 1,
+                                &mdl_lock_data, sizeof(MDL_LOCK_DATA),
+                                &mdlkey, MAX_MDLKEY_LENGTH,
                                 NullS)))
     DBUG_RETURN(HA_ERR_OUT_OF_MEM);
 
@@ -7288,6 +7284,9 @@ int Table_map_log_event::do_apply_event(
   table_list->updating= 1;
   strmov(table_list->db, rpl_filter->get_rewrite_db(m_dbnam, &dummy_len));
   strmov(table_list->table_name, m_tblnam);
+  mdl_init_lock(mdl_lock_data, mdlkey, 0, table_list->db,
+                table_list->table_name);
+  table_list->mdl_lock_data= mdl_lock_data;
 
   int error= 0;
 

=== modified file 'sql/log_event_old.cc'
--- a/sql/log_event_old.cc	2008-05-16 10:30:46 +0000
+++ b/sql/log_event_old.cc	2008-05-27 17:31:53 +0000
@@ -32,8 +32,7 @@ Old_rows_log_event::do_apply_event(Old_r
      */
     DBUG_ASSERT(ev->get_flags(Old_rows_log_event::STMT_END_F));
 
-    const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
-    close_thread_tables(thd);
+    const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
     thd->clear_error();
     DBUG_RETURN(0);
   }
@@ -91,7 +90,7 @@ Old_rows_log_event::do_apply_event(Old_r
                      "unexpected success or fatal error"));
         thd->is_slave_error= 1;
       }
-      const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+      const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
       DBUG_RETURN(actual_error);
     }
 
@@ -109,10 +108,8 @@ Old_rows_log_event::do_apply_event(Old_r
       {
         if (ptr->m_tabledef.compatible_with(rli, ptr->table))
         {
-          mysql_unlock_tables(thd, thd->lock);
-          thd->lock= 0;
           thd->is_slave_error= 1;
-          const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+          const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
           DBUG_RETURN(Old_rows_log_event::ERR_BAD_TABLE_DEF);
         }
       }
@@ -236,13 +233,6 @@ Old_rows_log_event::do_apply_event(Old_r
     }
   }
 
-  /*
-    We need to delay this clear until the table def is no longer needed.
-    The table def is needed in unpack_row().
-  */
-  if (rli->tables_to_lock && ev->get_flags(Old_rows_log_event::STMT_END_F))
-    const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
-
   if (error)
   {                     /* error has occured during the transaction */
     rli->report(ERROR_LEVEL, thd->main_da.sql_errno(),
@@ -1448,8 +1438,7 @@ int Old_rows_log_event::do_apply_event(R
      */
     DBUG_ASSERT(get_flags(STMT_END_F));
 
-    const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
-    close_thread_tables(thd);
+    const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
     thd->clear_error();
     DBUG_RETURN(0);
   }
@@ -1480,7 +1469,8 @@ int Old_rows_log_event::do_apply_event(R
     lex_start(thd);
 
     while ((error= lock_tables(thd, rli->tables_to_lock,
-                               rli->tables_to_lock_count, &need_reopen)))
+                               rli->tables_to_lock_count, 0,
+                               &need_reopen)))
     {
       if (!need_reopen)
       {
@@ -1504,7 +1494,7 @@ int Old_rows_log_event::do_apply_event(R
                       "Error in %s event: when locking tables",
                       get_type_str());
         }
-        const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+        const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
         DBUG_RETURN(error);
       }
 
@@ -1523,7 +1513,7 @@ int Old_rows_log_event::do_apply_event(R
        */
       thd->binlog_flush_pending_rows_event(false);
       TABLE_LIST *tables= rli->tables_to_lock;
-      close_tables_for_reopen(thd, &tables);
+      close_tables_for_reopen(thd, &tables, FALSE);
 
       uint tables_count= rli->tables_to_lock_count;
       if ((error= open_tables(thd, &tables, &tables_count, 0)))
@@ -1541,7 +1531,7 @@ int Old_rows_log_event::do_apply_event(R
                        "unexpected success or fatal error"));
           thd->is_slave_error= 1;
         }
-        const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+        const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
         DBUG_RETURN(error);
       }
     }
@@ -1560,10 +1550,8 @@ int Old_rows_log_event::do_apply_event(R
       {
         if (ptr->m_tabledef.compatible_with(rli, ptr->table))
         {
-          mysql_unlock_tables(thd, thd->lock);
-          thd->lock= 0;
           thd->is_slave_error= 1;
-          const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+          const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
           DBUG_RETURN(ERR_BAD_TABLE_DEF);
         }
       }
@@ -1730,13 +1718,6 @@ int Old_rows_log_event::do_apply_event(R
       thd->options|= OPTION_KEEP_LOG;
     }
   } // if (table)
-
-  /*
-    We need to delay this clear until here bacause unpack_current_row() uses
-    master-side table definitions stored in rli.
-  */
-  if (rli->tables_to_lock && get_flags(STMT_END_F))
-    const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
 
   if (error)
   {                     /* error has occured during the transaction */

=== added file 'sql/mdl.cc'
--- a/sql/mdl.cc	1970-01-01 00:00:00 +0000
+++ b/sql/mdl.cc	2008-06-20 13:11:20 +0000
@@ -0,0 +1,1617 @@
+/* Copyright (C) 2007-2008 MySQL AB
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; version 2 of the License.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
+
+
+#include "mdl.h"
+
+#include <hash.h>
+#include <mysqld_error.h>
+
+
+/**
+   The lock context. Created internally for an acquired lock.
+   For a given name, there exists only one MDL_LOCK instance,
+   and it exists only when the lock has been granted.
+   Can be seen as an MDL subsystem's version of TABLE_SHARE.
+*/
+
+struct MDL_LOCK
+{
+  I_P_List<MDL_LOCK_DATA, MDL_LOCK_DATA_lock> active_shared;
+  /*
+    There can be several upgraders and active exclusive
+    belonging to the same context.
+  */
+  I_P_List<MDL_LOCK_DATA, MDL_LOCK_DATA_lock> active_shared_waiting_upgrade;
+  I_P_List<MDL_LOCK_DATA, MDL_LOCK_DATA_lock> active_exclusive;
+  I_P_List<MDL_LOCK_DATA, MDL_LOCK_DATA_lock> waiting_exclusive;
+  /**
+     Number of MDL_LOCK_DATA objects associated with this MDL_LOCK instance
+     and therefore present in one of above lists. Note that this number
+     doesn't account for pending requests for shared lock since we don't
+     associate them with MDL_LOCK and don't keep them in any list.
+  */
+  uint   lock_data_count;
+  void   *cached_object;
+  mdl_cached_object_release_hook cached_object_release_hook;
+
+  MDL_LOCK() : cached_object(0), cached_object_release_hook(0) {}
+
+  MDL_LOCK_DATA *get_key_owner()
+  {
+     return !active_shared.is_empty() ?
+            active_shared.head() :
+            (!active_shared_waiting_upgrade.is_empty() ?
+             active_shared_waiting_upgrade.head() :
+             (!active_exclusive.is_empty() ?
+              active_exclusive.head() : waiting_exclusive.head()));
+  }
+
+  bool has_one_lock_data()
+  {
+    return (lock_data_count == 1);
+  }
+};
+
+
+pthread_mutex_t LOCK_mdl;
+pthread_cond_t  COND_mdl;
+HASH mdl_locks;
+
+/**
+   Structure implementing global metadata lock. The only types
+   of locks which are supported at the moment are shared and
+   intention exclusive locks. Note that the latter type of global
+   lock acquired automatically when one tries to acquire exclusive
+   or shared upgradable lock on particular object.
+*/
+
+struct MDL_GLOBAL_LOCK
+{
+  uint waiting_shared;
+  uint active_shared;
+  uint active_intention_exclusive;
+} global_lock;
+
+
+extern "C" uchar *mdl_locks_key(const uchar *record, size_t *length,
+                                my_bool not_used __attribute__((unused)))
+{
+  MDL_LOCK *entry=(MDL_LOCK*) record;
+  *length= entry->get_key_owner()->key_length;
+  return (uchar*) entry->get_key_owner()->key;
+}
+
+
+/**
+   Initialize the metadata locking subsystem.
+
+   This function is called at server startup.
+
+   In particular, initializes the new global mutex and
+   the associated condition variable: LOCK_mdl and COND_mdl.
+   These locking primitives are implementation details of the MDL
+   subsystem and are private to it.
+
+   Note, that even though the new implementation adds acquisition
+   of a new global mutex to the execution flow of almost every SQL
+   statement, the design capitalizes on that to later save on
+   look ups in the table definition cache. This leads to reduced
+   contention overall and on LOCK_open in particular.
+   Please see the description of mdl_acquire_shared_lock() for details.
+*/
+
+void mdl_init()
+{
+  pthread_mutex_init(&LOCK_mdl, NULL);
+  pthread_cond_init(&COND_mdl, NULL);
+  hash_init(&mdl_locks, &my_charset_bin, 16 /* FIXME */, 0, 0,
+            mdl_locks_key, 0, 0);
+  global_lock.waiting_shared= global_lock.active_shared= 0;
+  global_lock.active_intention_exclusive= 0;
+}
+
+
+/**
+   Release resources of metadata locking subsystem.
+
+   Destroys the global mutex and the condition variable.
+   Called at server shutdown.
+*/
+
+void mdl_destroy()
+{
+  DBUG_ASSERT(!mdl_locks.records);
+  pthread_mutex_destroy(&LOCK_mdl);
+  pthread_cond_destroy(&COND_mdl);
+  hash_free(&mdl_locks);
+}
+
+
+/**
+   Initialize a metadata locking context.
+
+   This is to be called when a new server connection is created.
+*/
+
+void mdl_context_init(MDL_CONTEXT *context, THD *thd)
+{
+  context->locks.empty();
+  context->thd= thd;
+  context->has_global_shared_lock= FALSE;
+}
+
+
+/**
+   Destroy metadata locking context.
+
+   Assumes and asserts that there are no active or pending locks
+   associated with this context at the time of the destruction.
+
+   Currently does nothing. Asserts that there are no pending
+   or satisfied lock requests. The pending locks must be released
+   prior to destruction. This is a new way to express the assertion
+   that all tables are closed before a connection is destroyed.
+*/
+
+void mdl_context_destroy(MDL_CONTEXT *context)
+{
+  DBUG_ASSERT(context->locks.is_empty());
+  DBUG_ASSERT(!context->has_global_shared_lock);
+}
+
+
+/**
+   Backup and reset state of meta-data locking context.
+
+   mdl_context_backup_and_reset(), mdl_context_restore() and
+   mdl_context_merge() are used by HANDLER implementation which
+   needs to open table for new HANDLER independently of already
+   open HANDLERs and add this table/metadata lock to the set of
+   tables open/metadata locks for HANDLERs afterwards.
+*/
+
+void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup)
+{
+  backup->locks.empty();
+  ctx->locks.swap(backup->locks);
+}
+
+
+/**
+   Restore state of meta-data locking context from backup.
+*/
+
+void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup)
+{
+  DBUG_ASSERT(ctx->locks.is_empty());
+  ctx->locks.swap(backup->locks);
+}
+
+
+/**
+   Merge meta-data locks from one context into another.
+*/
+
+void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src)
+{
+  MDL_LOCK_DATA *lock_data;
+
+  DBUG_ASSERT(dst->thd == src->thd);
+
+  if (!src->locks.is_empty())
+  {
+    I_P_List_iterator<MDL_LOCK_DATA, MDL_LOCK_DATA_context> it(src->locks);
+    while ((lock_data= it++))
+    {
+      DBUG_ASSERT(lock_data->ctx);
+      lock_data->ctx= dst;
+      dst->locks.push_front(lock_data);
+    }
+    src->locks.empty();
+  }
+}
+
+
+/**
+   Initialize a lock request.
+
+   This is to be used for every lock request.
+
+   Note that initialization and allocation are split
+   into two calls. This is to allow flexible memory management
+   of lock requests. Normally a lock request is stored
+   in statement memory (e.g. is a member of struct TABLE_LIST),
+   but we would also like to allow allocation of lock
+   requests in other memory roots, for example in the grant
+   subsystem, to lock privilege tables.
+
+   The MDL subsystem does not own or manage memory of lock
+   requests. Instead it assumes that the life time of every lock
+   request encloses calls to mdl_acquire_shared_lock() and
+   mdl_release_locks().
+
+   @param  lock_data  Pointer to an MDL_LOCK_DATA object to initialize
+   @param  key_buff   Pointer to the buffer for key for the lock request
+                      (should be at least strlen(db) + strlen(name)
+                      + 2 bytes, or, if the lengths are not known,                                                                                       MAX_DBNAME_LENGTH)
+   @param  type       Id of type of object to be locked
+   @param  db         Name of database to which the object belongs
+   @param  name       Name of of the object
+
+   Stores the database name, object name and the type in the key
+   buffer. Initializes mdl_el to point to the key.
+   We can't simply initialize MDL_LOCK_DATA with type, db and name
+   by-pointer because of the underlying HASH implementation
+   requires the key to be a contiguous buffer.
+
+   The initialized lock request will have MDL_SHARED type.
+
+   Suggested lock types: TABLE - 0 PROCEDURE - 1 FUNCTION - 2
+   Note that tables and views have the same lock type, since
+   they share the same name space in the SQL standard.
+*/
+
+void mdl_init_lock(MDL_LOCK_DATA *lock_data, char *key, int type,
+                   const char *db, const char *name)
+{
+  int4store(key, type);
+  lock_data->key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
+  lock_data->key= key;
+  lock_data->type= MDL_SHARED;
+  lock_data->state= MDL_INITIALIZED;
+#ifndef DBUG_OFF
+  lock_data->ctx= 0;
+  lock_data->lock= 0;
+#endif
+}
+
+
+/**
+   Allocate and initialize one lock request.
+
+   Same as mdl_init_lock(), but allocates the lock and the key buffer
+   on a memory root. Necessary to lock ad-hoc tables, e.g.
+   mysql.* tables of grant and data dictionary subsystems.
+
+   @param  type       Id of type of object to be locked
+   @param  db         Name of database to which object belongs
+   @param  name       Name of of object
+   @param  root       MEM_ROOT on which object should be allocated
+
+   @note The allocated lock request will have MDL_SHARED type.
+
+   @retval 0      Error
+   @retval non-0  Pointer to an object representing a lock request
+*/
+
+MDL_LOCK_DATA *mdl_alloc_lock(int type, const char *db, const char *name,
+                              MEM_ROOT *root)
+{
+  MDL_LOCK_DATA *lock_data;
+  char *key;
+
+  if (!multi_alloc_root(root, &lock_data, sizeof(MDL_LOCK_DATA), &key,
+                        MAX_MDLKEY_LENGTH, NULL))
+    return NULL;
+
+  mdl_init_lock(lock_data, key, type, db, name);
+
+  return lock_data;
+}
+
+
+/**
+   Add a lock request to the list of lock requests of the context.
+
+   The procedure to acquire metadata locks is:
+     - allocate and initialize lock requests (mdl_alloc_lock())
+     - associate them with a context (mdl_add_lock())
+     - call mdl_acquire_shared_lock()/mdl_release_lock() (maybe repeatedly).
+
+   Associates a lock request with the given context.
+
+   @param  context    The MDL context to associate the lock with.
+                      There should be no more than one context per
+                      connection, to avoid deadlocks.
+   @param  lock_data  The lock request to be added.
+*/
+
+void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data)
+{
+  DBUG_ENTER("mdl_add_lock");
+  DBUG_ASSERT(lock_data->state == MDL_INITIALIZED);
+  DBUG_ASSERT(!lock_data->ctx);
+  lock_data->ctx= context;
+  context->locks.push_front(lock_data);
+  DBUG_VOID_RETURN;
+}
+
+
+/**
+   Remove a lock request from the list of lock requests of the context.
+
+   Disassociates a lock request from the given context.
+
+   @param  context    The MDL context to remove the lock from.
+   @param  lock_data  The lock request to be removed.
+
+   @pre The lock request being removed should correspond to lock which
+        was released or was not acquired.
+
+   @note Resets lock request for lock released back to its initial state
+         (i.e. sets type to MDL_SHARED).
+*/
+
+void mdl_remove_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data)
+{
+  DBUG_ENTER("mdl_remove_lock");
+  DBUG_ASSERT(lock_data->state == MDL_INITIALIZED);
+  DBUG_ASSERT(context == lock_data->ctx);
+  /* Reset lock request back to its initial state. */
+  lock_data->type= MDL_SHARED;
+#ifndef DBUG_OFF
+  lock_data->ctx= 0;
+#endif
+  context->locks.remove(lock_data);
+  DBUG_VOID_RETURN;
+}
+
+
+/**
+   Clear all lock requests in the context (clear the context).
+
+   Disassociates lock requests from the context.
+   All granted locks must be released prior to calling this
+   function.
+
+   In other words, the expected procedure to release locks is:
+     - mdl_release_locks();
+     - mdl_remove_all_locks();
+
+   We could possibly merge mdl_remove_all_locks() and mdl_release_locks(),
+   but this function comes in handy when we need to back off: in that case
+   we release all the locks acquired so-far but do not free them, since
+   we know that the respective lock requests will be used again.
+
+   Also resets lock requests back to their initial state (i.e. MDL_SHARED).
+
+   @param context Context to be cleared.
+*/
+
+void mdl_remove_all_locks(MDL_CONTEXT *context)
+{
+  MDL_LOCK_DATA *lock_data;
+  I_P_List_iterator<MDL_LOCK_DATA, MDL_LOCK_DATA_context> it(context->locks);
+  while ((lock_data= it++))
+  {
+    /* Reset lock request back to its initial state. */
+    lock_data->type= MDL_SHARED;
+#ifndef DBUG_OFF
+    lock_data->ctx= 0;
+#endif
+  }
+  context->locks.empty();
+}
+
+
+/**
+   Auxiliary functions needed for creation/destruction of MDL_LOCK
+   objects.
+
+   @todo This naive implementation should be replaced with one that saves
+         on memory allocation by reusing released objects.
+*/
+
+static MDL_LOCK* get_lock_object(void)
+{
+  return new MDL_LOCK();
+}
+
+
+static void release_lock_object(MDL_LOCK *lock)
+{
+  delete lock;
+}
+
+
+/**
+   Helper functions which simplifies writing various checks and asserts.
+*/
+
+static bool is_shared(MDL_LOCK_DATA *lock_data)
+{
+  return (lock_data->type < MDL_EXCLUSIVE);
+}
+
+
+/**
+   Helper functions and macros to be used for killable waiting in metadata
+   locking subsystem.
+
+   @sa THD::enter_cond()/exit_cond()/killed.
+
+   @note We can't use THD::enter_cond()/exit_cond()/killed directly here
+         since this will make metadata subsystem dependant on THD class
+         and thus prevent us from writing unit tests for it. And usage of
+         wrapper functions to access THD::killed/enter_cond()/exit_cond()
+         will probably introduce too much overhead.
+*/
+
+#define MDL_ENTER_COND(A, B) mdl_enter_cond(A, B, __func__, __FILE__, __LINE__)
+
+static inline const char* mdl_enter_cond(MDL_CONTEXT *context,
+                                         st_my_thread_var *mysys_var,
+                                         const char *calling_func,
+                                         const char *calling_file,
+                                         const unsigned int calling_line)
+{
+  safe_mutex_assert_owner(&LOCK_mdl);
+
+  mysys_var->current_mutex= &LOCK_mdl;
+  mysys_var->current_cond= &COND_mdl;
+
+  return set_thd_proc_info(context->thd, "Waiting for table",
+                           calling_func, calling_file, calling_line);
+}
+
+#define MDL_EXIT_COND(A, B, C) mdl_exit_cond(A, B, C, __func__, __FILE__, __LINE__)
+
+static inline void mdl_exit_cond(MDL_CONTEXT *context,
+                                 st_my_thread_var *mysys_var,
+                                 const char* old_msg,
+                                 const char *calling_func,
+                                 const char *calling_file,
+                                 const unsigned int calling_line)
+{
+  DBUG_ASSERT(&LOCK_mdl == mysys_var->current_mutex);
+
+  pthread_mutex_unlock(&LOCK_mdl);
+  pthread_mutex_lock(&mysys_var->mutex);
+  mysys_var->current_mutex= 0;
+  mysys_var->current_cond= 0;
+  pthread_mutex_unlock(&mysys_var->mutex);
+
+  (void) set_thd_proc_info(context->thd, old_msg, calling_func,
+                           calling_file, calling_line);
+}
+
+
+/**
+   Check if request for the lock on particular object can be satisfied given
+   current state of the global metadata lock.
+
+   @note In other words, we're trying to check that the individual lock
+         request, implying a form of lock on the global metadata, is
+         compatible with the current state of the global metadata lock.
+
+   @param lock_data Request for lock on an individual object, implying a
+                    certain kind of global metadata lock.
+
+   @retval TRUE  - Lock request can be satisfied
+   @retval FALSE - There is some conflicting lock
+
+   Here is a compatibility matrix defined by this function:
+
+                   |             | Satisfied or pending requests
+                   |             | for global metadata lock
+   ----------------+-------------+--------------------------------------------
+   Type of request | Correspond. |
+   for indiv. lock | global lock | Active-S  Pending-S  Active-IS(**) Active-IX
+   ----------------+-------------+--------------------------------------------
+   S, high-prio S  |   IS        |    +         +          +             +
+   upgradable S    |   IX        |    -         -          +             +
+   X               |   IX        |    -         -          +             +
+   S upgraded to X |   IX (*)    |    0         +          +             +
+
+   Here: "+" -- means that request can be satisfied
+         "-" -- means that request can't be satisfied and should wait
+         "0" -- means impossible situation which will trigger assert
+
+   (*)  Since for upgradable shared locks we always take intention exclusive
+        global lock at the same time when obtaining the shared lock, there
+        is no need to obtain such lock during the upgrade itself.
+   (**) Since intention shared global locks are compatible with all other
+        type of locks we don't even have any accounting for them.
+*/
+
+static bool can_grant_global_lock(MDL_LOCK_DATA *lock_data)
+{
+  switch (lock_data->type)
+  {
+  case MDL_SHARED:
+  case MDL_SHARED_HIGH_PRIO:
+    return TRUE;
+    break;
+  case MDL_SHARED_UPGRADABLE:
+    if (global_lock.active_shared || global_lock.waiting_shared)
+    {
+      /*
+        We are going to obtain intention exclusive global lock and
+        there is active or pending shared global lock. Have to wait.
+      */
+      return FALSE;
+    }
+    else
+      return TRUE;
+    break;
+  case MDL_EXCLUSIVE:
+    if (lock_data->state == MDL_PENDING_UPGRADE)
+    {
+      /*
+        We are upgrading MDL_SHARED to MDL_EXCLUSIVE.
+
+        There should be no conflicting global locks since for each upgradable
+        shared lock we obtain intention exclusive global lock first.
+      */
+      DBUG_ASSERT(global_lock.active_shared == 0 &&
+                  global_lock.active_intention_exclusive);
+      return TRUE;
+    }
+    else
+    {
+      if (global_lock.active_shared || global_lock.waiting_shared)
+      {
+        /*
+          We are going to obtain intention exclusive global lock and
+          there is active or pending shared global lock.
+        */
+        return FALSE;
+      }
+      else
+        return TRUE;
+    }
+    break;
+  default:
+    DBUG_ASSERT(0);
+  }
+  return FALSE;
+}
+
+
+/**
+   Check if request for the lock can be satisfied given current state of lock.
+
+   @param  lock       Lock.
+   @param  lock_data  Request for lock.
+
+   @retval TRUE   Lock request can be satisfied
+   @retval FALSE  There is some conflicting lock.
+
+   This function defines the following compatibility matrix for metadata locks:
+
+                   | Satisfied or pending requests which we have in MDL_LOCK
+   ----------------+---------------------------------------------------------
+   Current request | Active-S  Pending-X Active-X Act-S-pend-upgrade-to-X
+   ----------------+---------------------------------------------------------
+   S, upgradable S |    +         -         - (*)           -
+   High-prio S     |    +         +         -               +
+   X               |    -         +         -               -
+   S upgraded to X |    - (**)    +         0               0
+
+   Here: "+" -- means that request can be satisfied
+         "-" -- means that request can't be satisfied and should wait
+         "0" -- means impossible situation which will trigger assert
+
+   (*)  Unless active exclusive lock belongs to the same context as shared
+        lock being requested.
+   (**) Unless all active shared locks belong to the same context as one
+        being upgraded.
+*/
+
+static bool can_grant_lock(MDL_LOCK *lock, MDL_LOCK_DATA *lock_data)
+{
+  switch (lock_data->type)
+  {
+  case MDL_SHARED:
+  case MDL_SHARED_UPGRADABLE:
+  case MDL_SHARED_HIGH_PRIO:
+    if ((lock->active_exclusive.is_empty() &&
+         (lock_data->type == MDL_SHARED_HIGH_PRIO ||
+          lock->waiting_exclusive.is_empty() &&
+          lock->active_shared_waiting_upgrade.is_empty())) ||
+        (!lock->active_exclusive.is_empty() &&
+         lock->active_exclusive.head()->ctx == lock_data->ctx))
+    {
+      /*
+        When exclusive lock comes from the same context we can satisfy our
+        shared lock. This is required for CREATE TABLE ... SELECT ... and
+        ALTER VIEW ... AS ....
+      */
+      return TRUE;
+    }
+    else
+      return FALSE;
+    break;
+  case MDL_EXCLUSIVE:
+    if (lock_data->state == MDL_PENDING_UPGRADE)
+    {
+      /* We are upgrading MDL_SHARED to MDL_EXCLUSIVE. */
+      MDL_LOCK_DATA *conf_lock_data;
+      I_P_List_iterator<MDL_LOCK_DATA,
+                        MDL_LOCK_DATA_lock> it(lock->active_shared);
+
+      /*
+        There should be no active exclusive locks since we own shared lock
+        on the object.
+      */
+      DBUG_ASSERT(lock->active_exclusive.is_empty() &&
+                  lock->active_shared_waiting_upgrade.head() == lock_data);
+
+      while ((conf_lock_data= it++))
+      {
+        /*
+          When upgrading shared lock to exclusive one we can have other shared
+          locks for the same object in the same context, e.g. in case when several
+          instances of TABLE are open.
+        */
+        if (conf_lock_data->ctx != lock_data->ctx)
+          return FALSE;
+      }
+      return TRUE;
+    }
+    else
+    {
+      return (lock->active_exclusive.is_empty() &&
+              lock->active_shared_waiting_upgrade.is_empty() &&
+              lock->active_shared.is_empty());
+    }
+    break;
+  default:
+    DBUG_ASSERT(0);
+  }
+  return FALSE;
+}
+
+
+/**
+   Try to acquire one shared lock.
+
+   Unlike exclusive locks, shared locks are acquired one by
+   one. This is interface is chosen to simplify introduction of
+   the new locking API to the system. mdl_acquire_shared_lock()
+   is currently used from open_table(), and there we have only one
+   table to work with.
+
+   In future we may consider allocating multiple shared locks at once.
+
+   This function must be called after the lock is added to a context.
+
+   @param context    [in]  Context containing request for lock
+   @param lock_data  [in]  Lock request object for lock to be acquired
+   @param retry      [out] Indicates that conflicting lock exists and another
+                           attempt should be made after releasing all current
+                           locks and waiting for conflicting lock go away
+                           (using mdl_wait_for_locks()).
+
+   @retval  FALSE   Success.
+   @retval  TRUE    Failure. Either error occured or conflicting lock exists.
+                    In the latter case "retry" parameter is set to TRUE.
+*/
+
+bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data,
+                             bool *retry)
+{
+  MDL_LOCK *lock;
+  *retry= FALSE;
+
+  DBUG_ASSERT(is_shared(lock_data) && lock_data->state == MDL_INITIALIZED);
+
+  DBUG_ASSERT(lock_data->ctx == context);
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  if (context->has_global_shared_lock &&
+      lock_data->type == MDL_SHARED_UPGRADABLE)
+  {
+    my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0));
+    return TRUE;
+  }
+
+  pthread_mutex_lock(&LOCK_mdl);
+
+  if (!can_grant_global_lock(lock_data))
+  {
+    pthread_mutex_unlock(&LOCK_mdl);
+    *retry= TRUE;
+    return TRUE;
+  }
+
+  if (!(lock= (MDL_LOCK *)hash_search(&mdl_locks, (uchar*)lock_data->key,
+                                      lock_data->key_length)))
+  {
+    if (!(lock= get_lock_object()))
+    {
+      pthread_mutex_unlock(&LOCK_mdl);
+      return TRUE;
+    }
+    /*
+      Before inserting MDL_LOCK object into hash we should add at least one
+      MDL_LOCK_DATA to its lists in order to provide key for this element.
+      Thus we can't merge two branches of the above if-statement.
+    */
+    lock->active_shared.push_front(lock_data);
+    lock->lock_data_count= 1;
+    if (my_hash_insert(&mdl_locks, (uchar*)lock))
+    {
+      release_lock_object(lock);
+      pthread_mutex_unlock(&LOCK_mdl);
+      return TRUE;
+    }
+    lock_data->state= MDL_ACQUIRED;
+    lock_data->lock= lock;
+    if (lock_data->type == MDL_SHARED_UPGRADABLE)
+      global_lock.active_intention_exclusive++;
+  }
+  else
+  {
+    if (can_grant_lock(lock, lock_data))
+    {
+      lock->active_shared.push_front(lock_data);
+      lock->lock_data_count++;
+      lock_data->state= MDL_ACQUIRED;
+      lock_data->lock= lock;
+      if (lock_data->type == MDL_SHARED_UPGRADABLE)
+        global_lock.active_intention_exclusive++;
+    }
+    else
+      *retry= TRUE;
+  }
+  pthread_mutex_unlock(&LOCK_mdl);
+
+  return *retry;
+}
+
+
+static void release_lock(MDL_LOCK_DATA *lock_data);
+
+
+/**
+   Acquire exclusive locks. The context must contain the list of
+   locks to be acquired. There must be no granted locks in the
+   context.
+
+   This is a replacement of lock_table_names(). It is used in
+   RENAME, DROP and other DDL SQL statements.
+
+   @param context  A context containing requests for exclusive locks
+                   The context may not have other lock requests.
+
+   @retval FALSE  Success
+   @retval TRUE   Failure
+*/
+
+bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context)
+{
+  MDL_LOCK_DATA *lock_data;
+  MDL_LOCK *lock;
+  bool signalled= FALSE;
+  const char *old_msg;
+  I_P_List_iterator<MDL_LOCK_DATA, MDL_LOCK_DATA_context> it(context->locks);
+  st_my_thread_var *mysys_var= my_thread_var;
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  if (context->has_global_shared_lock)
+  {
+    my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0));
+    return TRUE;
+  }
+
+  pthread_mutex_lock(&LOCK_mdl);
+
+  old_msg= MDL_ENTER_COND(context, mysys_var);
+
+  while ((lock_data= it++))
+  {
+    DBUG_ASSERT(lock_data->type == MDL_EXCLUSIVE &&
+                lock_data->state == MDL_INITIALIZED);
+    if (!(lock= (MDL_LOCK *)hash_search(&mdl_locks, (uchar*)lock_data->key,
+                                        lock_data->key_length)))
+    {
+      if (!(lock= get_lock_object()))
+        goto err;
+      /*
+        Again before inserting MDL_LOCK into hash provide key for
+        it by adding MDL_LOCK_DATA to one of its lists.
+      */
+      lock->waiting_exclusive.push_front(lock_data);
+      lock->lock_data_count= 1;
+      if (my_hash_insert(&mdl_locks, (uchar*)lock))
+      {
+        release_lock_object(lock);
+        goto err;
+      }
+      lock_data->lock= lock;
+      lock_data->state= MDL_PENDING;
+    }
+    else
+    {
+      lock->waiting_exclusive.push_front(lock_data);
+      lock->lock_data_count++;
+      lock_data->lock= lock;
+      lock_data->state= MDL_PENDING;
+    }
+  }
+
+  while (1)
+  {
+    it.rewind();
+    while ((lock_data= it++))
+    {
+      lock= lock_data->lock;
+
+      if (!can_grant_global_lock(lock_data))
+      {
+        /*
+          There is an active or pending global shared lock so we have
+          to wait until it goes away.
+        */
+        signalled= TRUE;
+        break;
+      }
+      else if (!can_grant_lock(lock, lock_data))
+      {
+        MDL_LOCK_DATA *conf_lock_data;
+        I_P_List_iterator<MDL_LOCK_DATA,
+                          MDL_LOCK_DATA_lock> it(lock->active_shared);
+
+        signalled= !lock->active_exclusive.is_empty() ||
+                   !lock->active_shared_waiting_upgrade.is_empty();
+
+        while ((conf_lock_data= it++))
+        {
+          signalled|=
+            mysql_notify_thread_having_shared_lock(context->thd,
+                                                   conf_lock_data->ctx->thd);
+        }
+
+        break;
+      }
+    }
+    if (!lock_data)
+      break;
+    if (signalled)
+      pthread_cond_wait(&COND_mdl, &LOCK_mdl);
+    else
+    {
+      /*
+        Another thread obtained shared MDL-lock on some table but
+        has not yet opened it and/or tried to obtain data lock on
+        it. In this case we need to wait until this happens and try
+        to abort this thread once again.
+      */
+      struct timespec abstime;
+      set_timespec(abstime, 10);
+      pthread_cond_timedwait(&COND_mdl, &LOCK_mdl, &abstime);
+    }
+    if (mysys_var->abort)
+      goto err;
+  }
+  it.rewind();
+  while ((lock_data= it++))
+  {
+    global_lock.active_intention_exclusive++;
+    lock= lock_data->lock;
+    lock->waiting_exclusive.remove(lock_data);
+    lock->active_exclusive.push_front(lock_data);
+    lock_data->state= MDL_ACQUIRED;
+    if (lock->cached_object)
+      (*lock->cached_object_release_hook)(lock->cached_object);
+    lock->cached_object= NULL;
+  }
+  /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */
+  MDL_EXIT_COND(context, mysys_var, old_msg);
+  return FALSE;
+
+err:
+  /*
+    Remove our pending lock requests from the locks.
+    Ignore those lock requests which were not made MDL_PENDING.
+  */
+  it.rewind();
+  while ((lock_data= it++) && lock_data->state == MDL_PENDING)
+  {
+    release_lock(lock_data);
+    lock_data->state= MDL_INITIALIZED;
+  }
+  /* May be some pending requests for shared locks can be satisfied now. */
+  pthread_cond_broadcast(&COND_mdl);
+  MDL_EXIT_COND(context, mysys_var, old_msg);
+  return TRUE;
+}
+
+
+/**
+   Upgrade a shared metadata lock to exclusive.
+
+   Used in ALTER TABLE, when a copy of the table with the
+   new definition has been constructed.
+
+   @param context   Context to which shared lock belongs
+   @param lock_data Satisfied request for shared lock to be upgraded
+
+   @note In case of failure to upgrade lock (e.g. because upgrader
+         was killed) leaves lock in its original state (locked in
+         shared mode).
+
+   @retval FALSE  Success
+   @retval TRUE   Failure (thread was killed)
+*/
+
+bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context,
+                                          MDL_LOCK_DATA *lock_data)
+{
+  MDL_LOCK *lock;
+  const char *old_msg;
+  st_my_thread_var *mysys_var= my_thread_var;
+
+  DBUG_ENTER("mdl_upgrade_shared_lock_to_exclusive");
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  DBUG_ASSERT(lock_data->state == MDL_ACQUIRED);
+
+  /* Allow this function to be called twice for the same lock request. */
+  if (lock_data->type == MDL_EXCLUSIVE)
+    DBUG_RETURN(FALSE);
+
+  DBUG_ASSERT(lock_data->type == MDL_SHARED_UPGRADABLE);
+
+  lock= lock_data->lock;
+
+  pthread_mutex_lock(&LOCK_mdl);
+
+  old_msg= MDL_ENTER_COND(context, mysys_var);
+
+  lock_data->state= MDL_PENDING_UPGRADE;
+  /* Set type of lock request to the type at which we are aiming. */
+  lock_data->type= MDL_EXCLUSIVE;
+  lock->active_shared.remove(lock_data);
+  /*
+    There can be only one upgrader for this lock or we will have deadlock.
+    This invariant is ensured by code outside of metadata subsystem usually
+    by obtaining some sort of exclusive table-level lock (e.g. TL_WRITE,
+    TL_WRITE_ALLOW_READ) before performing upgrade of metadata lock.
+  */
+  DBUG_ASSERT(lock->active_shared_waiting_upgrade.is_empty());
+  lock->active_shared_waiting_upgrade.push_front(lock_data);
+
+  /*
+    Since we should have been already acquired intention exclusive global lock
+    this call is only enforcing asserts.
+  */
+  DBUG_ASSERT(can_grant_global_lock(lock_data));
+
+  while (1)
+  {
+    if (can_grant_lock(lock, lock_data))
+      break;
+
+    bool signalled= FALSE;
+    MDL_LOCK_DATA *conf_lock_data;
+    I_P_List_iterator<MDL_LOCK_DATA, MDL_LOCK_DATA_lock> it(lock->active_shared);
+
+    while ((conf_lock_data= it++))
+    {
+      if (conf_lock_data->ctx != context)
+      {
+        signalled|=
+          mysql_notify_thread_having_shared_lock(context->thd,
+                                                 conf_lock_data->ctx->thd);
+      }
+    }
+
+    if (signalled)
+      pthread_cond_wait(&COND_mdl, &LOCK_mdl);
+    else
+    {
+      /*
+        Another thread obtained shared MDL-lock on some table but
+        has not yet opened it and/or tried to obtain data lock on
+        it. In this case we need to wait until this happens and try
+        to abort this thread once again.
+      */
+      struct timespec abstime;
+      set_timespec(abstime, 10);
+      DBUG_PRINT("info", ("Failed to wake-up from table-level lock ... sleeping"));
+      pthread_cond_timedwait(&COND_mdl, &LOCK_mdl, &abstime);
+    }
+    if (mysys_var->abort)
+    {
+      lock_data->state= MDL_ACQUIRED;
+      lock_data->type= MDL_SHARED_UPGRADABLE;
+      lock->active_shared_waiting_upgrade.remove(lock_data);
+      lock->active_shared.push_front(lock_data);
+      /* Pending requests for shared locks can be satisfied now. */
+      pthread_cond_broadcast(&COND_mdl);
+      MDL_EXIT_COND(context, mysys_var, old_msg);
+      DBUG_RETURN(TRUE);
+    }
+  }
+
+  lock->active_shared_waiting_upgrade.remove(lock_data);
+  lock->active_exclusive.push_front(lock_data);
+  lock_data->state= MDL_ACQUIRED;
+  if (lock->cached_object)
+    (*lock->cached_object_release_hook)(lock->cached_object);
+  lock->cached_object= 0;
+
+  /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */
+  MDL_EXIT_COND(context, mysys_var, old_msg);
+  DBUG_RETURN(FALSE);
+}
+
+
+/**
+   Try to acquire an exclusive lock on the object if there are
+   no conflicting locks.
+
+   Similar to the previous function, but returns
+   immediately without any side effect if encounters a lock
+   conflict. Otherwise takes the lock.
+
+   This function is used in CREATE TABLE ... LIKE to acquire a lock
+   on the table to be created. In this statement we don't want to
+   block and wait for the lock if the table already exists.
+
+   @param context  [in]  The context containing the lock request
+   @param lock     [in]  The lock request
+   @param conflict [out] Indicates that conflicting lock exists
+
+   @retval TRUE  Failure either conflicting lock exists or some error
+                 occured (probably OOM).
+   @retval FALSE Success, lock was acquired.
+
+   FIXME: Compared to lock_table_name_if_not_cached()
+          it gives sligthly more false negatives.
+*/
+
+bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context,
+                                    MDL_LOCK_DATA *lock_data,
+                                    bool *conflict)
+{
+  MDL_LOCK *lock;
+
+  DBUG_ASSERT(lock_data->type == MDL_EXCLUSIVE &&
+              lock_data->state == MDL_INITIALIZED);
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  *conflict= FALSE;
+
+  pthread_mutex_lock(&LOCK_mdl);
+
+  if (!(lock= (MDL_LOCK *)hash_search(&mdl_locks, (uchar*)lock_data->key,
+                                      lock_data->key_length)))
+  {
+    if (!(lock= get_lock_object()))
+      goto err;
+    lock->active_exclusive.push_front(lock_data);
+    lock->lock_data_count= 1;
+    if (my_hash_insert(&mdl_locks, (uchar*)lock))
+    {
+      release_lock_object(lock);
+      goto err;
+    }
+    lock_data->state= MDL_ACQUIRED;
+    lock_data->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;
+}
+
+
+/**
+   Acquire global shared metadata lock.
+
+   Holding this lock will block all requests for exclusive locks
+   and shared locks which can be potentially upgraded to exclusive.
+
+   @param context Current metadata locking context.
+
+   @retval FALSE Success -- the lock was granted.
+   @retval TRUE  Failure -- our thread was killed.
+*/
+
+bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context)
+{
+  st_my_thread_var *mysys_var= my_thread_var;
+  const char *old_msg;
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+  DBUG_ASSERT(!context->has_global_shared_lock);
+
+  pthread_mutex_lock(&LOCK_mdl);
+
+  global_lock.waiting_shared++;
+  old_msg= MDL_ENTER_COND(context, mysys_var);
+
+  while (!mysys_var->abort && global_lock.active_intention_exclusive)
+    pthread_cond_wait(&COND_mdl, &LOCK_mdl);
+
+  global_lock.waiting_shared--;
+  if (mysys_var->abort)
+  {
+    /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */
+    MDL_EXIT_COND(context, mysys_var, old_msg);
+    return TRUE;
+  }
+  global_lock.active_shared++;
+  context->has_global_shared_lock= TRUE;
+  /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */
+  MDL_EXIT_COND(context, mysys_var, old_msg);
+  return FALSE;
+}
+
+
+/**
+   Wait until there will be no locks that conflict with lock requests
+   in the context.
+
+   This is a part of the locking protocol and must be used by the
+   acquirer of shared locks after a back-off.
+
+   Does not acquire the locks!
+
+   @param context Context with which lock requests are associated.
+
+   @retval FALSE  Success. One can try to obtain metadata locks.
+   @retval TRUE   Failure (thread was killed)
+*/
+
+bool mdl_wait_for_locks(MDL_CONTEXT *context)
+{
+  MDL_LOCK_DATA *lock_data;
+  MDL_LOCK *lock;
+  I_P_List_iterator<MDL_LOCK_DATA, MDL_LOCK_DATA_context> it(context->locks);
+  const char *old_msg;
+  st_my_thread_var *mysys_var= my_thread_var;
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  while (!mysys_var->abort)
+  {
+    /*
+      We have to check if there are some HANDLERs open by this thread
+      which conflict with some pending exclusive locks. Otherwise we
+      might have a deadlock in situations when we are waiting for
+      pending writer to go away, which in its turn waits for HANDLER
+      open by our thread.
+
+      TODO: investigate situations in which we need to broadcast on
+            COND_mdl because of above scenario.
+    */
+    mysql_ha_flush(context->thd);
+    pthread_mutex_lock(&LOCK_mdl);
+    old_msg= MDL_ENTER_COND(context, mysys_var);
+    it.rewind();
+    while ((lock_data= it++))
+    {
+      DBUG_ASSERT(lock_data->state == MDL_INITIALIZED);
+      if (!can_grant_global_lock(lock_data))
+        break;
+      /*
+        To avoid starvation we don't wait if we have a conflict against
+        request for MDL_EXCLUSIVE lock.
+      */
+      if (is_shared(lock_data) &&
+          (lock= (MDL_LOCK *)hash_search(&mdl_locks, (uchar*)lock_data->key,
+                                         lock_data->key_length)) &&
+          !can_grant_lock(lock, lock_data))
+        break;
+    }
+    if (!lock_data)
+    {
+      pthread_mutex_unlock(&LOCK_mdl);
+      break;
+    }
+    pthread_cond_wait(&COND_mdl, &LOCK_mdl);
+    /* As a side-effect MDL_EXIT_COND() unlocks LOCK_mdl. */
+    MDL_EXIT_COND(context, mysys_var, old_msg);
+  }
+  return mysys_var->abort;
+}
+
+
+/**
+   Auxiliary function which allows to release particular lock
+   ownership of which is represented by lock request object.
+*/
+
+static void release_lock(MDL_LOCK_DATA *lock_data)
+{
+  MDL_LOCK *lock;
+
+  DBUG_ENTER("release_lock");
+  DBUG_PRINT("enter", ("db=%s name=%s", lock_data->key + 4,
+                        lock_data->key + 4 + strlen(lock_data->key + 4) + 1));
+
+  DBUG_ASSERT(lock_data->state == MDL_PENDING ||
+              lock_data->state == MDL_ACQUIRED);
+
+  lock= lock_data->lock;
+  if (lock->has_one_lock_data())
+  {
+    hash_delete(&mdl_locks, (uchar *)lock);
+    DBUG_PRINT("info", ("releasing cached_object cached_object=%p",
+                        lock->cached_object));
+    if (lock->cached_object)
+      (*lock->cached_object_release_hook)(lock->cached_object);
+    release_lock_object(lock);
+    if (lock_data->state == MDL_ACQUIRED &&
+        (lock_data->type == MDL_EXCLUSIVE ||
+         lock_data->type == MDL_SHARED_UPGRADABLE))
+      global_lock.active_intention_exclusive--;
+  }
+  else
+  {
+    switch (lock_data->type)
+    {
+      case MDL_SHARED_UPGRADABLE:
+        global_lock.active_intention_exclusive--;
+        /* Fallthrough. */
+      case MDL_SHARED:
+      case MDL_SHARED_HIGH_PRIO:
+        lock->active_shared.remove(lock_data);
+        break;
+      case MDL_EXCLUSIVE:
+        if (lock_data->state == MDL_PENDING)
+          lock->waiting_exclusive.remove(lock_data);
+        else
+        {
+          lock->active_exclusive.remove(lock_data);
+          global_lock.active_intention_exclusive--;
+        }
+        break;
+      default:
+        DBUG_ASSERT(0);
+    }
+    lock->lock_data_count--;
+  }
+
+  DBUG_VOID_RETURN;
+}
+
+
+/**
+   Release all locks associated with the context, but leave them
+   in the context as lock requests.
+
+   This function is used to back off in case of a lock conflict.
+   It is also used to release shared locks in the end of an SQL
+   statement.
+
+   @param context The context with which the locks to be released
+                  are associated.
+*/
+
+void mdl_release_locks(MDL_CONTEXT *context)
+{
+  MDL_LOCK_DATA *lock_data;
+  I_P_List_iterator<MDL_LOCK_DATA, MDL_LOCK_DATA_context> it(context->locks);
+  DBUG_ENTER("mdl_release_locks");
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  pthread_mutex_lock(&LOCK_mdl);
+  while ((lock_data= it++))
+  {
+    DBUG_PRINT("info", ("found lock to release lock_data=%p", lock_data));
+    /*
+      We should not release locks which pending shared locks as these
+      are not associated with lock object and don't present in its
+      lists. Allows us to avoid problems in open_tables() in case of
+      back-off
+    */
+    if (lock_data->state != MDL_INITIALIZED)
+    {
+      release_lock(lock_data);
+      lock_data->state= MDL_INITIALIZED;
+#ifndef DBUG_OFF
+      lock_data->lock= 0;
+#endif
+    }
+    /*
+      We will return lock request to its initial state only in
+      mdl_remove_all_locks() since we need to know type of lock
+      request in mdl_wait_for_locks().
+    */
+  }
+  /* Inefficient but will do for a while */
+  pthread_cond_broadcast(&COND_mdl);
+  pthread_mutex_unlock(&LOCK_mdl);
+  DBUG_VOID_RETURN;
+}
+
+
+/**
+   Release a lock.
+
+   @param context   Context containing lock in question
+   @param lock_data Lock to be released
+
+*/
+
+void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data)
+{
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  pthread_mutex_lock(&LOCK_mdl);
+  release_lock(lock_data);
+#ifndef DBUG_OFF
+  lock_data->lock= 0;
+#endif
+  lock_data->state= MDL_INITIALIZED;
+  pthread_cond_broadcast(&COND_mdl);
+  pthread_mutex_unlock(&LOCK_mdl);
+}
+
+
+/**
+   Release all locks in the context which correspond to the same name/
+   object as this lock request, remove lock requests from the context.
+
+   @param context   Context containing locks in question
+   @param lock_data One of the locks for the name/object for which all
+                    locks should be released.
+*/
+
+void mdl_release_and_remove_all_locks_for_name(MDL_CONTEXT *context,
+                                               MDL_LOCK_DATA *lock_data)
+{
+  MDL_LOCK *lock;
+  I_P_List_iterator<MDL_LOCK_DATA, MDL_LOCK_DATA_context> it(context->locks);
+
+  DBUG_ASSERT(lock_data->state == MDL_ACQUIRED);
+
+  /*
+    We can use MDL_LOCK_DATA::lock here to identify other locks for the same
+    object since even altough MDL_LOCK object might be reused for different
+    lock after the first lock for this object have been released we can't
+    have references to this other MDL_LOCK object in this context.
+  */
+  lock= lock_data->lock;
+
+  while ((lock_data= it++))
+  {
+    DBUG_ASSERT(lock_data->state == MDL_ACQUIRED);
+    if (lock_data->lock == lock)
+    {
+      mdl_release_lock(context, lock_data);
+      mdl_remove_lock(context, lock_data);
+    }
+  }
+}
+
+
+/**
+   Downgrade an exclusive lock to shared metadata lock.
+
+   @param context   A context to which exclusive lock belongs
+   @param lock_data Satisfied request for exclusive lock to be downgraded
+*/
+
+void mdl_downgrade_exclusive_lock(MDL_CONTEXT *context,
+                                  MDL_LOCK_DATA *lock_data)
+{
+  MDL_LOCK *lock;
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  DBUG_ASSERT(lock_data->state == MDL_ACQUIRED);
+
+  if (is_shared(lock_data))
+    return;
+
+  lock= lock_data->lock;
+
+  pthread_mutex_lock(&LOCK_mdl);
+  lock->active_exclusive.remove(lock_data);
+  lock_data->type= MDL_SHARED_UPGRADABLE;
+  lock->active_shared.push_front(lock_data);
+  pthread_cond_broadcast(&COND_mdl);
+  pthread_mutex_unlock(&LOCK_mdl);
+}
+
+
+/**
+   Release global shared metadata lock.
+
+   @param context Current context
+*/
+
+void mdl_release_global_shared_lock(MDL_CONTEXT *context)
+{
+  safe_mutex_assert_not_owner(&LOCK_open);
+  DBUG_ASSERT(context->has_global_shared_lock);
+
+  pthread_mutex_lock(&LOCK_mdl);
+  global_lock.active_shared--;
+  context->has_global_shared_lock= FALSE;
+  pthread_cond_broadcast(&COND_mdl);
+  pthread_mutex_unlock(&LOCK_mdl);
+}
+
+
+/**
+   Auxiliary function which allows to check if we have exclusive lock
+   on the object.
+
+   @param context Current context
+   @param type    Id of object type
+   @param db      Name of the database
+   @param name    Name of the object
+
+   @return TRUE if current context contains exclusive lock for the object,
+           FALSE otherwise.
+*/
+
+bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type,
+                                 const char *db, const char *name)
+{
+  char key[MAX_MDLKEY_LENGTH];
+  uint key_length;
+  MDL_LOCK_DATA *lock_data;
+  I_P_List_iterator<MDL_LOCK_DATA, MDL_LOCK_DATA_context> it(context->locks);
+
+  int4store(key, type);
+  key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
+
+  while ((lock_data= it++) &&
+         (lock_data->key_length != key_length ||
+          memcmp(lock_data->key, key, key_length) ||
+          !(lock_data->type == MDL_EXCLUSIVE &&
+            lock_data->state == MDL_ACQUIRED)))
+    continue;
+  return lock_data;
+}
+
+
+/**
+   Auxiliary function which allows to check if we some kind of lock on
+   the object.
+
+   @param context Current context
+   @param type    Id of object type
+   @param db      Name of the database
+   @param name    Name of the object
+
+   @return TRUE if current context contains satisfied lock for the object,
+           FALSE otherwise.
+*/
+
+bool mdl_is_lock_owner(MDL_CONTEXT *context, int type, const char *db,
+                        const char *name)
+{
+  char key[MAX_MDLKEY_LENGTH];
+  uint key_length;
+  MDL_LOCK_DATA *lock_data;
+  I_P_List_iterator<MDL_LOCK_DATA, MDL_LOCK_DATA_context> it(context->locks);
+
+  int4store(key, type);
+  key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
+
+  while ((lock_data= it++) &&
+         (lock_data->key_length != key_length ||
+          memcmp(lock_data->key, key, key_length) ||
+          lock_data->state != MDL_ACQUIRED))
+    continue;
+
+  return lock_data;
+}
+
+
+/**
+   Check if we have any pending exclusive locks which conflict with
+   existing shared lock.
+
+   @param lock_data Shared lock against which check should be performed.
+
+   @return TRUE if there are any conflicting locks, FALSE otherwise.
+*/
+
+bool mdl_has_pending_conflicting_lock(MDL_LOCK_DATA *lock_data)
+{
+  bool result;
+
+  DBUG_ASSERT(is_shared(lock_data) && lock_data->state == MDL_ACQUIRED);
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  pthread_mutex_lock(&LOCK_mdl);
+  result= !(lock_data->lock->waiting_exclusive.is_empty() &&
+            lock_data->lock->active_shared_waiting_upgrade.is_empty());
+  pthread_mutex_unlock(&LOCK_mdl);
+  return result;
+}
+
+
+/**
+   Associate pointer to an opaque object with a lock.
+
+   @param lock_data     Lock request for the lock with which the
+                        object should be associated.
+   @param cached_object Pointer to the object
+   @param release_hook  Cleanup function to be called when MDL subsystem
+                        decides to remove lock or associate another object.
+
+   This is used to cache a pointer to TABLE_SHARE in the lock
+   structure. Such caching can save one acquisition of LOCK_open
+   and one table definition cache lookup for every table.
+
+   Since the pointer may be stored only inside an acquired lock,
+   the caching is only effective when there is more than one lock
+   granted on a given table.
+
+   This function has the following usage pattern:
+     - try to acquire an MDL lock
+     - when done, call for mdl_get_cached_object(). If it returns NULL, our
+       thread has the only lock on this table.
+     - look up TABLE_SHARE in the table definition cache
+     - call mdl_set_cache_object() to assign the share to the opaque pointer.
+
+  The release hook is invoked when the last shared metadata
+  lock on this name is released.
+*/
+
+void mdl_set_cached_object(MDL_LOCK_DATA *lock_data, void *cached_object,
+                           mdl_cached_object_release_hook release_hook)
+{
+  DBUG_ENTER("mdl_set_cached_object");
+  DBUG_PRINT("enter", ("db=%s name=%s cached_object=%p", lock_data->key + 4,
+                       lock_data->key + 4 + strlen(lock_data->key + 4) + 1,
+                       cached_object));
+
+  DBUG_ASSERT(lock_data->state == MDL_ACQUIRED ||
+              lock_data->state == MDL_PENDING_UPGRADE);
+
+  /*
+    TODO: This assumption works now since we do mdl_get_cached_object()
+          and mdl_set_cached_object() in the same critical section. Once
+          this becomes false we will have to call release_hook here and
+          use additional mutex protecting 'cached_object' member.
+  */
+  DBUG_ASSERT(!lock_data->lock->cached_object);
+
+  lock_data->lock->cached_object= cached_object;
+  lock_data->lock->cached_object_release_hook= release_hook;
+
+  DBUG_VOID_RETURN;
+}
+
+
+/**
+   Get a pointer to an opaque object that associated with the lock.
+
+   @param  lock_data Lock request for the lock with which the object is
+                     associated.
+
+   @return Pointer to an opaque object associated with the lock.
+*/
+
+void* mdl_get_cached_object(MDL_LOCK_DATA *lock_data)
+{
+  DBUG_ASSERT(lock_data->state == MDL_ACQUIRED ||
+              lock_data->state == MDL_PENDING_UPGRADE);
+  return lock_data->lock->cached_object;
+}

=== added file 'sql/mdl.h'
--- a/sql/mdl.h	1970-01-01 00:00:00 +0000
+++ b/sql/mdl.h	2008-06-20 13:11:20 +0000
@@ -0,0 +1,259 @@
+#ifndef MDL_H
+#define MDL_H
+/* Copyright (C) 2007-2008 MySQL AB
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; version 2 of the License.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
+
+
+#include "sql_plist.h"
+#include <my_sys.h>
+#include <m_string.h>
+#include <mysql_com.h>
+
+class THD;
+
+struct MDL_LOCK_DATA;
+struct MDL_LOCK;
+struct MDL_CONTEXT;
+
+/**
+   Type of metadata lock request.
+
+   - High-priority shared locks differ from ordinary shared locks by
+     that they ignore pending requests for exclusive locks.
+   - Upgradable shared locks can be later upgraded to exclusive
+     (because of that their acquisition involves implicit
+      acquisition of global intention-exclusive lock).
+
+   @see Comments for can_grant_lock() and can_grant_global_lock() for details.
+*/
+
+enum enum_mdl_type {MDL_SHARED=0, MDL_SHARED_HIGH_PRIO,
+                    MDL_SHARED_UPGRADABLE, MDL_EXCLUSIVE};
+
+
+/** States which metadata lock request can have. */
+
+enum enum_mdl_state {MDL_INITIALIZED=0, MDL_PENDING,
+                     MDL_ACQUIRED, MDL_PENDING_UPGRADE};
+
+
+/**
+   A pending lock request or a granted metadata lock. A lock is requested
+   or granted based on a fully qualified name and type. E.g. for a table
+   the key consists of <0 (=table)>+<database name>+<table name>.
+   Later in this document this triple will be referred to simply as
+   "key" or "name".
+*/
+
+struct MDL_LOCK_DATA
+{
+  char          *key;
+  uint          key_length;
+  enum          enum_mdl_type type;
+  enum          enum_mdl_state state;
+
+private:
+  /**
+     Pointers for participating in the list of lock requests for this context.
+  */
+  MDL_LOCK_DATA *next_context;
+  MDL_LOCK_DATA **prev_context;
+  /**
+     Pointers for participating in the list of satisfied/pending requests
+     for the lock.
+  */
+  MDL_LOCK_DATA *next_lock;
+  MDL_LOCK_DATA **prev_lock;
+
+  friend struct MDL_LOCK_DATA_context;
+  friend struct MDL_LOCK_DATA_lock;
+
+public:
+  /*
+    Pointer to the lock object for this lock request. Valid only if this lock
+    request is satisified or is present in the list of pending lock requests
+    for particular lock.
+  */
+  MDL_LOCK      *lock;
+  MDL_CONTEXT   *ctx;
+};
+
+
+/**
+   Helper class which specifies which members of MDL_LOCK_DATA are used for
+   participation in the list lock requests belonging to one context.
+*/
+
+struct MDL_LOCK_DATA_context
+{
+  static inline MDL_LOCK_DATA **next_ptr(MDL_LOCK_DATA *l)
+  {
+    return &l->next_context;
+  }
+  static inline MDL_LOCK_DATA ***prev_ptr(MDL_LOCK_DATA *l)
+  {
+    return &l->prev_context;
+  }
+};
+
+
+/**
+   Helper class which specifies which members of MDL_LOCK_DATA are used for
+   participation in the list of satisfied/pending requests for the lock.
+*/
+
+struct MDL_LOCK_DATA_lock
+{
+  static inline MDL_LOCK_DATA **next_ptr(MDL_LOCK_DATA *l)
+  {
+    return &l->next_lock;
+  }
+  static inline MDL_LOCK_DATA ***prev_ptr(MDL_LOCK_DATA *l)
+  {
+    return &l->prev_lock;
+  }
+};
+
+
+/**
+   Context of the owner of metadata locks. I.e. each server
+   connection has such a context.
+*/
+
+struct MDL_CONTEXT
+{
+  I_P_List <MDL_LOCK_DATA, MDL_LOCK_DATA_context> locks;
+  bool has_global_shared_lock;
+  THD      *thd;
+};
+
+
+void mdl_init();
+void mdl_destroy();
+
+void mdl_context_init(MDL_CONTEXT *context, THD *thd);
+void mdl_context_destroy(MDL_CONTEXT *context);
+void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup);
+void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup);
+void mdl_context_merge(MDL_CONTEXT *target, MDL_CONTEXT *source);
+
+/** Maximal length of key for metadata locking subsystem. */
+#define MAX_MDLKEY_LENGTH (4 + NAME_LEN + 1 + NAME_LEN + 1)
+
+void mdl_init_lock(MDL_LOCK_DATA *lock_data, char *key, int type,
+                   const char *db, const char *name);
+MDL_LOCK_DATA *mdl_alloc_lock(int type, const char *db, const char *name,
+                              MEM_ROOT *root);
+void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data);
+void mdl_remove_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data);
+void mdl_remove_all_locks(MDL_CONTEXT *context);
+
+/**
+   Set type of lock request. Can be only applied to pending locks.
+*/
+
+inline void mdl_set_lock_type(MDL_LOCK_DATA *lock_data, enum_mdl_type lock_type)
+{
+  DBUG_ASSERT(lock_data->state == MDL_INITIALIZED);
+  lock_data->type= lock_type;
+}
+
+bool mdl_acquire_shared_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data,
+                             bool *retry);
+bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context);
+bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context,
+                                          MDL_LOCK_DATA *lock_data);
+bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context,
+                                    MDL_LOCK_DATA *lock_data,
+                                    bool *conflict);
+bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context);
+
+bool mdl_wait_for_locks(MDL_CONTEXT *context);
+
+void mdl_release_locks(MDL_CONTEXT *context);
+void mdl_release_and_remove_all_locks_for_name(MDL_CONTEXT *context,
+                                               MDL_LOCK_DATA *lock_data);
+void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK_DATA *lock_data);
+void mdl_downgrade_exclusive_lock(MDL_CONTEXT *context,
+                                  MDL_LOCK_DATA *lock_data);
+void mdl_release_global_shared_lock(MDL_CONTEXT *context);
+
+bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type, const char *db,
+                                 const char *name);
+bool mdl_is_lock_owner(MDL_CONTEXT *context, int type, const char *db,
+                       const char *name);
+
+bool mdl_has_pending_conflicting_lock(MDL_LOCK_DATA *lock_data);
+
+inline bool mdl_has_locks(MDL_CONTEXT *context)
+{
+  return !context->locks.is_empty();
+}
+
+
+/**
+   Get iterator for walking through all lock requests in the context.
+*/
+
+inline I_P_List_iterator<MDL_LOCK_DATA, MDL_LOCK_DATA_context>
+mdl_get_locks(MDL_CONTEXT *ctx)
+{
+  I_P_List_iterator<MDL_LOCK_DATA, MDL_LOCK_DATA_context> result(ctx->locks);
+  return result;
+}
+
+/**
+   Give metadata lock request object for the table get table definition
+   cache key corresponding to it.
+
+   @param lock_data  [in]  Lock request object for the table.
+   @param key        [out] LEX_STRING object where table definition cache key
+                           should be put.
+
+   @note This key will have the same life-time as this lock request object.
+
+   @note This is yet another place where border between MDL subsystem and the
+         rest of the server is broken. OTOH it allows to save some CPU cycles
+         and memory by avoiding generating these TDC keys from table list.
+*/
+
+inline void mdl_get_tdc_key(MDL_LOCK_DATA *lock_data, LEX_STRING *key)
+{
+  key->str= lock_data->key + 4;
+  key->length= lock_data->key_length - 4;
+}
+
+
+typedef void (* mdl_cached_object_release_hook)(void *);
+void* mdl_get_cached_object(MDL_LOCK_DATA *lock_data);
+void mdl_set_cached_object(MDL_LOCK_DATA *lock_data, void *cached_object,
+                           mdl_cached_object_release_hook release_hook);
+
+
+/*
+  Functions in the server's kernel used by metadata locking subsystem.
+*/
+
+extern bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use);
+extern void mysql_ha_flush(THD *thd);
+extern "C" const char *set_thd_proc_info(THD *thd, const char *info,
+                                         const char *calling_function,
+                                         const char *calling_file,
+                                         const unsigned int calling_line);
+#ifndef DBUG_OFF
+extern pthread_mutex_t LOCK_open;
+#endif
+
+#endif

=== modified file 'sql/mysql_priv.h'
--- a/sql/mysql_priv.h	2008-06-23 07:14:00 +0000
+++ b/sql/mysql_priv.h	2008-06-23 13:26:17 +0000
@@ -801,7 +801,7 @@ extern my_decimal decimal_zero;
 void free_items(Item *item);
 void cleanup_items(Item *item);
 class THD;
-void close_thread_tables(THD *thd);
+void close_thread_tables(THD *thd, bool skip_mdl= 0);
 
 #ifndef NO_EMBEDDED_ACCESS_CHECKS
 bool check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *tables);
@@ -1095,7 +1095,6 @@ int mysql_rm_table_part2(THD *thd, TABLE
                          bool drop_temporary, bool drop_view, bool log_query);
 bool quick_rm_table(handlerton *base,const char *db,
                     const char *table_name, uint flags);
-void close_cached_table(THD *thd, TABLE *table);
 bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent);
 bool do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db,
                       char *new_table_name, char *new_table_alias,
@@ -1136,10 +1135,6 @@ bool check_dup(const char *db, const cha
 bool compare_record(TABLE *table);
 bool append_file_to_dir(THD *thd, const char **filename_ptr, 
                         const char *table_name);
-void wait_while_table_is_used(THD *thd, TABLE *table,
-                              enum ha_extra_function function);
-bool table_cache_init(void);
-void table_cache_free(void);
 bool table_def_init(void);
 void table_def_free(void);
 void assign_new_table_id(TABLE_SHARE *share);
@@ -1317,35 +1312,27 @@ uint create_table_def_key(THD *thd, char
                           bool tmp_table);
 TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key,
                              uint key_length, uint db_flags, int *error);
-void release_table_share(TABLE_SHARE *share, enum release_type type);
+void release_table_share(TABLE_SHARE *share);
 TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name);
 TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update,
                    uint lock_flags);
-TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem,
-		  bool *refresh, uint flags);
-bool name_lock_locked_table(THD *thd, TABLE_LIST *tables);
-bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in);
-TABLE *table_cache_insert_placeholder(THD *thd, const char *key,
-                                      uint key_length);
-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);
+enum enum_open_table_action {OT_NO_ACTION= 0, OT_BACK_OFF_AND_RETRY,
+                             OT_DISCOVER, OT_REPAIR};
+bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem,
+                enum_open_table_action *action, uint flags);
+bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias,
+                   char *cache_key, uint cache_key_length,
+                   MEM_ROOT *mem_root, uint flags);
+TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name);
+TABLE *find_write_locked_table(TABLE *list, 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,
-                                      const char *table_name);
-void close_handle_and_leave_table_as_lock(TABLE *table);
 bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias,
                   uint db_stat, uint prgflag,
                   uint ha_open_flags, TABLE *outparam,
                   TABLE_LIST *table_desc, MEM_ROOT *mem_root);
-bool wait_for_tables(THD *thd);
-bool table_is_used(TABLE *table, bool wait_for_name_lock);
-TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name);
-void abort_locked_tables(THD *thd,const char *db, const char *table_name);
 void execute_init_command(THD *thd, sys_var_str *init_command_var,
 			  rw_lock_t *var_mutex);
 extern Field *not_found_field;
@@ -1481,7 +1468,7 @@ bool mysql_ha_close(THD *thd, TABLE_LIST
 bool mysql_ha_read(THD *, TABLE_LIST *,enum enum_ha_read_modes,char *,
                    List<Item> *,enum ha_rkey_function,Item *,ha_rows,ha_rows);
 void mysql_ha_flush(THD *thd);
-void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables, bool is_locked);
+void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables);
 void mysql_ha_cleanup(THD *thd);
 
 /* sql_base.cc */
@@ -1513,9 +1500,12 @@ void add_join_on(TABLE_LIST *b,Item *exp
 void add_join_natural(TABLE_LIST *a,TABLE_LIST *b,List<String> *using_fields,
                       SELECT_LEX *lex);
 bool add_proc_to_list(THD *thd, Item *item);
-void unlink_open_table(THD *thd, TABLE *find, bool unlock);
+bool wait_while_table_is_used(THD *thd, TABLE *table,
+                              enum ha_extra_function function);
 void drop_open_table(THD *thd, TABLE *table, const char *db_name,
                      const char *table_name);
+void close_all_tables_for_name(THD *thd, TABLE_SHARE *share,
+                               bool remove_from_locked_tables);
 void update_non_unique_table_error(TABLE_LIST *update,
                                    const char *operation,
                                    TABLE_LIST *duplicate);
@@ -1596,22 +1586,24 @@ void wait_for_condition(THD *thd, pthrea
                         pthread_cond_t *cond);
 int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags);
 /* open_and_lock_tables with optional derived handling */
-int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived);
+int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived,
+                                 uint flags);
 /* simple open_and_lock_tables without derived handling */
 inline int simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables)
 {
-  return open_and_lock_tables_derived(thd, tables, FALSE);
+  return open_and_lock_tables_derived(thd, tables, FALSE, 0);
 }
 /* open_and_lock_tables with derived handling */
 inline int open_and_lock_tables(THD *thd, TABLE_LIST *tables)
 {
-  return open_and_lock_tables_derived(thd, tables, TRUE);
+  return open_and_lock_tables_derived(thd, tables, TRUE, 0);
 }
 /* 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);
+                                thr_lock_type lock_type, uint flags);
 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 lock_tables(THD *thd, TABLE_LIST *tables, uint counter, uint flags,
+                bool *need_reopen);
 int decide_logging_format(THD *thd, TABLE_LIST *tables);
 TABLE *open_temporary_table(THD *thd, const char *path, const char *db,
                             const char *table_name, bool link_in_list,
@@ -1621,7 +1613,7 @@ void free_io_cache(TABLE *entry);
 void intern_close_table(TABLE *entry);
 bool close_thread_table(THD *thd, TABLE **table_ptr);
 void close_temporary_tables(THD *thd);
-void close_tables_for_reopen(THD *thd, TABLE_LIST **tables);
+void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, bool skip_mdl);
 TABLE_LIST *find_table_in_list(TABLE_LIST *table,
                                TABLE_LIST *TABLE_LIST::*link,
                                const char *db_name,
@@ -1636,7 +1628,6 @@ void close_temporary_table(THD *thd, TAB
 void close_temporary(TABLE *table, bool free_share, bool delete_table);
 bool rename_temporary_table(THD* thd, TABLE *table, const char *new_db,
 			    const char *table_name);
-void remove_db_from_cache(const char *db);
 void flush_tables();
 bool is_equal(const LEX_STRING *a, const LEX_STRING *b);
 char *make_default_log_name(char *buff,const char* log_ext);
@@ -1656,13 +1647,10 @@ uint prep_alter_part_table(THD *thd, TAB
                            uint *fast_alter_partition);
 #endif
 
-/* bits for last argument to remove_table_from_cache() */
-#define RTFC_NO_FLAG                0x0000
-#define RTFC_OWNED_BY_THD_FLAG      0x0001
-#define RTFC_WAIT_OTHER_THREAD_FLAG 0x0002
-#define RTFC_CHECK_KILLED_FLAG      0x0004
-bool remove_table_from_cache(THD *thd, const char *db, const char *table,
-                             uint flags);
+enum enum_tdc_remove_table_type {TDC_RT_REMOVE_ALL, TDC_RT_REMOVE_NOT_OWN,
+                                 TDC_RT_REMOVE_UNUSED};
+void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
+                      const char *db, const char *table_name);
 
 #define NORMAL_PART_NAME 0
 #define TEMP_PART_NAME 1
@@ -1676,7 +1664,7 @@ void create_subpartition_name(char *out,
 
 typedef struct st_lock_param_type
 {
-  TABLE_LIST table_list;
+  TABLE_LIST *table_list;
   ulonglong copied;
   ulonglong deleted;
   THD *thd;
@@ -1687,7 +1675,6 @@ typedef struct st_lock_param_type
   const char *db;
   const char *table_name;
   uchar *pack_frm_data;
-  enum thr_lock_type old_lock_type;
   uint key_count;
   uint db_options;
   size_t pack_frm_len;
@@ -1781,7 +1768,6 @@ extern pthread_mutex_t LOCK_gdl;
 bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags);
 int abort_and_upgrade_lock(ALTER_PARTITION_PARAM_TYPE *lpt);
 void close_open_tables_and_downgrade(ALTER_PARTITION_PARAM_TYPE *lpt);
-void mysql_wait_completed_table(ALTER_PARTITION_PARAM_TYPE *lpt, TABLE *my_table);
 
 /* Functions to work with system tables. */
 bool open_system_tables_for_read(THD *thd, TABLE_LIST *table_list,
@@ -1794,7 +1780,7 @@ TABLE *open_performance_schema_table(THD
 void close_performance_schema_table(THD *thd, Open_tables_state *backup);
 
 bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
-                         bool wait_for_refresh, bool wait_for_placeholders);
+                         bool wait_for_refresh);
 bool close_cached_connection_tables(THD *thd, bool wait_for_refresh,
                                     LEX_STRING *connect_string,
                                     bool have_lock = FALSE);
@@ -2104,8 +2090,9 @@ extern const char *opt_date_time_formats
 extern KNOWN_DATE_TIME_FORMAT known_date_time_formats[];
 
 extern String null_string;
-extern HASH open_cache, lock_db_cache;
+extern HASH table_def_cache, lock_db_cache;
 extern TABLE *unused_tables;
+extern uint  table_cache_count;
 extern const char* any_db;
 extern struct my_option my_long_options[];
 extern const LEX_STRING view_type;
@@ -2142,16 +2129,35 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, 
 /* mysql_lock_tables() and open_table() flags bits */
 #define MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK      0x0001
 #define MYSQL_LOCK_IGNORE_FLUSH                 0x0002
-#define MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN        0x0004
-#define MYSQL_OPEN_TEMPORARY_ONLY               0x0008
-#define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY      0x0010
-#define MYSQL_LOCK_PERF_SCHEMA                  0x0020
+#define MYSQL_OPEN_TEMPORARY_ONLY               0x0004
+#define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY      0x0008
+#define MYSQL_LOCK_PERF_SCHEMA                  0x0010
+#define MYSQL_OPEN_TAKE_UPGRADABLE_MDL          0x0020
+/**
+  Do not try to acquire a metadata lock on the table: we
+  already have one.
+*/
+#define MYSQL_OPEN_HAS_MDL_LOCK                 0x0040
+/**
+  If in locked tables mode, ignore the locked tables and get
+  a new instance of the table.
+*/
+#define MYSQL_OPEN_GET_NEW_TABLE                0x0080
+/** Don't look up the table in the list of temporary tables. */
+#define MYSQL_OPEN_SKIP_TEMPORARY               0x0100
+
+/** Please refer to the internals manual. */
+#define MYSQL_OPEN_REOPEN  (MYSQL_LOCK_IGNORE_FLUSH |\
+                            MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK |\
+                            MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY |\
+                            MYSQL_OPEN_GET_NEW_TABLE |\
+                            MYSQL_OPEN_SKIP_TEMPORARY |\
+                            MYSQL_OPEN_HAS_MDL_LOCK)
 
 void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock);
 void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock);
 void mysql_unlock_some_tables(THD *thd, TABLE **table,uint count);
-void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table,
-                       bool always_unlock);
+void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table);
 void mysql_lock_abort(THD *thd, TABLE *table, bool upgrade_lock);
 void mysql_lock_downgrade_write(THD *thd, TABLE *table,
                                 thr_lock_type new_lock_type);
@@ -2174,18 +2180,8 @@ int set_handler_table_locks(THD *thd, TA
                             bool transactional);
 
 /* Lock based on name */
-int lock_and_wait_for_table_name(THD *thd, TABLE_LIST *table_list);
-int lock_table_name(THD *thd, TABLE_LIST *table_list, bool check_in_use);
-void unlock_table_name(THD *thd, TABLE_LIST *table_list);
-bool wait_for_locked_table_names(THD *thd, TABLE_LIST *table_list);
 bool lock_table_names(THD *thd, TABLE_LIST *table_list);
-void unlock_table_names(THD *thd, TABLE_LIST *table_list,
-			TABLE_LIST *last_table);
-bool lock_table_names_exclusively(THD *thd, TABLE_LIST *table_list);
-bool is_table_name_exclusively_locked_by_this_thread(THD *thd, 
-                                                     TABLE_LIST *table_list);
-bool is_table_name_exclusively_locked_by_this_thread(THD *thd, uchar *key,
-                                                     int key_length);
+void unlock_table_names(THD *thd);
 
 
 /* old unireg functions */

=== modified file 'sql/mysqld.cc'
--- a/sql/mysqld.cc	2008-06-23 07:14:00 +0000
+++ b/sql/mysqld.cc	2008-06-23 13:26:17 +0000
@@ -1314,9 +1314,9 @@ void clean_up(bool print_message)
   grant_free();
 #endif
   query_cache_destroy();
-  table_cache_free();
   table_def_free();
   hostname_cache_free();
+  mdl_destroy();
   item_user_lock_free();
   lex_free();				/* Free some memory */
   item_create_cleanup();
@@ -3800,7 +3800,8 @@ static int init_server_components()
     We need to call each of these following functions to ensure that
     all things are initialized so that unireg_abort() doesn't fail
   */
-  if (table_cache_init() | table_def_init() | hostname_cache_init())
+  mdl_init();
+  if (table_def_init() | hostname_cache_init())
     unireg_abort(1);
 
   query_cache_result_size_limit(query_cache_limit);

=== modified file 'sql/rpl_rli.cc'
--- a/sql/rpl_rli.cc	2008-05-08 16:01:15 +0000
+++ b/sql/rpl_rli.cc	2008-05-23 13:54:03 +0000
@@ -1166,8 +1166,7 @@ void Relay_log_info::cleanup_context(THD
     end_trans(thd, ROLLBACK); // if a "real transaction"
   }
   m_table_map.clear_tables();
-  close_thread_tables(thd);
-  clear_tables_to_lock();
+  slave_close_thread_tables(thd);
   clear_flag(IN_STMT);
   /*
     Cleanup for the flags that have been set at do_apply_event.
@@ -1180,6 +1179,13 @@ void Relay_log_info::cleanup_context(THD
 
 void Relay_log_info::clear_tables_to_lock()
 {
+  /*
+    Deallocating elements of table list below will also free memory where
+    meta-data locks are stored. So we want to be sure that we don't have
+    any references to this memory left.
+  */
+  DBUG_ASSERT(!mdl_has_locks(&(current_thd->mdl_context)));
+
   while (tables_to_lock)
   {
     uchar* to_free= reinterpret_cast<uchar*>(tables_to_lock);
@@ -1196,4 +1202,15 @@ void Relay_log_info::clear_tables_to_loc
   DBUG_ASSERT(tables_to_lock == NULL && tables_to_lock_count == 0);
 }
 
+void Relay_log_info::slave_close_thread_tables(THD *thd)
+{
+  /*
+    Since we use same memory chunks for allocation of metadata lock
+    objects for tables as we use for allocating corresponding elements
+    of 'tables_to_lock' list, we have to release metadata locks by
+    closing tables before calling clear_tables_to_lock().
+  */
+  close_thread_tables(thd);
+  clear_tables_to_lock();
+}
 #endif

=== modified file 'sql/rpl_rli.h'
--- a/sql/rpl_rli.h	2008-03-27 19:02:15 +0000
+++ b/sql/rpl_rli.h	2008-05-23 13:54:03 +0000
@@ -326,6 +326,7 @@ public:
   bool cached_charset_compare(char *charset) const;
 
   void cleanup_context(THD *, bool);
+  void slave_close_thread_tables(THD *);
   void clear_tables_to_lock();
 
   /*

=== modified file 'sql/set_var.cc'
--- a/sql/set_var.cc	2008-06-23 07:14:00 +0000
+++ b/sql/set_var.cc	2008-06-23 13:26:17 +0000
@@ -4008,7 +4008,7 @@ bool sys_var_opt_readonly::update(THD *t
   DBUG_ENTER("sys_var_opt_readonly::update");
 
   /* Prevent self dead-lock */
-  if (thd->locked_tables || thd->active_transaction())
+  if (thd->locked_tables_mode || thd->active_transaction())
   {
     my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
     DBUG_RETURN(true);
@@ -4049,7 +4049,7 @@ bool sys_var_opt_readonly::update(THD *t
     can cause to wait on a read lock, it's required for the client application
     to unlock everything, and acceptable for the server to wait on all locks.
   */
-  if (result= close_cached_tables(thd, NULL, FALSE, TRUE, TRUE))
+  if (result= close_cached_tables(thd, NULL, FALSE, TRUE))
     goto end_with_read_lock;
 
   if (result= make_global_read_lock_block_commit(thd))

=== modified file 'sql/si_objects.cc'
--- a/sql/si_objects.cc	2008-05-14 13:49:41 +0000
+++ b/sql/si_objects.cc	2008-05-23 13:54:03 +0000
@@ -1928,6 +1928,8 @@ bool TriggerObj::serialize(THD *thd, Str
   if (!lst)
     DBUG_RETURN(FALSE);
 
+  alloc_mdl_locks(lst, thd->mem_root);
+
   if (open_tables(thd, &lst, &num_tables, 0))
     DBUG_RETURN(FALSE);
 

=== modified file 'sql/sp.cc'
--- a/sql/sp.cc	2008-05-21 10:17:29 +0000
+++ b/sql/sp.cc	2008-06-04 11:18:52 +0000
@@ -1683,6 +1683,12 @@ sp_cache_routines_and_add_tables_aux(THD
     int type= rt->key.str[0];
     sp_head *sp;
 
+    /*
+      Triggers can't happen here: their bodies are always processed
+      in sp_cache_routines_and_add_tables_for_triggers().
+    */
+    DBUG_ASSERT(type == TYPE_ENUM_FUNCTION || type == TYPE_ENUM_PROCEDURE);
+
     if (!(sp= sp_cache_lookup((type == TYPE_ENUM_FUNCTION ?
                               &thd->sp_func_cache : &thd->sp_proc_cache),
                               &name)))
@@ -1826,11 +1832,6 @@ int
 sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex,
                                               TABLE_LIST *table)
 {
-  int ret= 0;
-
-  Sroutine_hash_entry **last_cached_routine_ptr=
-    (Sroutine_hash_entry **)lex->sroutines_list.next;
-
   if (static_cast<int>(table->lock_type) >=
       static_cast<int>(TL_WRITE_ALLOW_WRITE))
   {
@@ -1847,21 +1848,29 @@ sp_cache_routines_and_add_tables_for_tri
               add_used_routine(lex, thd->stmt_arena, &trigger->m_sroutines_key,
                                table->belong_to_view))
           {
+            int ret;
+            /* Sic: excludes the trigger key from processing */
+            Sroutine_hash_entry **last_cached_routine_ptr=
+              (Sroutine_hash_entry **)lex->sroutines_list.next;
+
             trigger->add_used_tables_to_table_list(thd, &lex->query_tables_last,
                                                    table->belong_to_view);
             trigger->propagate_attributes(lex);
             sp_update_stmt_used_routines(thd, lex,
                                          &trigger->m_sroutines,
                                          table->belong_to_view);
+
+            ret= sp_cache_routines_and_add_tables_aux(thd, lex,
+                                                      *last_cached_routine_ptr,
+                                                      FALSE);
+            if (ret)
+              return ret;
           }
         }
       }
     }
   }
-  ret= sp_cache_routines_and_add_tables_aux(thd, lex,
-                                            *last_cached_routine_ptr,
-                                            FALSE);
-  return ret;
+  return 0;
 }
 
 

=== modified file 'sql/sp_head.cc'
--- a/sql/sp_head.cc	2008-05-22 18:40:15 +0000
+++ b/sql/sp_head.cc	2008-06-23 13:26:17 +0000
@@ -1232,7 +1232,7 @@ sp_head::execute(THD *thd)
       Will write this SP statement into binlog separately
       (TODO: consider changing the condition to "not inside event union")
     */
-    if (thd->prelocked_mode == NON_PRELOCKED)
+    if (thd->locked_tables_mode <= LTM_LOCK_TABLES)
       thd->user_var_events_alloc= thd->mem_root;
 
     err_status= i->execute(thd, &ip);
@@ -1244,7 +1244,7 @@ sp_head::execute(THD *thd)
       If we've set thd->user_var_events_alloc to mem_root of this SP
       statement, clean all the events allocated in it.
     */
-    if (thd->prelocked_mode == NON_PRELOCKED)
+    if (thd->locked_tables_mode <= LTM_LOCK_TABLES)
     {
       reset_dynamic(&thd->user_var_events);
       thd->user_var_events_alloc= NULL;//DEBUG
@@ -2686,7 +2686,7 @@ sp_lex_keeper::reset_lex_and_exec_core(T
   thd->query_id= next_query_id();
   pthread_mutex_unlock(&LOCK_thread_count);
 
-  if (thd->prelocked_mode == NON_PRELOCKED)
+  if (thd->locked_tables_mode <= LTM_LOCK_TABLES)
   {
     /*
       This statement will enter/leave prelocked mode on its own.
@@ -3932,6 +3932,10 @@ sp_head::add_used_tables_to_table_list(T
       table->prelocking_placeholder= 1;
       table->belong_to_view= belong_to_view;
       table->trg_event_map= stab->trg_event_map;
+      table->mdl_lock_data= mdl_alloc_lock(0, table->db, table->table_name,
+                                           thd->locked_tables_root ?
+                                           thd->locked_tables_root :
+                                           thd->mem_root);
 
       /* Everyting else should be zeroed */
 
@@ -3975,6 +3979,9 @@ sp_add_to_query_tables(THD *thd, LEX *le
   table->lock_transactional= 1; /* allow transactional locks */
   table->select_lex= lex->current_select;
   table->cacheable_table= 1;
+  table->mdl_lock_data= mdl_alloc_lock(0, table->db, table->table_name,
+                                       thd->locked_tables_root ?
+                                       thd->locked_tables_root : thd->mem_root);
 
   lex->add_to_query_tables(table);
   return table;

=== modified file 'sql/sql_acl.cc'
--- a/sql/sql_acl.cc	2008-05-22 18:40:15 +0000
+++ b/sql/sql_acl.cc	2008-06-23 13:26:17 +0000
@@ -676,12 +676,8 @@ my_bool acl_reload(THD *thd)
   my_bool return_val= 1;
   DBUG_ENTER("acl_reload");
 
-  if (thd->locked_tables)
-  {					// Can't have locked tables here
-    thd->lock=thd->locked_tables;
-    thd->locked_tables=0;
-    close_thread_tables(thd);
-  }
+  /* Can't have locked tables here. */
+  thd->locked_tables_list.unlock_locked_tables(thd);
 
   /*
     To avoid deadlocks we should obtain table locks before
@@ -697,6 +693,7 @@ my_bool acl_reload(THD *thd)
   tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_READ;
   tables[0].skip_temporary= tables[1].skip_temporary=
     tables[2].skip_temporary= TRUE;
+  alloc_mdl_locks(tables, thd->mem_root);
 
   if (simple_open_n_lock_tables(thd, tables))
   {
@@ -1592,6 +1589,7 @@ bool change_password(THD *thd, const cha
   bzero((char*) &tables, sizeof(tables));
   tables.alias= tables.table_name= (char*) "user";
   tables.db= (char*) "mysql";
+  alloc_mdl_locks(&tables, thd->mem_root);
 
 #ifdef HAVE_REPLICATION
   /*
@@ -3025,6 +3023,7 @@ int mysql_table_grant(THD *thd, TABLE_LI
 			    ? tables+2 : 0);
   tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_WRITE;
   tables[0].db=tables[1].db=tables[2].db=(char*) "mysql";
+  alloc_mdl_locks(tables, thd->mem_root);
 
   /*
     This statement will be replicated as a statement, even when using
@@ -3250,6 +3249,7 @@ bool mysql_routine_grant(THD *thd, TABLE
   tables[0].next_local= tables[0].next_global= tables+1;
   tables[0].lock_type=tables[1].lock_type=TL_WRITE;
   tables[0].db=tables[1].db=(char*) "mysql";
+  alloc_mdl_locks(tables, thd->mem_root);
 
   /*
     This statement will be replicated as a statement, even when using
@@ -3391,6 +3391,7 @@ bool mysql_grant(THD *thd, const char *d
   tables[0].next_local= tables[0].next_global= tables+1;
   tables[0].lock_type=tables[1].lock_type=TL_WRITE;
   tables[0].db=tables[1].db=(char*) "mysql";
+  alloc_mdl_locks(tables, thd->mem_root);
 
   /*
     This statement will be replicated as a statement, even when using
@@ -3724,6 +3725,7 @@ static my_bool grant_reload_procs_priv(T
   table.db= (char *) "mysql";
   table.lock_type= TL_READ;
   table.skip_temporary= 1;
+  alloc_mdl_locks(&table, thd->mem_root);
 
   if (simple_open_n_lock_tables(thd, &table))
   {
@@ -3790,6 +3792,8 @@ my_bool grant_reload(THD *thd)
   tables[0].next_local= tables[0].next_global= tables+1;
   tables[0].lock_type= tables[1].lock_type= TL_READ;
   tables[0].skip_temporary= tables[1].skip_temporary= TRUE;
+  alloc_mdl_locks(tables, thd->mem_root);
+
   /*
     To avoid deadlocks we should obtain table locks before
     obtaining LOCK_grant rwlock.
@@ -5117,6 +5121,7 @@ int open_grant_tables(THD *thd, TABLE_LI
     (tables+4)->lock_type= TL_WRITE;
   tables->db= (tables+1)->db= (tables+2)->db= 
     (tables+3)->db= (tables+4)->db= (char*) "mysql";
+  alloc_mdl_locks(tables, thd->mem_root);
 
 #ifdef HAVE_REPLICATION
   /*

=== modified file 'sql/sql_base.cc'
--- a/sql/sql_base.cc	2008-05-22 18:40:15 +0000
+++ b/sql/sql_base.cc	2008-06-23 13:26:17 +0000
@@ -87,55 +87,37 @@ bool Prelock_error_handler::safely_trapp
   @defgroup Data_Dictionary Data Dictionary
   @{
 */
-TABLE *unused_tables;				/* Used by mysql_test */
-HASH open_cache;				/* Used by mysql_test */
-static HASH table_def_cache;
+
+/**
+   Total number of TABLE instances for tables in the table definition cache
+   (both in use by threads and not in use). This value is accessible to user
+   as "Open_tables" status variable.
+*/
+uint  table_cache_count= 0;
+/**
+   List that contains all TABLE instances for tables in the table definition
+   cache that are not in use by any thread. Recently used TABLE instances are
+   appended to the end of the list. Thus the beginning of the list contains
+   tables which have been least recently used.
+*/
+TABLE *unused_tables;
+HASH table_def_cache;
 static TABLE_SHARE *oldest_unused_share, end_of_unused_share;
-static pthread_mutex_t LOCK_table_share;
 static bool table_def_inited= 0;
 
-static int open_unireg_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list,
-			     const char *alias,
-                             char *cache_key, uint cache_key_length,
-			     MEM_ROOT *mem_root, uint flags);
+static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables,
+                                           TABLE_SHARE *table_share);
+static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry);
+static bool auto_repair_table(THD *thd, TABLE_LIST *table_list);
 static void free_cache_entry(TABLE *entry);
-static void close_old_data_files(THD *thd, TABLE *table, bool morph_locks,
-                                 bool send_refresh);
+static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context);
 static bool
 has_two_write_locked_tables_with_auto_increment(TABLE_LIST *tables);
 
 
-extern "C" uchar *table_cache_key(const uchar *record, size_t *length,
-				 my_bool not_used __attribute__((unused)))
-{
-  TABLE *entry=(TABLE*) record;
-  *length= entry->s->table_cache_key.length;
-  return (uchar*) entry->s->table_cache_key.str;
-}
-
-
-bool table_cache_init(void)
-{
-  return hash_init(&open_cache, &my_charset_bin, table_cache_size+16,
-		   0, 0, table_cache_key,
-		   (hash_free_key) free_cache_entry, 0) != 0;
-}
-
-void table_cache_free(void)
-{
-  DBUG_ENTER("table_cache_free");
-  if (table_def_inited)
-  {
-    close_cached_tables(NULL, NULL, FALSE, FALSE, FALSE);
-    if (!open_cache.records)			// Safety first
-      hash_free(&open_cache);
-  }
-  DBUG_VOID_RETURN;
-}
-
 uint cached_open_tables(void)
 {
-  return open_cache.records;
+  return table_cache_count;
 }
 
 
@@ -143,7 +125,8 @@ uint cached_open_tables(void)
 static void check_unused(void)
 {
   uint count= 0, open_files= 0, idx= 0;
-  TABLE *cur_link,*start_link;
+  TABLE *cur_link, *start_link, *entry;
+  TABLE_SHARE *share;
 
   if ((start_link=cur_link=unused_tables))
   {
@@ -154,45 +137,42 @@ static void check_unused(void)
 	DBUG_PRINT("error",("Unused_links aren't linked properly")); /* purecov: inspected */
 	return; /* purecov: inspected */
       }
-    } while (count++ < open_cache.records &&
+    } while (count++ < table_cache_count &&
 	     (cur_link=cur_link->next) != start_link);
     if (cur_link != start_link)
     {
       DBUG_PRINT("error",("Unused_links aren't connected")); /* purecov: inspected */
     }
   }
-  for (idx=0 ; idx < open_cache.records ; idx++)
+  for (idx=0 ; idx < table_def_cache.records ; idx++)
   {
-    TABLE *entry=(TABLE*) hash_element(&open_cache,idx);
-    if (!entry->in_use)
+    share= (TABLE_SHARE*) hash_element(&table_def_cache, idx);
+
+    I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables);
+    while ((entry= it++))
+    {
+      if (entry->in_use)
+      {
+        DBUG_PRINT("error",("Used table is in share's list of unused tables")); /* purecov: inspected */
+      }
       count--;
-    if (entry->file)
       open_files++;
+    }
+    it.init(share->used_tables);
+    while ((entry= it++))
+    {
+      if (!entry->in_use)
+      {
+        DBUG_PRINT("error",("Unused table is in share's list of used tables")); /* purecov: inspected */
+      }
+      open_files++;
+    }
   }
   if (count != 0)
   {
     DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d", /* purecov: inspected */
 			count)); /* purecov: inspected */
   }
-
-#ifdef NOT_SAFE_FOR_REPAIR
-  /*
-    check that open cache and table definition cache has same number of
-    aktive tables
-  */
-  count= 0;
-  for (idx=0 ; idx < table_def_cache.records ; idx++)
-  {
-    TABLE_SHARE *entry= (TABLE_SHARE*) hash_element(&table_def_cache,idx);
-    count+= entry->ref_count;
-  }
-  if (count != open_files)
-  {
-    DBUG_PRINT("error", ("table_def ref_count: %u  open_cache: %u",
-                         count, open_files));
-    DBUG_ASSERT(count == open_files);
-  }
-#endif
 }
 #else
 #define check_unused()
@@ -256,13 +236,12 @@ extern "C" uchar *table_def_key(const uc
 static void table_def_free_entry(TABLE_SHARE *share)
 {
   DBUG_ENTER("table_def_free_entry");
+  safe_mutex_assert_owner(&LOCK_open);
   if (share->prev)
   {
     /* remove from old_unused_share list */
-    pthread_mutex_lock(&LOCK_table_share);
     *share->prev= share->next;
     share->next->prev= share->prev;
-    pthread_mutex_unlock(&LOCK_table_share);
   }
   free_table_share(share);
   DBUG_VOID_RETURN;
@@ -272,7 +251,6 @@ static void table_def_free_entry(TABLE_S
 bool table_def_init(void)
 {
   table_def_inited= 1;
-  pthread_mutex_init(&LOCK_table_share, MY_MUTEX_INIT_FAST);
   oldest_unused_share= &end_of_unused_share;
   end_of_unused_share.prev= &oldest_unused_share;
 
@@ -287,8 +265,10 @@ void table_def_free(void)
   DBUG_ENTER("table_def_free");
   if (table_def_inited)
   {
+    /* Free all open TABLEs first. */
+    close_cached_tables(NULL, NULL, FALSE, FALSE);
     table_def_inited= 0;
-    pthread_mutex_destroy(&LOCK_table_share);
+    /* Free table definitions. */
     hash_free(&table_def_cache);
   }
   DBUG_VOID_RETURN;
@@ -302,6 +282,114 @@ uint cached_table_definitions(void)
 
 
 /*
+  Auxiliary routines for manipulating with per-share used/unused and
+  global unused lists of TABLE objects and table_cache_count counter.
+  Responsible for preserving invariants between those lists, counter
+  and TABLE::in_use member.
+  In fact those routines implement sort of implicit table cache as
+  part of table definition cache.
+*/
+
+
+/**
+   Add newly created TABLE object for table share which is going
+   to be used right away.
+*/
+
+static void table_def_add_used_table(THD *thd, TABLE *table)
+{
+  DBUG_ASSERT(table->in_use == thd);
+  table->s->used_tables.push_front(table);
+  table_cache_count++;
+}
+
+
+/**
+   Prepare used or unused TABLE instance for destruction by removing
+   it from share's and global list.
+*/
+
+static void table_def_remove_table(TABLE *table)
+{
+  if (table->in_use)
+  {
+    /* Remove from per-share chain of used TABLE objects. */
+    table->s->used_tables.remove(table);
+  }
+  else
+  {
+    /* Remove from per-share chain of unused TABLE objects. */
+    table->s->free_tables.remove(table);
+
+    /* And global unused chain. */
+    table->next->prev=table->prev;
+    table->prev->next=table->next;
+    if (table == unused_tables)
+    {
+      unused_tables=unused_tables->next;
+      if (table == unused_tables)
+	unused_tables=0;
+    }
+    check_unused();
+  }
+  table_cache_count--;
+}
+
+
+/**
+   Mark already existing TABLE instance as used.
+*/
+
+static void table_def_use_table(THD *thd, TABLE *table)
+{
+  DBUG_ASSERT(!table->in_use);
+
+  /* Unlink table from list of unused tables for this share. */
+  table->s->free_tables.remove(table);
+  /* Unlink able from global unused tables list. */
+  if (table == unused_tables)
+  {						// First unused
+    unused_tables=unused_tables->next;	        // Remove from link
+    if (table == unused_tables)
+      unused_tables=0;
+  }
+  table->prev->next=table->next;		/* Remove from unused list */
+  table->next->prev=table->prev;
+  check_unused();
+  /* Add table to list of used tables for this share. */
+  table->s->used_tables.push_front(table);
+  table->in_use= thd;
+}
+
+
+/**
+   Mark already existing used TABLE instance as unused.
+*/
+
+static void table_def_unuse_table(TABLE *table)
+{
+  DBUG_ASSERT(table->in_use);
+
+  table->in_use= 0;
+  /* Remove table from the list of tables used in this share. */
+  table->s->used_tables.remove(table);
+  /* Add table to the list of unused TABLE objects for this share. */
+  table->s->free_tables.push_front(table);
+  /* Also link it last in the global list of unused TABLE objects. */
+  if (unused_tables)
+  {
+    table->next=unused_tables;
+    table->prev=unused_tables->prev;
+    unused_tables->prev=table;
+    table->prev->next=table;
+  }
+  else
+    unused_tables=table->next=table->prev=table;
+  check_unused();
+}
+
+
+/*
   Get TABLE_SHARE for a table.
 
   get_table_share()
@@ -334,6 +422,13 @@ TABLE_SHARE *get_table_share(THD *thd, T
 
   *error= 0;
 
+  /*
+    To be able perform any operation on table we should own
+    some kind of metadata lock on it.
+  */
+  DBUG_ASSERT(mdl_is_lock_owner(&thd->mdl_context, 0, table_list->db,
+                                table_list->table_name));
+
   /* Read table definition from cache */
   if ((share= (TABLE_SHARE*) hash_search(&table_def_cache,(uchar*) key,
                                          key_length)))
@@ -345,12 +440,6 @@ TABLE_SHARE *get_table_share(THD *thd, T
   }
 
   /*
-    Lock mutex to be able to read table definition from file without
-    conflicts
-  */
-  (void) pthread_mutex_lock(&share->mutex);
-
-  /*
     We assign a new table id under the protection of the LOCK_open and
     the share's own mutex.  We do this insted of creating a new mutex
     and using it for the sole purpose of serializing accesses to a
@@ -379,7 +468,6 @@ TABLE_SHARE *get_table_share(THD *thd, T
   share->ref_count++;				// Mark in use
   DBUG_PRINT("exit", ("share: %p  ref_count: %u",
                       share, share->ref_count));
-  (void) pthread_mutex_unlock(&share->mutex);
   DBUG_RETURN(share);
 
 found:
@@ -387,20 +475,15 @@ found:
      We found an existing table definition. Return it if we didn't get
      an error when reading the table definition from file.
   */
-
-  /* We must do a lock to ensure that the structure is initialized */
-  (void) pthread_mutex_lock(&share->mutex);
   if (share->error)
   {
     /* Table definition contained an error */
     open_table_error(share, share->error, share->open_errno, share->errarg);
-    (void) pthread_mutex_unlock(&share->mutex);
     DBUG_RETURN(0);
   }
   if (share->is_view && !(db_flags & OPEN_VIEW))
   {
     open_table_error(share, 1, ENOENT, 0);
-    (void) pthread_mutex_unlock(&share->mutex);
     DBUG_RETURN(0);
   }
 
@@ -411,22 +494,16 @@ found:
       Unlink share from this list
     */
     DBUG_PRINT("info", ("Unlinking from not used list"));
-    pthread_mutex_lock(&LOCK_table_share);
     *share->prev= share->next;
     share->next->prev= share->prev;
     share->next= 0;
     share->prev= 0;
-    pthread_mutex_unlock(&LOCK_table_share);
   }
-  (void) pthread_mutex_unlock(&share->mutex);
 
    /* Free cache if too big */
   while (table_def_cache.records > table_def_size &&
          oldest_unused_share->next)
-  {
-    pthread_mutex_lock(&oldest_unused_share->mutex);
     hash_delete(&table_def_cache, (uchar*) oldest_unused_share);
-  }
 
   DBUG_PRINT("exit", ("share: %p  ref_count: %u",
                       share, share->ref_count));
@@ -513,29 +590,17 @@ static TABLE_SHARE
                               db_flags, error));
 }
 
+/**
+  Mark that we are not using table share anymore.
 
-/* 
-   Mark that we are not using table share anymore.
-
-   SYNOPSIS
-     release_table_share()
-     share		Table share
-     release_type	How the release should be done:
-     			RELEASE_NORMAL
-                         - Release without checking
-                        RELEASE_WAIT_FOR_DROP
-                         - Don't return until we get a signal that the
-                           table is deleted or the thread is killed.
-
-   IMPLEMENTATION
-     If ref_count goes to zero and (we have done a refresh or if we have
-     already too many open table shares) then delete the definition.
+  @param  share   Table share
 
-     If type == RELEASE_WAIT_FOR_DROP then don't return until we get a signal
-     that the table is deleted or the thread is killed.
+  If the share has no open tables and (we have done a refresh or
+  if we have already too many open table shares) then delete the
+  definition.
 */
 
-void release_table_share(TABLE_SHARE *share, enum release_type type)
+void release_table_share(TABLE_SHARE *share)
 {
   bool to_be_deleted= 0;
   DBUG_ENTER("release_table_share");
@@ -546,7 +611,6 @@ void release_table_share(TABLE_SHARE *sh
 
   safe_mutex_assert_owner(&LOCK_open);
 
-  pthread_mutex_lock(&share->mutex);
   if (!--share->ref_count)
   {
     if (share->version != refresh_version)
@@ -557,12 +621,10 @@ void release_table_share(TABLE_SHARE *sh
       DBUG_PRINT("info",("moving share to unused list"));
 
       DBUG_ASSERT(share->next == 0);
-      pthread_mutex_lock(&LOCK_table_share);
       share->prev= end_of_unused_share.prev;
       *end_of_unused_share.prev= share;
       end_of_unused_share.prev= &share->next;
       share->next= &end_of_unused_share;
-      pthread_mutex_unlock(&LOCK_table_share);
 
       to_be_deleted= (table_def_cache.records > table_def_size);
     }
@@ -572,9 +634,7 @@ void release_table_share(TABLE_SHARE *sh
   {
     DBUG_PRINT("info", ("Deleting share"));
     hash_delete(&table_def_cache, (uchar*) share);
-    DBUG_VOID_RETURN;
   }
-  pthread_mutex_unlock(&share->mutex);
   DBUG_VOID_RETURN;
 }
 
@@ -606,66 +666,25 @@ TABLE_SHARE *get_cached_table_share(cons
 }  
 
 
-/*
-  Close file handle, but leave the table in the table cache
-
-  SYNOPSIS
-    close_handle_and_leave_table_as_lock()
-    table		Table handler
-
-  NOTES
-    By leaving the table in the table cache, it disallows any other thread
-    to open the table
-
-    thd->killed will be set if we run out of memory
+/**
+   @brief Mark table share as having one more user (increase its reference
+          count).
 
-    If closing a MERGE child, the calling function has to take care for
-    closing the parent too, if necessary.
+   @param share Table share for which reference count should be increased.
 */
 
-
-void close_handle_and_leave_table_as_lock(TABLE *table)
+static void reference_table_share(TABLE_SHARE *share)
 {
-  TABLE_SHARE *share, *old_share= table->s;
-  char *key_buff;
-  MEM_ROOT *mem_root= &table->mem_root;
-  DBUG_ENTER("close_handle_and_leave_table_as_lock");
-
-  DBUG_ASSERT(table->db_stat);
-
-  /*
-    Make a local copy of the table share and free the current one.
-    This has to be done to ensure that the table share is removed from
-    the table defintion cache as soon as the last instance is removed
-  */
-  if (multi_alloc_root(mem_root,
-                       &share, sizeof(*share),
-                       &key_buff, old_share->table_cache_key.length,
-                       NULL))
-  {
-    bzero((char*) share, sizeof(*share));
-    share->set_table_cache_key(key_buff, old_share->table_cache_key.str,
-                               old_share->table_cache_key.length);
-    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);
-  table->s= share;
-  table->file->change_table_ptr(table, table->s);
-
+  DBUG_ENTER("reference_table_share");
+  DBUG_ASSERT(share->ref_count);
+  safe_mutex_assert_owner(&LOCK_open);
+  share->ref_count++;
+  DBUG_PRINT("exit", ("share: 0x%lx  ref_count: %u",
+                     (ulong) share, share->ref_count));
   DBUG_VOID_RETURN;
 }
 
 
-
 /*
   Create a list for all open tables matching SQL expression
 
@@ -696,11 +715,9 @@ OPEN_TABLE_LIST *list_open_tables(THD *t
   start_list= &open_list;
   open_list=0;
 
-  for (uint idx=0 ; result == 0 && idx < open_cache.records; idx++)
+  for (uint idx=0 ; result == 0 && idx < table_def_cache.records; idx++)
   {
-    OPEN_TABLE_LIST *table;
-    TABLE *entry=(TABLE*) hash_element(&open_cache,idx);
-    TABLE_SHARE *share= entry->s;
+    TABLE_SHARE *share= (TABLE_SHARE *)hash_element(&table_def_cache, idx);
 
     if (db && my_strcasecmp(system_charset_info, db, share->db.str))
       continue;
@@ -714,21 +731,7 @@ OPEN_TABLE_LIST *list_open_tables(THD *t
 
     if (check_table_access(thd, SELECT_ACL, &table_list, TRUE, TRUE, 1))
       continue;
-    /* need to check if we haven't already listed it */
-    for (table= open_list  ; table ; table=table->next)
-    {
-      if (!strcmp(table->table, share->table_name.str) &&
-	  !strcmp(table->db,    share->db.str))
-      {
-	if (entry->in_use)
-	  table->in_use++;
-	if (entry->locked_by_name)
-	  table->locked++;
-	break;
-      }
-    }
-    if (table)
-      continue;
+
     if (!(*start_list = (OPEN_TABLE_LIST *)
 	  sql_alloc(sizeof(**start_list)+share->table_cache_key.length)))
     {
@@ -739,8 +742,11 @@ OPEN_TABLE_LIST *list_open_tables(THD *t
 	   strmov(((*start_list)->db= (char*) ((*start_list)+1)),
 		  share->db.str)+1,
 	   share->table_name.str);
-    (*start_list)->in_use= entry->in_use ? 1 : 0;
-    (*start_list)->locked= entry->locked_by_name ? 1 : 0;
+    (*start_list)->in_use= 0;
+    I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables);
+    while (it++)
+      ++(*start_list)->in_use;
+    (*start_list)->locked= (share->version == 0) ? 1 : 0;
     start_list= &(*start_list)->next;
     *start_list=0;
   }
@@ -763,7 +769,7 @@ void intern_close_table(TABLE *table)
 
   free_io_cache(table);
   delete table->triggers;
-  if (table->file)                              // Not true if name lock
+  if (table->file)                              // Not true if placeholder
     (void) closefrm(table, 1);			// close file
   DBUG_VOID_RETURN;
 }
@@ -786,19 +792,11 @@ static void free_cache_entry(TABLE *tabl
   /* Assert that MERGE children are not attached before final close. */
   DBUG_ASSERT(!table->is_children_attached());
 
+  /* This should be done before releasing table share. */
+  table_def_remove_table(table);
+
   intern_close_table(table);
-  if (!table->in_use)
-  {
-    table->next->prev=table->prev;		/* remove from used chain */
-    table->prev->next=table->next;
-    if (table == unused_tables)
-    {
-      unused_tables=unused_tables->next;
-      if (table == unused_tables)
-	unused_tables=0;
-    }
-    check_unused();				// consisty check
-  }
+
   my_free((uchar*) table,MYF(0));
   DBUG_VOID_RETURN;
 }
@@ -818,6 +816,43 @@ void free_io_cache(TABLE *table)
 }
 
 
+/**
+   Auxiliary function which allows to kill delayed threads for
+   particular table identified by its share.
+
+   @param share Table share.
+
+   @pre Caller should have LOCK_open mutex acquired.
+*/
+
+static void kill_delayed_threads_for_table(TABLE_SHARE *share)
+{
+  I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables);
+  TABLE *tab;
+
+  safe_mutex_assert_owner(&LOCK_open);
+
+  while ((tab= it++))
+  {
+    THD *in_use= tab->in_use;
+
+    if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
+        ! in_use->killed)
+    {
+      in_use->killed= THD::KILL_CONNECTION;
+      pthread_mutex_lock(&in_use->mysys_var->mutex);
+      if (in_use->mysys_var->current_cond)
+      {
+        pthread_mutex_lock(in_use->mysys_var->current_mutex);
+        pthread_cond_broadcast(in_use->mysys_var->current_cond);
+        pthread_mutex_unlock(in_use->mysys_var->current_mutex);
+      }
+      pthread_mutex_unlock(&in_use->mysys_var->mutex);
+    }
+  }
+}
+
+
 /*
   Close all tables which aren't in use by any thread
 
@@ -825,18 +860,23 @@ void free_io_cache(TABLE *table)
   @param tables List of tables to remove from the cache
   @param have_lock If LOCK_open is locked
   @param wait_for_refresh Wait for a impending flush
-  @param wait_for_placeholders Wait for tables being reopened so that the GRL
-         won't proceed while write-locked tables are being reopened by other
-         threads.
 
-  @remark THD can be NULL, but then wait_for_refresh must be FALSE
-          and tables must be NULL.
+  @note THD can be NULL, but then wait_for_refresh must be FALSE
+        and tables must be NULL.
+
+  @note When called as part of FLUSH TABLES WITH READ LOCK this function
+        ignores metadata locks held by other threads. In order to avoid
+        situation when FLUSH TABLES WITH READ LOCK sneaks in at the moment
+        when some write-locked table is being reopened (by FLUSH TABLES or
+        ALTER TABLE) we have to rely on additional global shared metadata
+        lock taken by thread trying to obtain global read lock.
 */
 
 bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
-                         bool wait_for_refresh, bool wait_for_placeholders)
+                         bool wait_for_refresh)
 {
-  bool result=0;
+  bool result= FALSE;
+  bool found= TRUE;
   DBUG_ENTER("close_cached_tables");
   DBUG_ASSERT(thd || (!wait_for_refresh && !tables));
 
@@ -845,164 +885,144 @@ bool close_cached_tables(THD *thd, TABLE
   if (!tables)
   {
     refresh_version++;				// Force close of open tables
-    while (unused_tables)
-    {
-#ifdef EXTRA_DEBUG
-      if (hash_delete(&open_cache,(uchar*) unused_tables))
-	printf("Warning: Couldn't delete open table from hash\n");
-#else
-      (void) hash_delete(&open_cache,(uchar*) unused_tables);
-#endif
-    }
-    /* Free table shares */
-    while (oldest_unused_share->next)
-    {
-      pthread_mutex_lock(&oldest_unused_share->mutex);
-      hash_delete(&table_def_cache, (uchar*) oldest_unused_share);
-    }
     DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu",
                           refresh_version));
-    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;
-      }
-    }
+    kill_delayed_threads();
+    /*
+      Get rid of all unused TABLE and TABLE_SHARE instances. By doing
+      this we automatically close all tables which were marked as "old".
+    */
+    while (unused_tables)
+      free_cache_entry(unused_tables);
+    /* Free table shares which were not freed implicitly by loop above. */
+    while (oldest_unused_share->next)
+      (void) hash_delete(&table_def_cache, (uchar*) oldest_unused_share);
   }
   else
   {
     bool found=0;
     for (TABLE_LIST *table= tables; table; table= table->next_local)
     {
-      if (remove_table_from_cache(thd, table->db, table->table_name,
-                                  RTFC_OWNED_BY_THD_FLAG))
+      TABLE_SHARE *share= get_cached_table_share(table->db, table->table_name);
+
+      if (share)
+      {
+        kill_delayed_threads_for_table(share);
+        /* tdc_remove_table() also sets TABLE_SHARE::version to 0. */
+        tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, table->db,
+                         table->table_name);
 	found=1;
+      }
     }
     if (!found)
       wait_for_refresh=0;			// Nothing to wait for
   }
-#ifndef EMBEDDED_LIBRARY
-  if (!tables)
-    kill_delayed_threads();
-#endif
-  if (wait_for_refresh)
+
+  if (!have_lock)
+    pthread_mutex_unlock(&LOCK_open);
+
+  if (!wait_for_refresh)
+    DBUG_RETURN(result);
+
+  /* Code below assume that LOCK_open is released. */
+  DBUG_ASSERT(!have_lock);
+
+  if (thd->locked_tables_mode)
   {
     /*
-      If there is any table that has a lower refresh_version, wait until
-      this is closed (or this thread is killed) before returning
+      If we are under LOCK TABLES, we need to reopen the tables without
+      opening a door for any concurrent threads to sneak in and get
+      lock on our tables. To achieve this we use exclusive metadata
+      locks.
     */
-    thd->mysys_var->current_mutex= &LOCK_open;
-    thd->mysys_var->current_cond= &COND_refresh;
-    thd_proc_info(thd, "Flushing tables");
+    TABLE_LIST *tables_to_reopen= (tables ? tables :
+                                  thd->locked_tables_list.locked_tables());
+
+    for (TABLE_LIST *table_list= tables_to_reopen; table_list;
+         table_list= table_list->next_global)
+    {
+      /* A check that the table was locked for write is done by the caller. */
+      TABLE *table= find_locked_table(thd->open_tables, table_list->db,
+                                      table_list->table_name);
+
+      /* May return NULL if this table has already been closed via an alias. */
+      if (! table)
+        continue;
+
+      if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+      {
+        result= TRUE;
+        goto err_with_reopen;
+      }
+      close_all_tables_for_name(thd, table->s, FALSE);
+    }
+  }
+
+  /* Wait until all threads have closed all the tables we are flushing. */
+  DBUG_PRINT("info", ("Waiting for other threads to close their open tables"));
 
-    close_old_data_files(thd,thd->open_tables,1,1);
+  while (found && ! thd->killed)
+  {
+    found= FALSE;
+    /*
+      To avoid self and other kinds of deadlock we have to flush open HANDLERs.
+    */
     mysql_ha_flush(thd);
 
-    bool found=1;
-    /* Wait until all threads has closed all the tables we had locked */
-    DBUG_PRINT("info",
-	       ("Waiting for other threads to close their open tables"));
-    while (found && ! thd->killed)
+    pthread_mutex_lock(&LOCK_open);
+
+    thd->enter_cond(&COND_refresh, &LOCK_open, "Flushing tables");
+
+    if (!tables)
     {
-      found=0;
-      for (uint idx=0 ; idx < open_cache.records ; idx++)
+      for (uint idx=0 ; idx < table_def_cache.records ; idx++)
       {
-	TABLE *table=(TABLE*) hash_element(&open_cache,idx);
-        /* Avoid a self-deadlock. */
-        if (table->in_use == thd)
-          continue;
-        /*
-          Note that we wait here only for tables which are actually open, and
-          not for placeholders with TABLE::open_placeholder set. Waiting for
-          latter will cause deadlock in the following scenario, for example:
-
-          conn1: lock table t1 write;
-          conn2: lock table t2 write;
-          conn1: flush tables;
-          conn2: flush tables;
-
-          It also does not make sense to wait for those of placeholders that
-          are employed by CREATE TABLE as in this case table simply does not
-          exist yet.
-        */
-	if (table->needs_reopen_or_name_lock() && (table->db_stat ||
-            (table->open_placeholder && wait_for_placeholders)))
-	{
-	  found=1;
-          DBUG_PRINT("signal", ("Waiting for COND_refresh"));
-	  pthread_cond_wait(&COND_refresh,&LOCK_open);
-	  break;
-	}
+        TABLE_SHARE *share=(TABLE_SHARE*) hash_element(&table_def_cache, idx);
+        if (share->version != refresh_version)
+        {
+          found= TRUE;
+          break;
+        }
+      }
+    }
+    else
+    {
+      for (TABLE_LIST *table= tables; table; table= table->next_local)
+      {
+        TABLE_SHARE *share= get_cached_table_share(table->db, table->table_name);
+        if (share && share->version != refresh_version)
+        {
+	  found= TRUE;
+          break;
+        }
       }
     }
+
+    if (found)
+    {
+      DBUG_PRINT("signal", ("Waiting for COND_refresh"));
+      pthread_cond_wait(&COND_refresh,&LOCK_open);
+    }
+
+    thd->exit_cond(NULL);
+  }
+
+err_with_reopen:
+  if (thd->locked_tables_mode)
+  {
     /*
       No other thread has the locked tables open; reopen them and get the
       old locks. This should always succeed (unless some external process
       has removed the tables)
     */
-    thd->in_lock_tables=1;
-    result=reopen_tables(thd,1,1);
-    thd->in_lock_tables=0;
-    /* Set version for table */
-    for (TABLE *table=thd->open_tables; table ; table= table->next)
-    {
-      /*
-        Preserve the version (0) of write locked tables so that a impending
-        global read lock won't sneak in.
-      */
-      if (table->reginfo.lock_type < TL_WRITE_ALLOW_WRITE)
-        table->s->version= refresh_version;
-    }
-  }
-  if (!have_lock)
-    pthread_mutex_unlock(&LOCK_open);
-  if (wait_for_refresh)
-  {
-    pthread_mutex_lock(&thd->mysys_var->mutex);
-    thd->mysys_var->current_mutex= 0;
-    thd->mysys_var->current_cond= 0;
-    thd_proc_info(thd, 0);
-    pthread_mutex_unlock(&thd->mysys_var->mutex);
+    thd->locked_tables_list.reopen_tables(thd);
+    /*
+      Since mdl_downgrade_exclusive_lock() won't do anything with shared
+      metadata lock it is much simplier to go through all open tables rather
+      than picking only those tables that were flushed.
+    */
+    for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
+      mdl_downgrade_exclusive_lock(&thd->mdl_context, tab->mdl_lock_data);
   }
   DBUG_RETURN(result);
 }
@@ -1055,7 +1075,7 @@ bool close_cached_connection_tables(THD 
   }
 
   if (tables)
-    result= close_cached_tables(thd, tables, TRUE, FALSE, FALSE);
+    result= close_cached_tables(thd, tables, TRUE, FALSE);
 
   if (!have_lock)
     pthread_mutex_unlock(&LOCK_open);
@@ -1131,6 +1151,8 @@ static void mark_used_tables_as_free_for
 {
   for (; table ; table= table->next)
   {
+    DBUG_ASSERT(table->pos_in_locked_tables == NULL ||
+                table->pos_in_locked_tables->table == table);
     if (table->query_id == thd->query_id)
     {
       table->query_id= 0;
@@ -1163,9 +1185,8 @@ static void close_open_tables(THD *thd)
   thd->some_tables_deleted= 0;
 
   /* Free tables to hold down open files */
-  while (open_cache.records > table_cache_size && unused_tables)
-    hash_delete(&open_cache,(uchar*) unused_tables); /* purecov: tested */
-  check_unused();
+  while (table_cache_count > table_cache_size && unused_tables)
+    free_cache_entry(unused_tables);
   if (found_old_table)
   {
     /* Tell threads waiting for refresh that something has happened */
@@ -1176,6 +1197,75 @@ static void close_open_tables(THD *thd)
 }
 
 
+/**
+  Close all open instances of the table but keep the MDL lock,
+  if any.
+
+  Works both under LOCK TABLES and in the normal mode.
+  Removes all closed instances of the table from the table cache.
+
+  @param     thd     thread handle
+  @param[in] share   table share, but is just a handy way to
+                     access the table cache key
+
+  @param[in] remove_from_locked_tables
+                     TRUE if the table is being dropped or renamed.
+                     In that case the documented behaviour is to
+                     implicitly remove the table from LOCK TABLES
+                     list.
+*/
+
+void
+close_all_tables_for_name(THD *thd, TABLE_SHARE *share,
+                          bool remove_from_locked_tables)
+{
+  char key[MAX_DBKEY_LENGTH];
+  uint key_length= share->table_cache_key.length;
+
+  memcpy(key, share->table_cache_key.str, key_length);
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+  /*
+    We need to hold LOCK_open while changing the open_tables
+    list, since another thread may work on it.
+    @sa mysql_notify_thread_having_shared_lock()
+  */
+  pthread_mutex_lock(&LOCK_open);
+
+  for (TABLE **prev= &thd->open_tables; *prev; )
+  {
+    TABLE *table= *prev;
+
+    if (table->s->table_cache_key.length == key_length &&
+        !memcmp(table->s->table_cache_key.str, key, key_length))
+    {
+      /*
+        Does nothing if the table is not locked.
+        This allows one to use this function after a table
+        has been unlocked, e.g. in partition management.
+      */
+      mysql_lock_remove(thd, thd->lock, table);
+
+      thd->locked_tables_list.unlink_from_list(thd,
+                                               table->pos_in_locked_tables,
+                                               remove_from_locked_tables);
+
+      /* Make sure the table is removed from the cache */
+      table->s->version= 0;
+      close_thread_table(thd, prev);
+    }
+    else
+    {
+      /* Step to next entry in open_tables list. */
+      prev= &table->next;
+    }
+  }
+  /* We have been removing tables from the table cache. */
+  broadcast_refresh();
+  pthread_mutex_unlock(&LOCK_open);
+}
+
+
 /*
   Close all tables used by the current substatement, or all tables
   used by this thread if we are on the upper level.
@@ -1194,10 +1284,11 @@ static void close_open_tables(THD *thd)
     leave prelocked mode if needed.
 */
 
-void close_thread_tables(THD *thd)
+void close_thread_tables(THD *thd,
+                         bool skip_mdl)
 {
   TABLE *table;
-  prelocked_mode_type prelocked_mode= thd->prelocked_mode;
+  bool clear_table_lock_option= FALSE;
   DBUG_ENTER("close_thread_tables");
 
 #ifdef EXTRA_DEBUG
@@ -1255,11 +1346,12 @@ void close_thread_tables(THD *thd)
       Reset transaction state, but only if we're not inside a
       sub-statement of a prelocked statement.
     */
-    if (! prelocked_mode || thd->lex->requires_prelocking())
+    if (thd->locked_tables_mode <= LTM_LOCK_TABLES ||
+        thd->lex->requires_prelocking())
       thd->transaction.stmt.reset();
   }
 
-  if (thd->locked_tables || prelocked_mode)
+  if (thd->locked_tables_mode)
   {
 
     /* Ensure we are calling ha_reset() for all used tables */
@@ -1268,8 +1360,13 @@ void close_thread_tables(THD *thd)
     /*
       We are under simple LOCK TABLES or we're inside a sub-statement
       of a prelocked statement, so should not do anything else.
+
+      Note that even if we are in LTM_LOCK_TABLES mode and statement
+      requires prelocking (e.g. when we are closing tables after
+      failing ot "open" all tables required for statement execution)
+      we will exit this function a few lines below.
     */
-    if (!prelocked_mode || !thd->lex->requires_prelocking())
+    if (! thd->lex->requires_prelocking())
       DBUG_VOID_RETURN;
 
     /*
@@ -1277,14 +1374,19 @@ void close_thread_tables(THD *thd)
       so we have to leave the prelocked mode now with doing implicit
       UNLOCK TABLES if needed.
     */
-    DBUG_PRINT("info",("thd->prelocked_mode= NON_PRELOCKED"));
-    thd->prelocked_mode= NON_PRELOCKED;
+    if (thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)
+      thd->locked_tables_mode= LTM_LOCK_TABLES;
 
-    if (prelocked_mode == PRELOCKED_UNDER_LOCK_TABLES)
+    if (thd->locked_tables_mode == LTM_LOCK_TABLES)
       DBUG_VOID_RETURN;
 
-    thd->lock= thd->locked_tables;
-    thd->locked_tables= 0;
+    thd->locked_tables_mode= LTM_NONE;
+    clear_table_lock_option= TRUE;
+
+    /*
+      Note that we are leaving prelocked mode so we don't need
+      to care about THD::locked_tables_root.
+    */
     /* Fallthrough */
   }
 
@@ -1306,23 +1408,22 @@ void close_thread_tables(THD *thd)
   /*
     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())
+    (See: mysql_notify_thread_having_shared_lock())
     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);
 
-  if (prelocked_mode == PRELOCKED)
+  mdl_release_locks(&thd->mdl_context);
+  if (!skip_mdl)
   {
-    /*
-      If we are here then we are leaving normal prelocked mode, so it is
-      good idea to turn off OPTION_TABLE_LOCK flag.
-    */
-    DBUG_ASSERT(thd->lex->requires_prelocking());
-    thd->options&= ~(OPTION_TABLE_LOCK);
+    mdl_remove_all_locks(&thd->mdl_context);
   }
 
+  if (clear_table_lock_option)
+    thd->options&= ~OPTION_TABLE_LOCK;
+
   DBUG_VOID_RETURN;
 }
 
@@ -1336,8 +1437,7 @@ bool close_thread_table(THD *thd, TABLE 
   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' %p", table->s->db.str,
-                        table->s->table_name.str, table));
+  safe_mutex_assert_owner(&LOCK_open);
 
   *table_ptr=table->next;
   /*
@@ -1347,35 +1447,21 @@ bool close_thread_table(THD *thd, TABLE 
   if (table->child_l || table->parent)
     detach_merge_children(table, TRUE);
 
-  if (table->needs_reopen_or_name_lock() ||
+  table->mdl_lock_data= 0;
+  if (table->needs_reopen() ||
       thd->version != refresh_version || !table->db_stat)
   {
-    hash_delete(&open_cache,(uchar*) table);
+    free_cache_entry(table);
     found_old_table=1;
   }
   else
   {
-    /*
-      Open placeholders have TABLE::db_stat set to 0, so they should be
-      handled by the first alternative.
-    */
-    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;
-    if (unused_tables)
-    {
-      table->next=unused_tables;		/* Link in last */
-      table->prev=unused_tables->prev;
-      unused_tables->prev=table;
-      table->prev->next=table;
-    }
-    else
-      unused_tables=table->next=table->prev=table;
+    table_def_unuse_table(table);
   }
   DBUG_RETURN(found_old_table);
 }
@@ -1761,9 +1847,10 @@ TABLE *find_temporary_table(THD *thd, TA
   Try to locate the table in the list of thd->temporary_tables.
   If the table is found:
    - if the table is being used by some outer statement, fail.
-   - if the table is in thd->locked_tables, unlock it and
-     remove it from the list of locked tables. Currently only transactional
-     temporary tables are present in the locked_tables list.
+   - if the table is locked with LOCK TABLES or by prelocking,
+   unlock it and remove it from the list of locked tables
+   (THD::lock). Currently only transactional temporary tables
+   are locked.
    - Close the temporary table, remove its .FRM
    - remove the table from the list of temporary tables
 
@@ -1802,7 +1889,7 @@ int drop_temporary_table(THD *thd, TABLE
     If LOCK TABLES list is not empty and contains this table,
     unlock the table and remove the table from this list.
   */
-  mysql_lock_remove(thd, thd->locked_tables, table, FALSE);
+  mysql_lock_remove(thd, thd->lock, table);
   close_temporary_table(thd, table, 1, 1);
   DBUG_RETURN(0);
 }
@@ -1923,179 +2010,70 @@ bool rename_temporary_table(THD* thd, TA
 }
 
 
-	/* move table first in unused links */
-
-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 */
-    table->next->prev=table->prev;
-    table->next=unused_tables;			/* Link in unused tables */
-    table->prev=unused_tables->prev;
-    unused_tables->prev->next=table;
-    unused_tables->prev=table;
-    unused_tables=table;
-    check_unused();
-  }
-}
-
-
 /**
-  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;
-}
-
+   Force all other threads to stop using the table by upgrading
+   metadata lock on it and remove unused TABLE instances from cache.
 
-/**
-    Remove all instances of table from thread's open list and
-    table cache.
+   @param thd      Thread handler
+   @param table    Table to remove from cache
+   @param function HA_EXTRA_PREPARE_FOR_DROP if table is to be deleted
+                   HA_EXTRA_FORCE_REOPEN if table is not be used
+                   HA_EXTRA_PREPARE_FOR_RENAME if table is to be renamed
 
-    @param  thd     Thread context
-    @param  find    Table to remove
-    @param  unlock  TRUE  - free all locks on tables removed that are
-                            done with LOCK TABLES
-                    FALSE - otherwise
+   @note When returning, the table will be unusable for other threads
+         until metadata lock is downgraded.
 
-    @note When unlock parameter is FALSE or current thread doesn't have
-          any tables locked with LOCK TABLES, tables are assumed to be
-          not locked (for example already unlocked).
+   @retval FALSE Success.
+   @retval TRUE  Failure (e.g. because thread was killed).
 */
 
-void unlink_open_table(THD *thd, TABLE *find, bool unlock)
+bool wait_while_table_is_used(THD *thd, TABLE *table,
+                              enum ha_extra_function function)
 {
-  char key[MAX_DBKEY_LENGTH];
-  uint key_length= find->s->table_cache_key.length;
-  TABLE *list, **prev;
-  DBUG_ENTER("unlink_open_table");
-
-  safe_mutex_assert_owner(&LOCK_open);
+  enum thr_lock_type old_lock_type;
 
-  memcpy(key, find->s->table_cache_key.str, key_length);
-  /*
-    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; )
-  {
-    list= *prev;
+  DBUG_ENTER("wait_while_table_is_used");
+  DBUG_PRINT("enter", ("table: '%s'  share: %p  db_stat: %u  version: %lu",
+                       table->s->table_name.str, table->s,
+                       table->db_stat, table->s->version));
 
-    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->parent ? list->parent : list, TRUE);
+  (void) table->file->extra(function);
 
-      /* Prepare MERGE table for close. Close parent if necessary. */
-      unlink_open_merge(thd, list, &prev);
+  old_lock_type= table->reginfo.lock_type;
+  mysql_lock_abort(thd, table, TRUE);	/* end threads waiting on lock */
 
-      /* Remove table from open_tables list. */
-      *prev= list->next;
-      /* Close table. */
-      hash_delete(&open_cache,(uchar*) list); // Close table
-    }
-    else
-    {
-      /* Step to next entry in open_tables list. */
-      prev= &list->next;
-    }
+  if (mdl_upgrade_shared_lock_to_exclusive(&thd->mdl_context,
+                                           table->mdl_lock_data))
+  {
+    mysql_lock_downgrade_write(thd, table, old_lock_type);
+    DBUG_RETURN(TRUE);
   }
 
-  // Notify any 'refresh' threads
-  broadcast_refresh();
-  DBUG_VOID_RETURN;
+  pthread_mutex_lock(&LOCK_open);
+  tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN,
+                   table->s->db.str, table->s->table_name.str);
+  pthread_mutex_unlock(&LOCK_open);
+  DBUG_RETURN(FALSE);
 }
 
 
 /**
-    Auxiliary routine which closes and drops open table.
+  Close a and drop a just created table in CREATE TABLE ... SELECT.
 
-    @param  thd         Thread handle
-    @param  table       TABLE object for table to be dropped
-    @param  db_name     Name of database for this table
-    @param  table_name  Name of this table
-
-    @note This routine assumes that table to be closed is open only
-          by calling thread so we needn't wait until other threads
-          will close the table. Also unless called under implicit or
-          explicit LOCK TABLES mode it assumes that table to be
-          dropped is already unlocked. In the former case it will
-          also remove lock on the table. But one should not rely on
-          this behaviour as it may change in future.
-          Currently, however, this function is never called for a
-          table that was locked with LOCK TABLES.
+  @param  thd         Thread handle
+  @param  table       TABLE object for the table to be dropped
+  @param  db_name     Name of database for this table
+  @param  table_name  Name of this table
+
+  This routine assumes that the table to be closed is open only
+  by the calling thread, so we needn't wait until other threads
+  close the table. It also assumes that the table is first
+  in thd->open_ables and a data lock on it, if any, has been
+  released. To sum up, it's tuned to work with
+  CREATE TABLE ... SELECT and CREATE TABLE .. SELECT only.
+  Note, that currently CREATE TABLE ... SELECT is not supported
+  under LOCK TABLES. This function, still, can be called in
+  prelocked mode, e.g. if we do CREATE TABLE .. SELECT f1();
 */
 
 void drop_open_table(THD *thd, TABLE *table, const char *db_name,
@@ -2105,13 +2083,14 @@ void drop_open_table(THD *thd, TABLE *ta
     close_temporary_table(thd, table, 1, 1);
   else
   {
+    DBUG_ASSERT(table == thd->open_tables);
+
     handlerton *table_type= table->s->db_type();
+    /* Ensure the table is removed from the cache. */
+    table->s->version= 0;
+
     pthread_mutex_lock(&LOCK_open);
-    /*
-      unlink_open_table() also tells threads waiting for refresh or close
-      that something has happened.
-    */
-    unlink_open_table(thd, table, FALSE);
+    close_thread_table(thd, &thd->open_tables);
     quick_rm_table(table_type, db_name, table_name, 0);
     pthread_mutex_unlock(&LOCK_open);
   }
@@ -2163,233 +2142,6 @@ void wait_for_condition(THD *thd, pthrea
 
 
 /**
-  Exclusively name-lock a table that is already write-locked by the
-  current thread.
-
-  @param thd current thread context
-  @param tables table list containing one table to open.
-
-  @return FALSE on success, TRUE otherwise.
-*/
-
-bool name_lock_locked_table(THD *thd, TABLE_LIST *tables)
-{
-  DBUG_ENTER("name_lock_locked_table");
-
-  /* Under LOCK TABLES we must only accept write locked tables. */
-  tables->table= find_locked_table(thd, tables->db, tables->table_name);
-
-  if (!tables->table)
-    my_error(ER_TABLE_NOT_LOCKED, MYF(0), tables->alias);
-  else if (tables->table->reginfo.lock_type < TL_WRITE_LOW_PRIORITY)
-    my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), tables->alias);
-  else
-  {
-    /*
-      Ensures that table is opened only by this thread and that no
-      other statement will open this table.
-    */
-    wait_while_table_is_used(thd, tables->table, HA_EXTRA_FORCE_REOPEN);
-    DBUG_RETURN(FALSE);
-  }
-
-  DBUG_RETURN(TRUE);
-}
-
-
-/*
-  Open table which is already name-locked by this thread.
-
-  SYNOPSIS
-    reopen_name_locked_table()
-      thd         Thread handle
-      table_list  TABLE_LIST object for table to be open, TABLE_LIST::table
-                  member should point to TABLE object which was used for
-                  name-locking.
-      link_in     TRUE  - if TABLE object for table to be opened should be
-                          linked into THD::open_tables list.
-                  FALSE - placeholder used for name-locking is already in
-                          this list so we only need to preserve TABLE::next
-                          pointer.
-
-  NOTE
-    This function assumes that its caller already acquired LOCK_open mutex.
-
-  RETURN VALUE
-    FALSE - Success
-    TRUE  - Error
-*/
-
-bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in)
-{
-  TABLE *table= table_list->table;
-  TABLE_SHARE *share;
-  char *table_name= table_list->table_name;
-  TABLE orig_table;
-  DBUG_ENTER("reopen_name_locked_table");
-
-  safe_mutex_assert_owner(&LOCK_open);
-
-  if (thd->killed || !table)
-    DBUG_RETURN(TRUE);
-
-  orig_table= *table;
-
-  if (open_unireg_entry(thd, table, table_list, table_name,
-                        table->s->table_cache_key.str,
-                        table->s->table_cache_key.length, thd->mem_root, 0))
-  {
-    intern_close_table(table);
-    /*
-      If there was an error during opening of table (for example if it
-      does not exist) '*table' object can be wiped out. To be able
-      properly release name-lock in this case we should restore this
-      object to its original state.
-    */
-    *table= orig_table;
-    DBUG_RETURN(TRUE);
-  }
-
-  share= table->s;
-  /*
-    We want to prevent other connections from opening this table until end
-    of statement as it is likely that modifications of table's metadata are
-    not yet finished (for example CREATE TRIGGER have to change .TRG file,
-    or we might want to drop table if CREATE TABLE ... SELECT fails).
-    This also allows us to assume that no other connection will sneak in
-    before we will get table-level lock on this table.
-  */
-  share->version=0;
-  table->in_use = thd;
-  check_unused();
-
-  if (link_in)
-  {
-    table->next= thd->open_tables;
-    thd->open_tables= table;
-  }
-  else
-  {
-    /*
-      TABLE object should be already in THD::open_tables list so we just
-      need to set TABLE::next correctly.
-    */
-    table->next= orig_table.next;
-  }
-
-  table->tablenr=thd->current_tablenr++;
-  table->used_fields=0;
-  table->const_table=0;
-  table->null_row= table->maybe_null= table->force_index= 0;
-  table->status=STATUS_NO_RECORD;
-  DBUG_RETURN(FALSE);
-}
-
-
-/**
-    Create and insert into table cache placeholder for table
-    which will prevent its opening (or creation) (a.k.a lock
-    table name).
-
-    @param thd         Thread context
-    @param key         Table cache key for name to be locked
-    @param key_length  Table cache key length
-
-    @return Pointer to TABLE object used for name locking or 0 in
-            case of failure.
-*/
-
-TABLE *table_cache_insert_placeholder(THD *thd, const char *key,
-                                      uint key_length)
-{
-  TABLE *table;
-  TABLE_SHARE *share;
-  char *key_buff;
-  DBUG_ENTER("table_cache_insert_placeholder");
-
-  safe_mutex_assert_owner(&LOCK_open);
-
-  /*
-    Create a table entry with the right key and with an old refresh version
-    Note that we must use my_multi_malloc() here as this is freed by the
-    table cache
-  */
-  if (!my_multi_malloc(MYF(MY_WME | MY_ZEROFILL),
-                       &table, sizeof(*table),
-                       &share, sizeof(*share),
-                       &key_buff, key_length,
-                       NULL))
-    DBUG_RETURN(NULL);
-
-  table->s= share;
-  share->set_table_cache_key(key_buff, key, key_length);
-  share->tmp_table= INTERNAL_TMP_TABLE;  // for intern_close_table
-  table->in_use= thd;
-  table->locked_by_name=1;
-
-  if (my_hash_insert(&open_cache, (uchar*)table))
-  {
-    my_free((uchar*) table, MYF(0));
-    DBUG_RETURN(NULL);
-  }
-
-  DBUG_RETURN(table);
-}
-
-
-/**
-    Obtain an exclusive name lock on the table if it is not cached
-    in the table cache.
-
-    @param      thd         Thread context
-    @param      db          Name of database
-    @param      table_name  Name of table
-    @param[out] table       Out parameter which is either:
-                            - set to NULL if table cache contains record for
-                              the table or
-                            - set to point to the TABLE instance used for
-                              name-locking.
-
-    @note This function takes into account all records for table in table
-          cache, even placeholders used for name-locking. This means that
-          'table' parameter can be set to NULL for some situations when
-          table does not really exist.
-
-    @retval  TRUE   Error occured (OOM)
-    @retval  FALSE  Success. 'table' parameter set according to above rules.
-*/
-
-bool lock_table_name_if_not_cached(THD *thd, const char *db,
-                                   const char *table_name, TABLE **table)
-{
-  char key[MAX_DBKEY_LENGTH];
-  uint key_length;
-  DBUG_ENTER("lock_table_name_if_not_cached");
-
-  key_length= (uint)(strmov(strmov(key, db) + 1, table_name) - key) + 1;
-  pthread_mutex_lock(&LOCK_open);
-
-  if (hash_search(&open_cache, (uchar *)key, key_length))
-  {
-    pthread_mutex_unlock(&LOCK_open);
-    DBUG_PRINT("info", ("Table is cached, name-lock is not obtained"));
-    *table= 0;
-    DBUG_RETURN(FALSE);
-  }
-  if (!(*table= table_cache_insert_placeholder(thd, key, key_length)))
-  {
-    pthread_mutex_unlock(&LOCK_open);
-    DBUG_RETURN(TRUE);
-  }
-  (*table)->open_placeholder= 1;
-  (*table)->next= thd->open_tables;
-  thd->open_tables= *table;
-  pthread_mutex_unlock(&LOCK_open);
-  DBUG_RETURN(FALSE);
-}
-
-
-/**
     Check that table exists in table definition cache, on disk
     or in some storage engine.
 
@@ -2399,8 +2151,8 @@ bool lock_table_name_if_not_cached(THD *
                          exists and to FALSE otherwise.
 
     @note This function assumes that caller owns LOCK_open mutex.
-          It also assumes that the fact that there are no name-locks
-          on the table was checked beforehand.
+          It also assumes that the fact that there are no exclusive
+          metadata locks on the table was checked beforehand.
 
     @note If there is no .FRM file for the table but it exists in one
           of engines (e.g. it was created on another node of NDB cluster)
@@ -2453,6 +2205,81 @@ bool check_if_table_exists(THD *thd, TAB
 }
 
 
+/**
+   @brief Helper function used by MDL subsystem for releasing TABLE_SHARE
+          objects in cases when it no longer wants to cache reference to it.
+*/
+
+void table_share_release_hook(void *share)
+{
+  pthread_mutex_lock(&LOCK_open);
+  release_table_share((TABLE_SHARE*) share);
+  broadcast_refresh();
+  pthread_mutex_unlock(&LOCK_open);
+}
+
+
+/**
+   A helper function that acquires an MDL lock for a table
+   being opened.
+*/
+
+static bool
+open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list,
+                        MDL_LOCK_DATA *mdl_lock_data,
+                        uint flags,
+                        enum_open_table_action *action)
+{
+  mdl_add_lock(&thd->mdl_context, mdl_lock_data);
+
+  if (table_list->open_type)
+  {
+    /*
+      In case of CREATE TABLE .. If NOT EXISTS .. SELECT, the table
+      may not yet exist. Let's acquire an exclusive lock for that
+      case. If later it turns out the table existsed, we will
+      downgrade the lock to shared. Note that, according to the
+      locking protocol, all exclusive locks must be acquired before
+      shared locks. This invariant is preserved here and is also
+      enforced by asserts in metadata locking subsystem.
+    */
+    mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE);
+    if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+    {
+      mdl_remove_lock(&thd->mdl_context, mdl_lock_data);
+      return 1;
+    }
+  }
+  else
+  {
+    bool retry;
+
+    /*
+      There is no MDL_SHARED_UPGRADABLE_HIGH_PRIO type of metadata lock so we
+      want to be sure that caller doesn't pass us both flags simultaneously.
+    */
+    DBUG_ASSERT(!(flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) ||
+                !(flags & MYSQL_LOCK_IGNORE_FLUSH));
+
+    if (flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL &&
+        table_list->lock_type >= TL_WRITE_ALLOW_WRITE)
+      mdl_set_lock_type(mdl_lock_data, MDL_SHARED_UPGRADABLE);
+    if (flags & MYSQL_LOCK_IGNORE_FLUSH)
+      mdl_set_lock_type(mdl_lock_data, MDL_SHARED_HIGH_PRIO);
+
+    if (mdl_acquire_shared_lock(&thd->mdl_context, mdl_lock_data, &retry))
+    {
+      if (retry)
+        *action= OT_BACK_OFF_AND_RETRY;
+      else
+        mdl_remove_lock(&thd->mdl_context, mdl_lock_data);
+      return 1;
+    }
+  }
+  return 0;
+}
+
+
 /*
   Open a table.
 
@@ -2460,55 +2287,62 @@ bool check_if_table_exists(THD *thd, TAB
     open_table()
     thd                 Thread context.
     table_list          Open first table in list.
-    refresh      INOUT  Pointer to memory that will be set to 1 if
-                        we need to close all tables and reopen them.
-                        If this is a NULL pointer, then the table is not
-                        put in the thread-open-list.
+    action       INOUT  Pointer to variable of enum_open_table_action type
+                        which will be set according to action which is
+                        required to remedy problem appeared during attempt
+                        to open table.
     flags               Bitmap of flags to modify how open works:
                           MYSQL_LOCK_IGNORE_FLUSH - Open table even if
-                          someone has done a flush or namelock on it.
+                          someone has done a flush or there is a pending
+                          exclusive metadata lock requests against it
+                          (i.e. request high priority metadata lock).
                           No version number checking is done.
                           MYSQL_OPEN_TEMPORARY_ONLY - Open only temporary
                           table not the base table or view.
+                          MYSQL_OPEN_TAKE_UPGRADABLE_MDL - Obtain upgradable
+                          metadata lock for tables on which we are going to
+                          take some kind of write table-level lock.
 
   IMPLEMENTATION
     Uses a cache of open tables to find a table not in use.
 
-    If table list element for the table to be opened has "create" flag
-    set and table does not exist, this function will automatically insert
-    a placeholder for exclusive name lock into the open tables cache and
-    will return the TABLE instance that corresponds to this placeholder.
+    If table list element for the table to be opened has "open_type" set
+    to OPEN_OR_CREATE and table does not exist, this function will take
+    exclusive metadata lock on the table, also it will do this if
+    "open_type" is TAKE_EXCLUSIVE_MDL.
 
   RETURN
-    NULL  Open failed.  If refresh is set then one should close
-          all other tables and retry the open.
-    #     Success. Pointer to TABLE object for open table.
+    TRUE  Open failed. "action" parameter may contain type of action
+          needed to remedy problem before retrying again.
+    FALSE Success. Members of TABLE_LIST structure are filled properly (e.g.
+          TABLE_LIST::table is set for real tables and TABLE_LIST::view is
+          set for views).
 */
 
 
-TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
-		  bool *refresh, uint flags)
+bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
+                enum_open_table_action *action, uint flags)
 {
   reg1	TABLE *table;
   char	key[MAX_DBKEY_LENGTH];
   uint	key_length;
   char	*alias= table_list->alias;
-  HASH_SEARCH_STATE state;
+  MDL_LOCK_DATA *mdl_lock_data;
+  int error;
+  TABLE_SHARE *share;
   DBUG_ENTER("open_table");
 
   /* Parsing of partitioning information from .frm needs thd->lex set up. */
   DBUG_ASSERT(thd->lex->is_lex_started);
 
-  /* find a unused table in the open table cache */
-  if (refresh)
-    *refresh=0;
+  *action= OT_NO_ACTION;
 
   /* an open table operation needs a lot of the stack space */
   if (check_stack_overrun(thd, STACK_MIN_SIZE_FOR_OPEN, (uchar *)&alias))
-    DBUG_RETURN(0);
+    DBUG_RETURN(TRUE);
 
   if (thd->killed)
-    DBUG_RETURN(0);
+    DBUG_RETURN(TRUE);
 
   key_length= (create_table_def_key(thd, key, table_list, 1) -
                TMP_TABLE_KEY_EXTRA);
@@ -2520,7 +2354,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
     same name. This block implements the behaviour.
     TODO: move this block into a separate function.
   */
-  if (!table_list->skip_temporary)
+  if (!table_list->skip_temporary && ! (flags & MYSQL_OPEN_SKIP_TEMPORARY))
   {
     for (table= thd->temporary_tables; table ; table=table->next)
     {
@@ -2542,7 +2376,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
                       (ulong) table->query_id, (uint) thd->server_id,
                       (ulong) thd->variables.pseudo_thread_id));
 	  my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias);
-	  DBUG_RETURN(0);
+	  DBUG_RETURN(TRUE);
 	}
 	table->query_id= thd->query_id;
 	thd->thread_specific_used= TRUE;
@@ -2555,7 +2389,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
   if (flags & MYSQL_OPEN_TEMPORARY_ONLY)
   {
     my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->table_name);
-    DBUG_RETURN(0);
+    DBUG_RETURN(TRUE);
   }
 
   /*
@@ -2565,11 +2399,12 @@ TABLE *open_table(THD *thd, TABLE_LIST *
     open not pre-opened tables in pre-locked/LOCK TABLES mode.
     TODO: move this block into a separate function.
   */
-  if (thd->locked_tables || thd->prelocked_mode)
+  if (thd->locked_tables_mode &&
+      ! (flags & MYSQL_OPEN_GET_NEW_TABLE))
   {						// Using table locks
     TABLE *best_table= 0;
     int best_distance= INT_MIN;
-    bool check_if_used= thd->prelocked_mode &&
+    bool check_if_used= thd->locked_tables_mode > LTM_LOCK_TABLES &&
                         ((int) table_list->lock_type >=
                          (int) TL_WRITE_ALLOW_WRITE);
     for (table=thd->open_tables; table ; table=table->next)
@@ -2588,7 +2423,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
           */
           my_error(ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG, MYF(0),
                    table->s->table_name.str);
-          DBUG_RETURN(0);
+          DBUG_RETURN(TRUE);
         }
         /*
           When looking for a usable TABLE, ignore MERGE children, as they
@@ -2596,7 +2431,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *
         */
         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->locked_tables_mode == LTM_LOCK_TABLES ||
+             table->query_id == 0) &&
             !table->parent)
         {
           int distance= ((int) table->reginfo.lock_type -
@@ -2643,29 +2479,33 @@ TABLE *open_table(THD *thd, TABLE_LIST *
       Is this table a view and not a base table?
       (it is work around to allow to open view with locked tables,
       real fix will be made after definition cache will be made)
+
+      Since opening of view which was not explicitly locked by LOCK
+      TABLES breaks metadata locking protocol (potentially can lead
+      to deadlocks) it should be disallowed.
     */
+    if (mdl_is_lock_owner(&thd->mdl_context, 0, table_list->db,
+                          table_list->table_name))
     {
       char path[FN_REFLEN];
       enum legacy_db_type not_used;
       build_table_filename(path, sizeof(path) - 1,
                            table_list->db, table_list->table_name, reg_ext, 0);
+      /*
+        Note that we can't be 100% sure that it is a view since it's
+        possible that we either simply have not found unused TABLE
+        instance in THD::open_tables list or were unable to open table
+        during prelocking process (in this case in theory we still
+        should hold shared metadata lock on it).
+      */
       if (mysql_frm_type(thd, path, &not_used) == FRMTYPE_VIEW)
       {
-        /*
-          Will not be used (because it's VIEW) but has to be passed.
-          Also we will not free it (because it is a stack variable).
-        */
-        TABLE tab;
-        table= &tab;
-        pthread_mutex_lock(&LOCK_open);
-        if (!open_unireg_entry(thd, table, table_list, alias,
-                              key, key_length, mem_root, 0))
+        if (!tdc_open_view(thd, table_list, alias, key, key_length,
+                           mem_root, 0))
         {
           DBUG_ASSERT(table_list->view != 0);
-          pthread_mutex_unlock(&LOCK_open);
-          DBUG_RETURN(0); // VIEW
+          DBUG_RETURN(FALSE); // VIEW
         }
-        pthread_mutex_unlock(&LOCK_open);
       }
     }
     /*
@@ -2675,29 +2515,26 @@ TABLE *open_table(THD *thd, TABLE_LIST *
       so we may only end up here if the table did not exist when
       locked tables list was created.
     */
-    if (thd->prelocked_mode == PRELOCKED)
+    if (thd->locked_tables_mode == LTM_PRELOCKED)
       my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->alias);
     else
       my_error(ER_TABLE_NOT_LOCKED, MYF(0), alias);
-    DBUG_RETURN(0);
+    DBUG_RETURN(TRUE);
   }
 
   /*
-    Non pre-locked/LOCK TABLES mode, and the table is not temporary:
-    this is the normal use case.
-    Now we should:
-    - try to find the table in the table cache.
-    - if one of the discovered TABLE instances is name-locked
-      (table->s->version == 0) or some thread has started FLUSH TABLES
-      (refresh_version > table->s->version), back off -- we have to wait
-      until no one holds a name lock on the table.
-    - if there is no such TABLE in the name cache, read the table definition
-    and insert it into the cache.
-    We perform all of the above under LOCK_open which currently protects
-    the open cache (also known as table cache) and table definitions stored
-    on disk.
+    Non pre-locked/LOCK TABLES mode, and the table is not temporary.
+    This is the normal use case.
   */
 
+  mdl_lock_data= table_list->mdl_lock_data;
+  if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK))
+  {
+    if (open_table_get_mdl_lock(thd, table_list, mdl_lock_data, flags,
+                                action))
+      DBUG_RETURN(TRUE);
+  }
+
   pthread_mutex_lock(&LOCK_open);
 
   /*
@@ -2714,226 +2551,208 @@ TABLE *open_table(THD *thd, TABLE_LIST *
            ! (flags & MYSQL_LOCK_IGNORE_FLUSH))
   {
     /* Someone did a refresh while thread was opening tables */
-    if (refresh)
-      *refresh=1;
+    *action= OT_BACK_OFF_AND_RETRY;
     pthread_mutex_unlock(&LOCK_open);
-    DBUG_RETURN(0);
+    DBUG_RETURN(TRUE);
   }
 
-  /*
-    In order for the back off and re-start process to work properly,
-    handler tables having old versions (due to FLUSH TABLES or pending
-    name-lock) MUST be closed. This is specially important if a name-lock
-    is pending for any table of the handler_tables list, otherwise a
-    deadlock may occur.
-  */
-  if (thd->handler_tables)
-    mysql_ha_flush(thd);
-
-  /*
-    Actually try to find the table in the open_cache.
-    The cache may contain several "TABLE" instances for the same
-    physical table. The instances that are currently "in use" by
-    some thread have their "in_use" member != NULL.
-    There is no good reason for having more than one entry in the
-    hash for the same physical table, except that we use this as
-    an implicit "pending locks queue" - see
-    wait_for_locked_table_names for details.
-  */
-  for (table= (TABLE*) hash_first(&open_cache, (uchar*) key, key_length,
-                                  &state);
-       table && table->in_use ;
-       table= (TABLE*) hash_next(&open_cache, (uchar*) key, key_length,
-                                 &state))
+  if (table_list->open_type == TABLE_LIST::OPEN_OR_CREATE)
   {
-    DBUG_PRINT("tcache", ("in_use table: '%s'.'%s' %p", table->s->db.str,
-                          table->s->table_name.str, table));
-    /*
-      Here we flush tables marked for flush.
-      Normally, table->s->version contains the value of
-      refresh_version from the moment when this table was
-      (re-)opened and added to the cache.
-      If since then we did (or just started) FLUSH TABLES
-      statement, refresh_version has been increased.
-      For "name-locked" TABLE instances, table->s->version is set
-      to 0 (see lock_table_name for details).
-      In case there is a pending FLUSH TABLES or a name lock, we
-      need to back off and re-start opening tables.
-      If we do not back off now, we may dead lock in case of lock
-      order mismatch with some other thread:
-      c1: name lock t1; -- sort of exclusive lock 
-      c2: open t2;      -- sort of shared lock
-      c1: name lock t2; -- blocks
-      c2: open t1; -- blocks
-    */
-    if (table->needs_reopen_or_name_lock())
-    {
-      DBUG_PRINT("note",
-                 ("Found table '%s.%s' with different refresh version",
-                  table_list->db, table_list->table_name));
+    bool exists;
 
-      if (flags & MYSQL_LOCK_IGNORE_FLUSH)
-      {
-        /* Force close at once after usage */
-        thd->version= table->s->version;
-        continue;
-      }
+    if (check_if_table_exists(thd, table_list, &exists))
+      goto err_unlock2;
 
-      /* Avoid self-deadlocks by detecting self-dependencies. */
-      if (table->open_placeholder && table->in_use == thd)
-      {
-	pthread_mutex_unlock(&LOCK_open);
-        my_error(ER_UPDATE_TABLE_USED, MYF(0), table->s->table_name.str);
-        DBUG_RETURN(0);
-      }
+    if (!exists)
+    {
+      pthread_mutex_unlock(&LOCK_open);
+      DBUG_RETURN(FALSE);
+    }
+    /* Table exists. Let us try to open it. */
+  }
+  else if (table_list->open_type == TABLE_LIST::TAKE_EXCLUSIVE_MDL)
+  {
+    pthread_mutex_unlock(&LOCK_open);
+    DBUG_RETURN(FALSE);
+  }
 
+  if (!(share= (TABLE_SHARE *)mdl_get_cached_object(mdl_lock_data)))
+  {
+    if (!(share= get_table_share_with_create(thd, table_list, key,
+                                             key_length, OPEN_VIEW,
+                                             &error)))
+      goto err_unlock2;
+
+    if (share->is_view)
+    {
       /*
-        Back off, part 1: mark the table as "unused" for the
-        purpose of name-locking by setting table->db_stat to 0. Do
-        that only for the tables in this thread that have an old
-        table->s->version (this is an optimization (?)).
-        table->db_stat == 0 signals wait_for_locked_table_names
-        that the tables in question are not used any more. See
-        table_is_used call for details.
-
-        Notice that HANDLER tables were already taken care of by
-        the earlier call to mysql_ha_flush() in this same critical
-        section.
-      */
-      close_old_data_files(thd,thd->open_tables,0,0);
-      /*
-        Back-off part 2: try to avoid "busy waiting" on the table:
-        if the table is in use by some other thread, we suspend
-        and wait till the operation is complete: when any
-        operation that juggles with table->s->version completes,
-        it broadcasts COND_refresh condition variable.
-        If 'old' table we met is in use by current thread we return
-        without waiting since in this situation it's this thread
-        which is responsible for broadcasting on COND_refresh
-        (and this was done already in close_old_data_files()).
-        Good example of such situation is when we have statement
-        that needs two instances of table and FLUSH TABLES comes
-        after we open first instance but before we open second
-        instance.
+        This table is a view. Validate its metadata version: in particular,
+        that it was a view when the statement was prepared.
       */
-      if (table->in_use != thd)
+      if (check_and_update_table_version(thd, table_list, share))
+        goto err_unlock;
+      if (table_list->i_s_requested_object &  OPEN_TABLE_ONLY)
+        goto err_unlock;
+
+      /* Open view */
+      if (open_new_frm(thd, share, alias,
+                       (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
+                               HA_GET_INDEX | HA_TRY_READ_ONLY),
+                       READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD |
+                       (flags & OPEN_VIEW_NO_PARSE), thd->open_options,
+                       0, table_list, mem_root))
+        goto err_unlock;
+
+      /* TODO: Don't free this */
+      release_table_share(share);
+
+      if (flags & OPEN_VIEW_NO_PARSE)
       {
-        /* wait_for_conditionwill unlock LOCK_open for us */
-        wait_for_condition(thd, &LOCK_open, &COND_refresh);
+        /*
+          VIEW not really opened, only frm were read.
+          Set 1 as a flag here
+        */
+        table_list->view= (st_lex*)1;
       }
       else
       {
-	pthread_mutex_unlock(&LOCK_open);
+        DBUG_ASSERT(table_list->view);
       }
-      /*
-        There is a refresh in progress for this table.
-        Signal the caller that it has to try again.
-      */
-      if (refresh)
-	*refresh=1;
-      DBUG_RETURN(0);
+
+      pthread_mutex_unlock(&LOCK_open);
+      DBUG_RETURN(FALSE);
     }
+    /*
+      Note that situation when we are trying to open a table for what
+      was a view during previous execution of PS will be handled in by
+      the caller. Here we should simply open our table even if
+      TABLE_LIST::view is true.
+    */
+
+    if (table_list->i_s_requested_object &  OPEN_VIEW_ONLY)
+      goto err_unlock;
+
+    /*
+      We are going to to store extra reference to the share in MDL-subsystem
+      so we need to increase reference counter;
+    */
+    reference_table_share(share);
+    mdl_set_cached_object(mdl_lock_data, share, table_share_release_hook);
   }
-  if (table)
+  else
   {
-    DBUG_PRINT("tcache", ("unused table: '%s'.'%s' %p", table->s->db.str,
-                          table->s->table_name.str, table));
-    /* Unlink the table from "unused_tables" list. */
-    if (table == unused_tables)
-    {						// First unused
-      unused_tables=unused_tables->next;	// Remove from link
-      if (table == unused_tables)
-	unused_tables=0;
+    if (table_list->view)
+    {
+      DBUG_ASSERT(thd->m_reprepare_observer);
+      check_and_update_table_version(thd, table_list, share);
+      /* Always an error. */
+      DBUG_ASSERT(thd->is_error());
+      goto err_unlock;
     }
-    table->prev->next=table->next;		/* Remove from unused list */
-    table->next->prev=table->prev;
-    table->in_use= thd;
+    /* When we have cached TABLE_SHARE we know that is not a view. */
+    if (table_list->i_s_requested_object &  OPEN_VIEW_ONLY)
+      goto err_unlock;
+
+    /*
+      We are going to use this share for construction of new TABLE object
+      so reference counter should be increased.
+    */
+    reference_table_share(share);
+  }
+
+  if (share->version != refresh_version)
+  {
+    if (!(flags & MYSQL_LOCK_IGNORE_FLUSH))
+    {
+       /*
+         We already have an MDL lock. But we have encountered an old
+         version of table in the table definition cache which is possible
+         when someone changes the table version directly in the cache
+         without acquiring a metadata lock (e.g. this can happen during
+         "rolling" FLUSH TABLE(S)).
+         Note, that to avoid a "busywait" in this case, we have to wait
+         separately in the caller for old table versions to go away
+         (see tdc_wait_for_old_versions()).
+       */
+      *action= OT_BACK_OFF_AND_RETRY;
+      release_table_share(share);
+      pthread_mutex_unlock(&LOCK_open);
+      DBUG_RETURN(TRUE);
+    }
+    /* Force close at once after usage */
+    thd->version= share->version;
+  }
+
+  if (!share->free_tables.is_empty())
+  {
+    table= share->free_tables.head();
+    table_def_use_table(thd, table);
+    /* We need to release share as we have EXTRA reference to it in our hands. */
+    release_table_share(share);
   }
   else
   {
-    /* 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)
-      hash_delete(&open_cache,(uchar*) unused_tables); /* purecov: tested */
+    /* We have too many TABLE instances around let us try to get rid of them. */
+    while (table_cache_count > table_cache_size && unused_tables)
+      free_cache_entry(unused_tables);
 
-    if (table_list->create)
+    /* make a new table */
+    if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
+      goto err_unlock;
+
+    error= open_table_from_share(thd, share, alias,
+                                 (uint) (HA_OPEN_KEYFILE |
+                                         HA_OPEN_RNDFILE |
+                                         HA_GET_INDEX |
+                                         HA_TRY_READ_ONLY),
+                                 (READ_KEYINFO | COMPUTE_TYPES |
+                                  EXTRA_RECORD),
+                                 thd->open_options, table, OTM_OPEN);
+
+    if (error)
     {
-      bool exists;
+      my_free(table, MYF(0));
 
-      if (check_if_table_exists(thd, table_list, &exists))
+      if (error == 7)
       {
-        pthread_mutex_unlock(&LOCK_open);
-        DBUG_RETURN(NULL);
+        share->version= 0;
+        *action= OT_DISCOVER;
       }
-
-      if (!exists)
+      else if (share->crashed)
       {
-        /*
-          Table to be created, so we need to create placeholder in table-cache.
-        */
-        if (!(table= table_cache_insert_placeholder(thd, key, key_length)))
-        {
-          pthread_mutex_unlock(&LOCK_open);
-          DBUG_RETURN(NULL);
-        }
-        /*
-          Link placeholder to the open tables list so it will be automatically
-          removed once tables are closed. Also mark it so it won't be ignored
-          by other trying to take name-lock.
-        */
-        table->open_placeholder= 1;
-        table->next= thd->open_tables;
-        thd->open_tables= table;
-        pthread_mutex_unlock(&LOCK_open);
-        DBUG_RETURN(table);
+        share->version= 0;
+        *action= OT_REPAIR;
       }
-      /* Table exists. Let us try to open it. */
-    }
 
-    /* make a new table */
-    if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
-    {
-      pthread_mutex_unlock(&LOCK_open);
-      DBUG_RETURN(NULL);
+      goto err_unlock;
     }
 
-    error= open_unireg_entry(thd, table, table_list, alias, key, key_length,
-                             mem_root, (flags & OPEN_VIEW_NO_PARSE));
-    if (error > 0)
+    if (open_table_entry_fini(thd, share, table))
     {
+      closefrm(table, 0);
       my_free((uchar*)table, MYF(0));
-      pthread_mutex_unlock(&LOCK_open);
-      DBUG_RETURN(NULL);
+      goto err_unlock;
     }
-    if (table_list->view || error < 0)
-    {
-      /*
-        VIEW not really opened, only frm were read.
-        Set 1 as a flag here
-      */
-      if (error < 0)
-        table_list->view= (st_lex*)1;
 
-      my_free((uchar*)table, MYF(0));
-      pthread_mutex_unlock(&LOCK_open);
-      DBUG_RETURN(0); // VIEW
-    }
-    DBUG_PRINT("info", ("inserting table '%s'.'%s' %p into the cache",
-                        table->s->db.str, table->s->table_name.str,
-                        table));
-    (void) my_hash_insert(&open_cache,(uchar*) table);
+    /* Add table to the share's used tables list. */
+    table_def_add_used_table(thd, table);
   }
 
-  check_unused();				// Debugging call
-
   pthread_mutex_unlock(&LOCK_open);
-  if (refresh)
-  {
-    table->next=thd->open_tables;		/* Link into simple list */
-    thd->open_tables=table;
-  }
+
+  /*
+    In CREATE TABLE .. If NOT EXISTS .. SELECT we have found that
+    table exists now we should downgrade our exclusive metadata
+    lock on this table to shared metadata lock.
+  */
+  if (table_list->open_type == TABLE_LIST::OPEN_OR_CREATE)
+    mdl_downgrade_exclusive_lock(&thd->mdl_context, table_list->mdl_lock_data);
+
+  table->mdl_lock_data= mdl_lock_data;
+
+  table->next=thd->open_tables;		/* Link into simple list */
+  thd->open_tables=table;
+
   table->reginfo.lock_type=TL_READ;		/* Assume read */
 
  reset:
@@ -2950,7 +2769,6 @@ TABLE *open_table(THD *thd, TABLE_LIST *
                                      MYF(MY_WME));
     memcpy((char*) table->alias, alias, length);
   }
-  /* These variables are also set in reopen_table() */
   table->tablenr=thd->current_tablenr++;
   table->used_fields=0;
   table->const_table=0;
@@ -2967,17 +2785,39 @@ TABLE *open_table(THD *thd, TABLE_LIST *
   table->pos_in_table_list= table_list;
   table_list->updatable= 1; // It is not derived table nor non-updatable VIEW
   table->clear_column_bitmaps();
+  table_list->table= table;
   DBUG_ASSERT(table->key_read == 0);
-  DBUG_RETURN(table);
+  DBUG_RETURN(FALSE);
+
+err_unlock:
+  release_table_share(share);
+err_unlock2:
+  pthread_mutex_unlock(&LOCK_open);
+  if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK))
+  {
+    mdl_release_lock(&thd->mdl_context, mdl_lock_data);
+    mdl_remove_lock(&thd->mdl_context, mdl_lock_data);
+  }
+  DBUG_RETURN(TRUE);
 }
 
 
-TABLE *find_locked_table(THD *thd, const char *db,const char *table_name)
+/**
+   Find table in the list of open tables.
+
+   @param list       List of TABLE objects to be inspected.
+   @param db         Database name
+   @param table_name Table name
+
+   @return Pointer to the TABLE object found, 0 if no table found.
+*/
+
+TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name)
 {
   char	key[MAX_DBKEY_LENGTH];
   uint key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1;
 
-  for (TABLE *table=thd->open_tables; table ; table=table->next)
+  for (TABLE *table= list; table ; table=table->next)
   {
     if (table->s->table_cache_key.length == key_length &&
 	!memcmp(table->s->table_cache_key.str, key, key_length))
@@ -2987,679 +2827,300 @@ TABLE *find_locked_table(THD *thd, const
 }
 
 
-/*
-  Reopen an table because the definition has changed.
-
-  SYNOPSIS
-    reopen_table()
-    table	Table object
+/**
+   Find write locked instance of table in the list of open tables,
+   emit error if no such instance found.
 
-  NOTES
-   The data file for the table is already closed and the share is released
-   The table has a 'dummy' share that mainly contains database and table name.
+   @param thd        List of TABLE objects to be searched
+   @param db         Database name.
+   @param table_name Name of table.
 
- RETURN
-   0  ok
-   1  error. The old table object is not changed.
+   @return Pointer to write-locked TABLE instance, 0 - otherwise.
 */
 
-bool reopen_table(TABLE *table)
+TABLE *find_write_locked_table(TABLE *list, const char *db, const char *table_name)
 {
-  TABLE tmp;
-  bool error= 1;
-  Field **field;
-  uint key,part;
-  TABLE_LIST table_list;
-  THD *thd= table->in_use;
-  DBUG_ENTER("reopen_table");
-  DBUG_PRINT("tcache", ("table: '%s'.'%s' %p", table->s->db.str,
-                        table->s->table_name.str, 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)
-    sql_print_error("Table %s had a open data handler in reopen_table",
-		    table->alias);
-#endif
-  bzero((char*) &table_list, sizeof(TABLE_LIST));
-  table_list.db=         table->s->db.str;
-  table_list.table_name= table->s->table_name.str;
-  table_list.table=      table;
-
-  if (wait_for_locked_table_names(thd, &table_list))
-    DBUG_RETURN(1);                             // Thread was killed
-
-  if (open_unireg_entry(thd, &tmp, &table_list,
-			table->alias,
-                        table->s->table_cache_key.str,
-                        table->s->table_cache_key.length,
-                        thd->mem_root, 0))
-    goto end;
-
-  /* This list copies variables set by open_table */
-  tmp.tablenr=		table->tablenr;
-  tmp.used_fields=	table->used_fields;
-  tmp.const_table=	table->const_table;
-  tmp.null_row=		table->null_row;
-  tmp.maybe_null=	table->maybe_null;
-  tmp.status=		table->status;
-
-  tmp.s->table_map_id=  table->s->table_map_id;
-
-  /* Get state */
-  tmp.in_use=    	thd;
-  tmp.reginfo.lock_type=table->reginfo.lock_type;
-  tmp.grant=		table->grant;
-
-  /* Replace table in open list */
-  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
+  TABLE *tab= find_locked_table(list, db, table_name);
 
-  *table= tmp;
-  table->default_column_bitmaps();
-  table->file->change_table_ptr(table, table->s);
-
-  DBUG_ASSERT(table->alias != 0);
-  for (field=table->field ; *field ; field++)
+  if (!tab)
   {
-    (*field)->table= (*field)->orig_table= table;
-    (*field)->table_name= &table->alias;
+    my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_name);
+    return 0;
   }
-  for (key=0 ; key < table->s->keys ; key++)
+  else
   {
-    for (part=0 ; part < table->key_info[key].usable_key_parts ; part++)
-      table->key_info[key].key_part[part].field->table= table;
+    while (tab->reginfo.lock_type < TL_WRITE_LOW_PRIORITY &&
+           (tab= find_locked_table(tab->next, db, table_name)))
+      continue;
+    if (!tab)
+    {
+      my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_name);
+      return 0;
+    }
   }
-  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;
-
- end:
-  DBUG_RETURN(error);
+  return tab;
 }
 
 
+/***********************************************************************
+  class Locked_tables_list implementation. Declared in sql_class.h
+************************************************************************/
+
 /**
-    Close all instances of a table open by this thread and replace
-    them with exclusive name-locks.
+  Enter LTM_LOCK_TABLES mode.
 
-    @param thd        Thread context
-    @param db         Database name for the table to be closed
-    @param table_name Name of the table to be closed
+  Enter the LOCK TABLES mode using all the tables that are
+  currently open and locked in this connection.
+  Initializes a TABLE_LIST instance for every locked table.
 
-    @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.
+  @param  thd  thread handle
 
-    @note Must not use TABLE_SHARE::table_name/db of the table being closed,
-          the strings are used in a loop even after the share may be freed.
+  @return TRUE if out of memory.
 */
 
-void close_data_files_and_morph_locks(THD *thd, const char *db,
-                                      const char *table_name)
+bool
+Locked_tables_list::init_locked_tables(THD *thd)
 {
-  TABLE *table;
-  DBUG_ENTER("close_data_files_and_morph_locks");
-
-  safe_mutex_assert_owner(&LOCK_open);
+  DBUG_ASSERT(thd->locked_tables_mode == LTM_NONE);
+  DBUG_ASSERT(m_locked_tables == NULL);
 
-  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 *table= thd->open_tables; table; table= table->next)
   {
-    if (!strcmp(table->s->table_name.str, table_name) &&
-	!strcmp(table->s->db.str, db))
+    TABLE_LIST *src_table_list= table->pos_in_table_list;
+    char *db, *table_name, *alias;
+    size_t db_len= src_table_list->db_length;
+    size_t table_name_len= src_table_list->table_name_length;
+    size_t alias_len= strlen(src_table_list->alias);
+    TABLE_LIST *dst_table_list;
+
+    if (! multi_alloc_root(&m_locked_tables_root,
+                           &dst_table_list, sizeof(*dst_table_list),
+                           &db, db_len + 1,
+                           &table_name, table_name_len + 1,
+                           &alias, alias_len + 1,
+                           NullS))
     {
-      if (thd->locked_tables)
-      {
-        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);
+      unlock_locked_tables(0);
+      return TRUE;
     }
+
+    /**
+      Sic: remember the *actual* table level lock type taken, to
+      acquire the exact same type in reopen_tables().
+      E.g. if the table was locked for write, src_table_list->lock_type is
+      TL_WRITE_DEFAULT, whereas reginfo.lock_type has been updated from
+      thd->update_lock_default.
+    */
+    dst_table_list->init_one_table(db, db_len, table_name, table_name_len,
+                                   alias,
+                                   src_table_list->table->reginfo.lock_type);
+    dst_table_list->mdl_lock_data= src_table_list->mdl_lock_data;
+    dst_table_list->table= table;
+    memcpy(db, src_table_list->db, db_len + 1);
+    memcpy(table_name, src_table_list->table_name, table_name_len + 1);
+    memcpy(alias, src_table_list->alias, alias_len + 1);
+    /* Link last into the list of tables */
+    *(dst_table_list->prev_global= m_locked_tables_last)= dst_table_list;
+    m_locked_tables_last= &dst_table_list->next_global;
+    table->pos_in_locked_tables= dst_table_list;
   }
-  DBUG_VOID_RETURN;
+  thd->locked_tables_mode= LTM_LOCK_TABLES;
+  return FALSE;
 }
 
-
 /**
-  Reattach MERGE children after reopen.
+  Leave LTM_LOCK_TABLES mode if it's been entered.
 
-  @param[in]     thd            thread context
-  @param[in,out] err_tables_p   pointer to pointer of tables in error
+  Close all locked tables, free memory, and leave the mode.
 
-  @return       status
-    @retval     FALSE           OK, err_tables_p unchanged
-    @retval     TRUE            Error, err_tables_p contains table(s)
+  @note This function is a no-op if we're not in LOCK TABLES.
 */
 
-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");
+void
+Locked_tables_list::unlock_locked_tables(THD *thd)
 
-  for (table= thd->open_tables; table; table= next)
+{
+  if (thd)
   {
-    next= table->next;
-    DBUG_PRINT("tcache", ("check table: '%s'.'%s' %p  next: %p",
-                          table->s->db.str, table->s->table_name.str,
-                          table, 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' %p",
-                            table->s->db.str,
-                            table->s->table_name.str, table));
-      }
+    DBUG_ASSERT(!thd->in_sub_stmt &&
+                !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
+    /*
+      Sic: we must be careful to not close open tables if
+      we're not in LOCK TABLES mode: unlock_locked_tables() is
+      sometimes called implicitly, expecting no effect on
+      open tables, e.g. from begin_trans().
+    */
+    if (thd->locked_tables_mode != LTM_LOCK_TABLES)
+      return;
+
+    for (TABLE_LIST *table_list= m_locked_tables;
+         table_list; table_list= table_list->next_global)
+    {
+      /*
+        Clear the position in the list, the TABLE object will be
+        returned to the table cache.
+      */
+      table_list->table->pos_in_locked_tables= NULL;
     }
-    prv_p= &table->next;
+    thd->locked_tables_mode= LTM_NONE;
+
+    close_thread_tables(thd);
   }
-  DBUG_RETURN(error);
+  /*
+    After closing tables we can free memory used for storing lock
+    request for metadata locks and TABLE_LIST elements.
+  */
+  free_root(&m_locked_tables_root, MYF(0));
+  m_locked_tables= NULL;
+  m_locked_tables_last= &m_locked_tables;
 }
 
 
 /**
-    Reopen all tables with closed data files.
+  Unlink a locked table from the locked tables list, either
+  temporarily or permanently.
 
-    @param thd         Thread context
-    @param get_locks   Should we get locks after reopening tables ?
-    @param mark_share_as_old  Mark share as old to protect from a impending
-                              global read lock.
+  @param  thd        thread handle
+  @param  table_list the element of locked tables list.
+                     The implementation assumes that this argument
+                     points to a TABLE_LIST element linked into
+                     the locked tables list. Passing a TABLE_LIST
+                     instance that is not part of locked tables
+                     list will lead to a crash.
+  @parma  remove_from_locked_tables
+                      TRUE if the table is removed from the list
+                      permanently.
 
-    @note Since this function can't properly handle prelocking and
-          create placeholders it should be used in very special
-          situations like FLUSH TABLES or ALTER TABLE. In general
-          case one should just repeat open_tables()/lock_tables()
-          combination when one needs tables to be reopened (for
-          example see open_and_lock_tables()).
+  This function is a no-op if we're not under LOCK TABLES.
 
-    @note One should have lock on LOCK_open when calling this.
-
-    @return FALSE in case of success, TRUE - otherwise.
+  @sa Locked_tables_list::reopen_tables()
 */
 
-bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old)
+
+void Locked_tables_list::unlink_from_list(THD *thd,
+                                          TABLE_LIST *table_list,
+                                          bool remove_from_locked_tables)
 {
-  TABLE *table,*next,**prev;
-  TABLE **tables,**tables_ptr;			// For locks
-  TABLE *err_tables= NULL;
-  bool error=0, not_used;
-  bool merge_table_found= FALSE;
-  const uint flags= MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN |
-                    MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK |
-                    MYSQL_LOCK_IGNORE_FLUSH;
+  /*
+    If mode is not LTM_LOCK_TABLES, we needn't do anything. Moreover,
+    outside this mode pos_in_locked_tables value is not trustworthy.
+  */
+  if (thd->locked_tables_mode != LTM_LOCK_TABLES)
+    return;
 
-  DBUG_ENTER("reopen_tables");
+  /*
+    table_list must be set and point to pos_in_locked_tables of some
+    table.
+  */
+  DBUG_ASSERT(table_list->table->pos_in_locked_tables == table_list);
 
-  if (!thd->open_tables)
-    DBUG_RETURN(0);
+  /* Clear the pointer, the table will be returned to the table cache. */
+  table_list->table->pos_in_locked_tables= NULL;
 
-  safe_mutex_assert_owner(&LOCK_open);
-  if (get_locks)
-  {
-    /*
-      The ptr is checked later
-      Do not handle locks of MERGE children.
-    */
-    uint opens=0;
-    for (table= thd->open_tables; table ; table=table->next)
-      if (!table->parent)
-        opens++;
-    DBUG_PRINT("tcache", ("open tables to lock: %u", opens));
-    tables= (TABLE**) my_alloca(sizeof(TABLE*)*opens);
-  }
-  else
-    tables= &thd->open_tables;
-  tables_ptr =tables;
+  /* Mark the table as closed in the locked tables list. */
+  table_list->table= NULL;
 
-  prev= &thd->open_tables;
-  for (table=thd->open_tables; table ; table=next)
-  {
-    uint db_stat=table->db_stat;
-    next=table->next;
-    DBUG_PRINT("tcache", ("open table: '%s'.'%s' %p  "
-                          "parent: %p  db_stat: %u",
-                          table->s->db.str, table->s->table_name.str,
-                          table, 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);
-      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;
-      /* Do not handle locks of MERGE children. */
-      if (get_locks && !db_stat && !table->parent)
-	*tables_ptr++= table;			// need new lock on this
-      if (mark_share_as_old)
-      {
-	table->s->version=0;
-	table->open_placeholder= 0;
-      }
-    }
-  }
-  *prev=0;
   /*
-    When all tables are open again, we can re-attach MERGE children to
-    their parents. All TABLE objects are still present.
+    If the table is being dropped or renamed, remove it from
+    the locked tables list (implicitly drop the LOCK TABLES lock
+    on it).
   */
-  DBUG_PRINT("tcache", ("re-attaching MERGE tables: %d", merge_table_found));
-  if (!error && merge_table_found && reattach_merge(thd, &err_tables))
-  {
-    while (err_tables)
-    {
-      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
+  if (remove_from_locked_tables)
   {
-    MYSQL_LOCK *lock;
-    /*
-      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),
-                                 flags, &not_used)))
-    {
-      thd->locked_tables=mysql_lock_merge(thd->locked_tables,lock);
-    }
+    *table_list->prev_global= table_list->next_global;
+    if (table_list->next_global == NULL)
+      m_locked_tables_last= table_list->prev_global;
     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);
+      table_list->next_global->prev_global= table_list->prev_global;
   }
-  broadcast_refresh();
-  DBUG_RETURN(error);
 }
 
-
 /**
-    Close handlers for tables in list, but leave the TABLE structure
-    intact so that we can re-open these quickly.
+  This is an attempt to recover (somewhat) in case of an error.
+  If we failed to reopen a closed table, let's unlink it from the
+  list and forget about it. From a user perspective that would look
+  as if the server "lost" the lock on one of the locked tables.
 
-    @param thd           Thread context
-    @param table         Head of the list of TABLE objects
-    @param morph_locks   TRUE  - remove locks which we have on tables being closed
-                                 but ensure that no DML or DDL will sneak in before
-                                 we will re-open the table (i.e. temporarily morph
-                                 our table-level locks into name-locks).
-                         FALSE - otherwise
-    @param send_refresh  Should we awake waiters even if we didn't close any tables?
+  @note This function is a no-op if we're not under LOCK TABLES.
 */
 
-static void close_old_data_files(THD *thd, TABLE *table, bool morph_locks,
-                                 bool send_refresh)
+void Locked_tables_list::unlink_all_closed_tables()
 {
-  bool found= send_refresh;
-  DBUG_ENTER("close_old_data_files");
-
-  for (; table ; table=table->next)
+  for (TABLE_LIST *table_list= m_locked_tables; table_list; table_list=
+       table_list->next_global)
   {
-    DBUG_PRINT("tcache", ("checking table: '%s'.'%s' %p",
-                          table->s->db.str, table->s->table_name.str,
-                          table));
-    DBUG_PRINT("tcache", ("needs refresh: %d  is open: %u",
-                          table->needs_reopen_or_name_lock(), table->db_stat));
-    /*
-      Reopen marked for flush.
-    */
-    if (table->needs_reopen_or_name_lock())
+    if (table_list->table == NULL)
     {
-      found=1;
-      if (table->db_stat)
-      {
-	if (morph_locks)
-	{
-          /*
-            Forward lock handling to MERGE parent. But unlock parent
-            once only.
-          */
-          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.
-          */
-	  table->open_placeholder= 1;
-	}
-        close_handle_and_leave_table_as_lock(table);
-      }
-      else if (table->open_placeholder && !morph_locks)
-      {
-        /*
-          We come here only in close-for-back-off scenario. So we have to
-          "close" create placeholder here to avoid deadlocks (for example,
-          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.
-        */
-        table->open_placeholder= 0;
-      }
-    }
-  }
-  if (found)
-    broadcast_refresh();
-  DBUG_VOID_RETURN;
-}
-
-
-/*
-  Wait until all threads has closed the tables in the list
-  We have also to wait if there is thread that has a lock on this table even
-  if the table is closed
-*/
-
-bool table_is_used(TABLE *table, bool wait_for_name_lock)
-{
-  DBUG_ENTER("table_is_used");
-  do
-  {
-    char *key= table->s->table_cache_key.str;
-    uint key_length= table->s->table_cache_key.length;
-
-    DBUG_PRINT("loop", ("table_name: %s", table->alias));
-    HASH_SEARCH_STATE state;
-    for (TABLE *search= (TABLE*) hash_first(&open_cache, (uchar*) key,
-                                             key_length, &state);
-	 search ;
-         search= (TABLE*) hash_next(&open_cache, (uchar*) key,
-                                    key_length, &state))
-    {
-      DBUG_PRINT("info", ("share: %p  "
-                          "open_placeholder: %d  locked_by_name: %d "
-                          "db_stat: %u  version: %lu",
-                          search->s,
-                          search->open_placeholder, search->locked_by_name,
-                          search->db_stat,
-                          search->s->version));
-      if (search->in_use == table->in_use)
-        continue;                               // Name locked by this thread
-      /*
-        We can't use the table under any of the following conditions:
-        - There is an name lock on it (Table is to be deleted or altered)
-        - If we are in flush table and we didn't execute the flush
-        - If the table engine is open and it's an old version
-        (We must wait until all engines are shut down to use the table)
-      */
-      if ( (search->locked_by_name && wait_for_name_lock) ||
-           (search->is_name_opened() && search->needs_reopen_or_name_lock()))
-        DBUG_RETURN(1);
+      /* Unlink from list. */
+      *table_list->prev_global= table_list->next_global;
+      if (table_list->next_global == NULL)
+        m_locked_tables_last= table_list->prev_global;
+      else
+        table_list->next_global->prev_global= table_list->prev_global;
     }
-  } while ((table=table->next));
-  DBUG_RETURN(0);
-}
-
-
-/* Wait until all used tables are refreshed */
-
-bool wait_for_tables(THD *thd)
-{
-  bool result;
-  DBUG_ENTER("wait_for_tables");
-
-  thd_proc_info(thd, "Waiting for tables");
-  pthread_mutex_lock(&LOCK_open);
-  while (!thd->killed)
-  {
-    thd->some_tables_deleted=0;
-    close_old_data_files(thd,thd->open_tables,0,dropping_tables != 0);
-    mysql_ha_flush(thd);
-    if (!table_is_used(thd->open_tables,1))
-      break;
-    (void) pthread_cond_wait(&COND_refresh,&LOCK_open);
-  }
-  if (thd->killed)
-    result= 1;					// aborted
-  else
-  {
-    /* Now we can open all tables without any interference */
-    thd_proc_info(thd, "Reopen tables");
-    thd->version= refresh_version;
-    result=reopen_tables(thd,0,0);
   }
-  pthread_mutex_unlock(&LOCK_open);
-  thd_proc_info(thd, 0);
-  DBUG_RETURN(result);
 }
 
 
-/*
-  drop tables from locked list
-
-  SYNOPSIS
-    drop_locked_tables()
-    thd			Thread thandler
-    db			Database
-    table_name		Table name
-
-  INFORMATION
-    This is only called on drop tables
+/**
+  Reopen the tables locked with LOCK TABLES and temporarily closed
+  by a DDL statement or FLUSH TABLES.
 
-    The TABLE object for the dropped table is unlocked but still kept around
-    as a name lock, which means that the table will be available for other
-    thread as soon as we call unlock_table_names().
-    If there is multiple copies of the table locked, all copies except
-    the first, which acts as a name lock, is removed.
+  @note This function is a no-op if we're not under LOCK TABLES.
 
-  RETURN
-    #    If table existed, return table
-    0	 Table was not locked
+  @return TRUE if an error reopening the tables. May happen in
+               case of some fatal system error only, e.g. a disk
+               corruption, out of memory or a serious bug in the
+               locking.
 */
 
-
-TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name)
+bool
+Locked_tables_list::reopen_tables(THD *thd)
 {
-  TABLE *table,*next,**prev, *found= 0;
-  prev= &thd->open_tables;
-  DBUG_ENTER("drop_locked_tables");
+  enum enum_open_table_action ot_action_unused;
+  bool lt_refresh_unused;
 
-  /*
-    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)
+  for (TABLE_LIST *table_list= m_locked_tables;
+       table_list; table_list= table_list->next_global)
   {
-    next=table->next;
-    if (!strcmp(table->s->table_name.str, table_name) &&
-	!strcmp(table->s->db.str, db))
-    {
-      /* 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);
+    MYSQL_LOCK *lock;
 
-      if (!found)
-      {
-        found= table;
-        /* Close engine table, but keep object around as a name lock */
-        if (table->db_stat)
-        {
-          table->db_stat= 0;
-          table->file->close();
-        }
-      }
-      else
-      {
-        /* We already have a name lock, remove copy */
-        hash_delete(&open_cache,(uchar*) table);
-      }
-    }
-    else
+    if (table_list->table)                      /* The table was not closed */
+      continue;
+
+    /* Links into thd->open_tables upon success */
+    if (open_table(thd, table_list, thd->mem_root, &ot_action_unused,
+                   MYSQL_OPEN_REOPEN))
     {
-      *prev=table;
-      prev= &table->next;
+      unlink_all_closed_tables();
+      return TRUE;
     }
-  }
-  *prev=0;
-  if (found)
-    broadcast_refresh();
-  if (thd->locked_tables && thd->locked_tables->table_count == 0)
-  {
-    my_free((uchar*) thd->locked_tables,MYF(0));
-    thd->locked_tables=0;
-  }
-  DBUG_RETURN(found);
-}
-
-
-/*
-  If we have the table open, which only happens when a LOCK TABLE has been
-  done on the table, change the lock type to a lock that will abort all
-  other threads trying to get the lock.
-*/
-
-void abort_locked_tables(THD *thd,const char *db, const char *table_name)
-{
-  TABLE *table;
-  for (table= thd->open_tables; table ; table= table->next)
-  {
-    if (!strcmp(table->s->table_name.str, table_name) &&
-	!strcmp(table->s->db.str, db))
+    table_list->table->pos_in_locked_tables= table_list;
+    /* See also the comment on lock type in init_locked_tables(). */
+    table_list->table->reginfo.lock_type= table_list->lock_type;
+    thd->in_lock_tables= 1;
+    lock= mysql_lock_tables(thd, &table_list->table, 1,
+                            MYSQL_OPEN_REOPEN, &lt_refresh_unused);
+    thd->in_lock_tables= 0;
+    if (lock)
+      lock= mysql_lock_merge(thd->lock, lock);
+    if (lock == NULL)
     {
-      /* If MERGE child, forward lock handling to parent. */
-      mysql_lock_abort(thd, table->parent ? table->parent : table, TRUE);
-      break;
+      /*
+        No one's seen this branch work. Recover and report an
+        error just in case.
+      */
+      pthread_mutex_lock(&LOCK_open);
+      close_thread_table(thd, &thd->open_tables);
+      pthread_mutex_unlock(&LOCK_open);
+      table_list->table= 0;
+      unlink_all_closed_tables();
+      my_error(ER_LOCK_DEADLOCK, MYF(0));
+      return TRUE;
     }
+    thd->lock= lock;
   }
+  return FALSE;
 }
 
 
@@ -3751,7 +3212,7 @@ void assign_new_table_id(TABLE_SHARE *sh
   @retval  FALSE success, version in TABLE_LIST has been updated
 */
 
-bool
+static bool
 check_and_update_table_version(THD *thd,
                                TABLE_LIST *tables, TABLE_SHARE *table_share)
 {
@@ -3785,196 +3246,71 @@ check_and_update_table_version(THD *thd,
   return FALSE;
 }
 
-/*
-  Load a table definition from file and open unireg table
 
-  SYNOPSIS
-    open_unireg_entry()
-    thd			Thread handle
-    entry		Store open table definition here
-    table_list		TABLE_LIST with db, table_name & belong_to_view
-    alias		Alias name
-    cache_key		Key for share_cache
-    cache_key_length	length of cache_key
-    mem_root		temporary mem_root for parsing
-    flags               the OPEN_VIEW_NO_PARSE flag to be passed to
-                        openfrm()/open_new_frm()
+/**
+   Open view by getting its definition from disk (and table cache in future).
 
-  NOTES
-   Extra argument for open is taken from thd->open_options
-   One must have a lock on LOCK_open when calling this function
+   @param thd               Thread handle
+   @param table_list        TABLE_LIST with db, table_name & belong_to_view
+   @param alias             Alias name
+   @param cache_key         Key for table definition cache
+   @param cache_key_length  Length of cache_key
+   @param mem_root          Memory to be used for .frm parsing.
+   @param flags             Flags which modify how we open the view
 
-  RETURN
-    0	ok
-    #	Error
+   @todo This function is needed for special handling of views under
+         LOCK TABLES. We probably should get rid of it in long term.
+
+   @return FALSE if success, TRUE - otherwise.
 */
 
-static int open_unireg_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list,
-                             const char *alias,
-                             char *cache_key, uint cache_key_length,
-                             MEM_ROOT *mem_root, uint flags)
+bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias,
+                   char *cache_key, uint cache_key_length,
+                   MEM_ROOT *mem_root, uint flags)
 {
+  TABLE not_used;
   int error;
   TABLE_SHARE *share;
-  uint discover_retry_count= 0;
-  DBUG_ENTER("open_unireg_entry");
 
-  safe_mutex_assert_owner(&LOCK_open);
-retry:
+  pthread_mutex_lock(&LOCK_open);
+
   if (!(share= get_table_share_with_create(thd, table_list, cache_key,
                                            cache_key_length, 
-                                           OPEN_VIEW |
-                                           table_list->i_s_requested_object,
-                                           &error)))
-    DBUG_RETURN(1);
+                                           OPEN_VIEW, &error)))
+    goto err;
 
-  if (share->is_view)
+  if (share->is_view &&
+      !open_new_frm(thd, share, alias,
+                    (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
+                            HA_GET_INDEX | HA_TRY_READ_ONLY),
+                    READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD |
+                    flags, thd->open_options, &not_used, table_list,
+                    mem_root))
   {
-    /*
-      This table is a view. Validate its metadata version: in particular,
-      that it was a view when the statement was prepared.
-    */
-    if (check_and_update_table_version(thd, table_list, share))
-      goto err;
-    if (table_list->i_s_requested_object &  OPEN_TABLE_ONLY)
-      goto err;
-
-    /* Open view */
-    error= (int) open_new_frm(thd, share, alias,
-                              (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
-                                      HA_GET_INDEX | HA_TRY_READ_ONLY),
-                              READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD |
-                              (flags & OPEN_VIEW_NO_PARSE),
-                              thd->open_options, entry, table_list,
-                              mem_root);
-    if (error)
-      goto err;
-    /* TODO: Don't free this */
-    release_table_share(share, RELEASE_NORMAL);
-    DBUG_RETURN((flags & OPEN_VIEW_NO_PARSE)? -1 : 0);
-  }
-  else if (table_list->view)
-  {
-    /*
-      We're trying to open a table for what was a view.
-      This can only happen during (re-)execution.
-      At prepared statement prepare the view has been opened and
-      merged into the statement parse tree. After that, someone
-      performed a DDL and replaced the view with a base table.
-      Don't try to open the table inside a prepared statement,
-      invalidate it instead.
-
-      Note, the assert below is known to fail inside stored
-      procedures (Bug#27011).
-    */
-    DBUG_ASSERT(thd->m_reprepare_observer);
-    check_and_update_table_version(thd, table_list, share);
-    /* Always an error. */
-    DBUG_ASSERT(thd->is_error());
-    goto err;
+    release_table_share(share);
+    pthread_mutex_unlock(&LOCK_open);
+    return FALSE;
   }
 
-  if (table_list->i_s_requested_object &  OPEN_VIEW_ONLY)
-    goto err;
+  my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str, "VIEW");
+  release_table_share(share);