List:Commits« Previous MessageNext Message »
From:dlenev Date:May 22 2008 11:21am
Subject:bk commit into 6.0 tree (dlenev:1.2637) WL#3726
View as plain text  
Below is the list of changes that have just been committed into a local
6.0 repository of dlenev.  When dlenev does a push these changes
will be propagated to the main repository and, within 24 hours after the
push, to the public repository.
For information on how to access the public repository
see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html

ChangeSet@stripped, 2008-05-22 13:21:21+04:00, dlenev@stripped +76 -0
  WL#3726 "DDL locking for all metadata objects".
  
  After review fixes in progress.

  libmysqld/Makefile.am@stripped, 2008-05-22 13:21:10+04:00, dlenev@stripped +2 -1
    Added files implementing new meta-data locking subsystem to the server.

  mysql-test/include/handler.inc@stripped, 2008-05-22 13:21:10+04:00, dlenev@stripped
+9 -4
    Use separate connection for waiting while threads performing DDL operations
    conflicting with open HANDLER tables reach blocked state. This is required
    because now we check and close tables open by HANDLER statements in this
    connection conflicting with DDL in another each time open_tables() is
    called and thus select from I_S which is used for waiting will unblock DDL
    operations if issued from connection with open HANDLERs.

  mysql-test/include/locktrans.inc@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped
+1 -0
    It is no longer allowed to DROP VIEW under LOCK TABLES.

  mysql-test/r/create.result@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped +6
-1
    Adjusted test case after change in implementation of CREATE TABLE ... SELECT.
    We no longer have special check in open_table() which catches the case when
    we select from the table created. Instead we rely on unique_table() call
    which happens after opening and locking all tables.

  mysql-test/r/flush.result@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped +5 -0
    FLUSH TABLES WITH READ LOCK can no longer happen under LOCK TABLES.
    Updated test accordingly.

  mysql-test/r/flush_table.result@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped
+5 -12
    Under LOCK TABLES we no longer allow to do FLUSH TABLES for tables locked
    for read. Updated test accordingly.

  mysql-test/r/handler_innodb.result@stripped, 2008-05-22 13:21:11+04:00,
dlenev@stripped +2 -0
    Use separate connection for waiting while threads performing DDL operations
    conflicting with open HANDLER tables reach blocked state. This is required
    because now we check and close tables open by HANDLER statements in this
    connection conflicting with DDL in another each time open_tables() is
    called and thus select from I_S which is used for waiting will unblock DDL
    operations if issued from connection with open HANDLERs.

  mysql-test/r/handler_myisam.result@stripped, 2008-05-22 13:21:11+04:00,
dlenev@stripped +2 -0
    Use separate connection for waiting while threads performing DDL operations
    conflicting with open HANDLER tables reach blocked state. This is required
    because now we check and close tables open by HANDLER statements in this
    connection conflicting with DDL in another each time open_tables() is
    called and thus select from I_S which is used for waiting will unblock DDL
    operations if issued from connection with open HANDLERs.

  mysql-test/r/information_schema.result@stripped, 2008-05-22 13:21:11+04:00,
dlenev@stripped +34 -0
    Additional test for WL#3726 "DDL locking for all metadata objects".
    Check that we use high-priority metadata lock requests when filling
    I_S tables.

  mysql-test/r/kill.result@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped +103
-0
    Added tests checking that DDL and DML statements waiting for metadata
    locks can be interrupted by KILL command.

  mysql-test/r/lock.result@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped +2 -1
    One no longer is allowed to do DROP VIEW under LOCK TABLES even if 
    this view is locked by LOCK TABLES. The problem is that in such
    situation write locks on view are not mutually exclusive so upgrading
    metadata lock which is required for dropping of view will lead to
    deadlock.

  mysql-test/r/locktrans_innodb.result@stripped, 2008-05-22 13:21:11+04:00,
dlenev@stripped +1 -0
    It is no longer allowed to DROP VIEW under LOCK TABLES.

  mysql-test/r/locktrans_myisam.result@stripped, 2008-05-22 13:21:11+04:00,
dlenev@stripped +1 -0
    It is no longer allowed to DROP VIEW under LOCK TABLES.

  mysql-test/r/sp.result@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped +2 -4
    Under LOCK TABLES we no longer allow accessing views which were not
    explicitly locked. To access view we need to obtain metadata lock
    on it and doing this under LOCK TABLES may lead to deadlocks.

  mysql-test/r/view.result@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped +1 -3
    One no longer is allowed to do DROP VIEW under LOCK TABLES even if 
    this view is locked by LOCK TABLES. The problem is that in such
    situation even "write locks" on view are not mutually exclusive so
    upgrading metadata lock which is required for dropping of view will
    lead to deadlock.

  mysql-test/r/view_grant.result@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped
+2 -2
    ALTER VIEW implementation was changed to open a view only after checking
    that user which does alter has appropriate privileges on it. This means
    that in case when user's privileges are insufficient for this we won't
    check that new view definer is the same as original one or user performing
    alter has SUPER privilege. Adjusted test case accordingly.

  mysql-test/r/view_multi.result@stripped, 2008-05-22 13:21:15+04:00, dlenev@stripped
+48 -0
    Added test case for bug#25144 "replication / binlog with view breaks".

  mysql-test/r/view_multi.result@stripped, 2008-05-22 13:21:15+04:00, dlenev@stripped
+0 -0

  mysql-test/suite/rpl/r/rpl_locktrans_innodb.result@stripped, 2008-05-22 13:21:11+04:00,
dlenev@stripped +1 -0
    It is no longer allowed to DROP VIEW under LOCK TABLES.

  mysql-test/suite/rpl/r/rpl_locktrans_myisam.result@stripped, 2008-05-22 13:21:11+04:00,
dlenev@stripped +1 -0
    It is no longer allowed to DROP VIEW under LOCK TABLES.

  mysql-test/t/create.test@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped +6 -2
    Adjusted test case after change in implementation of CREATE TABLE ... SELECT.
    We no longer have special check in open_table() which catches the case when
    we select from the table created. Instead we rely on unique_table() call
    which happens after opening and locking all tables.

  mysql-test/t/flush.test@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped +9 -5
    FLUSH TABLES WITH READ LOCK can no longer happen under LOCK TABLES.
    Updated test accordingly.

  mysql-test/t/flush_table.test@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped
+8 -17
    Under LOCK TABLES we no longer allow to do FLUSH TABLES for tables locked
    for read. Updated test accordingly.

  mysql-test/t/information_schema.test@stripped, 2008-05-22 13:21:11+04:00,
dlenev@stripped +44 -0
    Additional test for WL#3726 "DDL locking for all metadata objects".
    Check that we use high-priority metadata lock requests when filling
    I_S tables.

  mysql-test/t/kill.test@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped +226 -0
    Added tests checking that DDL and DML statements waiting for metadata
    locks can be interrupted by KILL command.

  mysql-test/t/lock.test@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped +2 -1
    One no longer is allowed to do DROP VIEW under LOCK TABLES even if 
    this view is locked by LOCK TABLES. The problem is that in such
    situation write locks on view are not mutually exclusive so upgrading
    metadata lock which is required for dropping of view will lead to
    deadlock.

  mysql-test/t/lock_multi.test@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped +9
-17
    Adjusted test case to the changes of status in various places caused
    by change in implementation FLUSH TABLES WITH READ LOCK, which is now
    takes global metadata lock before flushing tables and therefore waits
    on at these places.

  mysql-test/t/sp.test@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped +7 -5
    Under LOCK TABLES we no longer allow accessing views which were not
    explicitly locked. To access view we need to obtain metadata lock
    on it and doing this under LOCK TABLES may lead to deadlocks.

  mysql-test/t/trigger_notembedded.test@stripped, 2008-05-22 13:21:11+04:00,
dlenev@stripped +1 -1
    Adjusted test case to the changes of status in various places caused
    by change in implementation FLUSH TABLES WITH READ LOCK, which is now
    takes global metadata lock before flushing tables and therefore waits
    on at these places.

  mysql-test/t/view.test@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped +1 -3
    One no longer is allowed to do DROP VIEW under LOCK TABLES even if 
    this view is locked by LOCK TABLES. The problem is that in such
    situation even "write locks" on view are not mutually exclusive so
    upgrading metadata lock which is required for dropping of view will
    lead to deadlock.

  mysql-test/t/view_grant.test@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped +2
-2
    ALTER VIEW implementation was changed to open a view only after checking
    that user which does alter has appropriate privileges on it. This means
    that in case when user's privileges are insufficient for this we won't
    check that new view definer is the same as original one or user performing
    alter has SUPER privilege. Adjusted test case accordingly.

  mysql-test/t/view_multi.test@stripped, 2008-05-22 13:21:15+04:00, dlenev@stripped
+110 -0
    Added test case for bug#25144 "replication / binlog with view breaks".

  mysql-test/t/view_multi.test@stripped, 2008-05-22 13:21:15+04:00, dlenev@stripped +0
-0

  sql/Makefile.am@stripped, 2008-05-22 13:21:11+04:00, dlenev@stripped +3 -2
    Added files implementing new meta-data locking subsystem to the server.

  sql/backup/backup_progress.cc@stripped, 2008-05-22 13:21:15+04:00, dlenev@stripped
+8 -0
    Allocate metadata lock requests objects (MDL_LOCK) on execution memory
    root in cases when we use stack TABLE_LIST objects to open tables.

  sql/backup/kernel.cc@stripped, 2008-05-22 13:21:15+04:00, dlenev@stripped +10 -1
    Allocate metadata lock requests objects (MDL_LOCK) on execution memory
    root in cases when we use stack TABLE_LIST objects to open tables.
    Use heap for MDL_LOCK objects in case when TABLE_LIST is allocated on
    heap.

  sql/event_db_repository.cc@stripped, 2008-05-22 13:21:12+04:00, dlenev@stripped +4
-0
    Allocate metadata lock requests objects (MDL_LOCK) on execution memory
    root in cases when TABLE_LIST objects is also allocated there or on
    stack.

  sql/ha_ndbcluster.cc@stripped, 2008-05-22 13:21:12+04:00, dlenev@stripped +20 -3
    Adjusted code to work nicely with new metadata locking subsystem.
    close_cached_tables() no longer has wait_for_placeholder argument.
    Instead of relying on this parameter and related behavior FLUSH TABLES
    WITH READ LOCK now takes global shared metadata lock.

  sql/ha_ndbcluster_binlog.cc@stripped, 2008-05-22 13:21:12+04:00, dlenev@stripped
+25 -14
    Adjusted code to work with new metadata locking subsystem.
    close_cached_tables() no longer has wait_for_placeholder
    argument. Instead of relying on this parameter and related
    behavior FLUSH TABLES WITH READ LOCK now takes global shared
    metadata lock.

  sql/handler.cc@stripped, 2008-05-22 13:21:12+04:00, dlenev@stripped +1 -8
    update_frm_version():
      Directly update TABLE_SHARE::mysql_version member instead of going
      through all TABLE instances for this table (old code was a legacy
      from pre-table-definition-cache days).

  sql/lock.cc@stripped, 2008-05-22 13:21:12+04:00, dlenev@stripped +54 -330
    Use new metadata locking subsystem. Threw away most of functions related
    to name locking as now one is supposed to use metadata locking API instead.
    In lock_global_read_lock() and unlock_global_read_lock() in order to 
    avoid problems with global read lock sneaking in at the moment
    when we perform FLUSH TABLES or ALTER TABLE under LOCK TABLES and
    when tables being reopened are protected only by metadata locks
    we also have to take global shared meta data lock.

  sql/log_event.cc@stripped, 2008-05-22 13:21:12+04:00, dlenev@stripped +12 -14
    Adjusted code to work with new metadata locking subsystem.
    For tables open by slave thread for applying RBR events allocate
    memory for lock request object in the same chunk of memory as
    TABLE_LIST objects for them. In order to ensure that we keep these
    objects around until tables are open always close tables before
    calling Relay_log_info::clear_tables_to_lock(). Use new auxiliary
    Relay_log_info::slave_close_thread_tables() method to enforce this.

  sql/log_event_old.cc@stripped, 2008-05-22 13:21:12+04:00, dlenev@stripped +10 -30
    Adjusted code to work with new metadata locking subsystem.
    Since for tables open by slave thread for applying RBR events memory for
    lock request object is allocated in the same chunk of memory as TABLE_LIST
    objects for them we have to ensure that we keep these objects around until
    tables are open. To ensure this we always close tables before calling
    Relay_log_info::clear_tables_to_lock(). To enfore this we use new
    auxiliary Relay_log_info::slave_close_thread_tables() method.

  sql/mdl.cc@stripped, 2008-05-22 13:21:15+04:00, dlenev@stripped +1342 -0
    Implemented new metadata locking subsystem and API described in
    WL3726 "DDL locking for all metadata objects".

  sql/mdl.cc@stripped, 2008-05-22 13:21:15+04:00, dlenev@stripped +0 -0

  sql/mdl.h@stripped, 2008-05-22 13:21:15+04:00, dlenev@stripped +260 -0
    Implemented new metadata locking subsystem and API described in
    WL3726 "DDL locking for all metadata objects".

  sql/mdl.h@stripped, 2008-05-22 13:21:15+04:00, dlenev@stripped +0 -0

  sql/mysql_priv.h@stripped, 2008-05-22 13:21:12+04:00, dlenev@stripped +23 -30
    - close_thread_tables()/close_tables_for_reopen() now has one more
      argument which indicates that metadata locks should be released
      but not removed from the context in order to be used later in
      mdl_wait_for_locks() and tdc_wait_for_old_version().
    - close_cached_table() routine is no longer public.
    - Thread waiting in wait_while_table_is_used() can be now killed so
      this function returns boolean to make caller aware of such situation.
    - We no longer have  table cache as separate entity instead
      used and unused TABLE instances are linked to TABLE_SHARE
      objects in table definition cache.
    - Now third argument of open_table() is also used for requesting table
      repair or auto-discovery of table's new definition. So its type was
      changed from bool to enum.
    - Added tdc_open_view() function for opening view by getting its
      definition from disk (and table cache in future).
    - reopen_name_locked_table() no longer needs "link_in" argument
      as now we have exclusive metadata locks instead of dummy TABLE
      instances when this function is called.
    - find_locked_table() now takes head of list of TABLE instances
      instead of always scanning through THD::open_tables list. Also
      added find_write_locked_table() auxiliary.
    - reopen_tables(), close_cached_tables() no longer have mark_share_as_old
      and wait_for_placeholder arguments. Instead of relying on this parameters
      and related behavior FLUSH TABLES WITH READ LOCK now takes global shared
      metadata lock.
    - We no longer need drop_locked_tables() and abort_locked_tables().
    - mysql_ha_rm_tables() now always assume that LOCK_open is not acquired
      by caller.
    - Added notify_thread_having_shared_lock() callback invoked by
      metadata locking subsystem when acquiring an exclusive lock,
      for each thread that has a conflicting shared metadata lock.
    - Introduced expel_table_from_cache() as replacement for
      remove_table_from_cache() (the main difference is that this
      new function assumes that caller follows metadata locking
      protocol and never waits).
    - Threw away most of functions related to name locking. One should
      use new metadata locking subsystem and API instead.

  sql/mysqld.cc@stripped, 2008-05-22 13:21:12+04:00, dlenev@stripped +3 -2
    Got rid of call initializing/deinitializing table cache since now it
    is embedded into table definition cache. Added calls for initializing/
    deinitializing metadata locking subsystem.

  sql/rpl_rli.cc@stripped, 2008-05-22 13:21:12+04:00, dlenev@stripped +19 -2
    Introduced auxiliary Relay_log_info::slave_close_thread_tables() method
    which is used for enforcing that we always close tables open for RBR
    before deallocating TABLE_LIST elements and MDL_LOCK objects for them.

  sql/rpl_rli.h@stripped, 2008-05-22 13:21:12+04:00, dlenev@stripped +1 -0
    Introduced auxiliary Relay_log_info::slave_close_thread_tables() method
    which is used for enforcing that we always close tables open for RBR
    before deallocating TABLE_LIST elements and MDL_LOCK objects for them.

  sql/set_var.cc@stripped, 2008-05-22 13:21:12+04:00, dlenev@stripped +1 -1
    close_cached_tables() no longer has wait_for_placeholder
    argument. Instead of relying on this parameter and related
    behavior FLUSH TABLES WITH READ LOCK now takes global shared
    metadata lock.

  sql/si_objects.cc@stripped, 2008-05-22 13:21:12+04:00, dlenev@stripped +2 -0
    Allocate metadata lock requests objects (MDL_LOCK) on execution memory
    root in cases when TABLE_LIST objects is also allocated there or on
    stack.

  sql/sp_head.cc@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +6 -0
    For tables added to the statement's table list by prelocking algorithm
    we allocate these objects either on the same memory as corresponding
    table list elements or on THD::locked_tables_root (if we are building
    table list for LOCK TABLES).

  sql/sql_acl.cc@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +10 -6
    Allocate metadata lock requests objects (MDL_LOCK) on execution memory
    root in cases when we use stack TABLE_LIST objects to open tables.
    Got rid of redundant code by using unlock_locked_tables() function.

  sql/sql_base.cc@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +1234 -1040
    Changed code to use new MDL subsystem. Got rid of separate table cache.
    Now used and unused TABLE instances are linked to the TABLE_SHAREs in
    table definition cache.
    
    check_unused():
      Adjusted code to the fact that we no longer have separate table cache.
      Removed dead code.
    table_def_free():
      Free TABLE instances referenced from TABLE_SHARE objects before destroying
      table definition cache.
    get_table_share():
      Added assert which ensures that noone will be able to access table
      (and its share) without acquiring some kind of metadata lock first.
    close_handle_and_leave_table_as_lock():
      Adjusted code to the fact that TABLE instances now are linked to
      list in TABLE_SHARE.
    list_open_tables():
      Changed this function to use table definition cache instead of
      table cache.
    free_cache_entry():
      Unlink freed TABLE elements from the list of all TABLE instances for
      the table in TABLE_SHARE.
    kill_delayed_thread_for_table():
      Added auxiliary for killing delayed insert threads for particular table.
    close_cached_tables():
      Got rid of wait_for_refresh argument as we now rely on global shared
      metadata lock to prevent FLUSH WITH READ LOCK sneaking in when we are
      reopening tables. Heavily reworked this function to use new MDL code
      and not to rely on separate table cache entity.
    close_open_tables():
      We no longer have separate table cache.
    close_thread_tables():
      Release metadata locks after closing all tables. Added skip_mdl
      argument which allows us not to remove metadata lock requests
      from the context in case when we are going to use this requests
      later in mdl_wait_for_locks() and tdc_wait_for_old_versions(). 
    close_thread_table()/close_table_for_reopen():
      Since we no longer have separate table cache and all TABLE instances
      are linked to TABLE_SHARE objects in table definition cache we have
      to link/unlink TABLE object to/from appropriate lists in the share.
    name_lock_locked_table():
      Moved redundant code to find_write_locked_table() function and
      adjusted code to the fact that wait_while_table_is_used() can
      now return with an error if our thread is killed.
    reopen_table_entry():
      We no longer need "link_in" argument as with MDL we no longer
      call this function with dummy TABLE object pre-allocated and
      added to the THD::open_tables. Also now we add newly-open TABLE
      instance to the list of share's used TABLE instances.
    table_cache_insert_placeholder():
      Got rid of name-locking legacy.
    lock_table_name_if_not_cached():
      Moved to sql_table.cc the only place where it is used. It was
      also reimplemented using new MDL API.
    open_table():
      - Reworked this function to use new MDL subsystem.
      - Changed code to deal with table definition cache directly
        instead of going through separate table cache.
      - Now third argument is also used for requesting table repair
        or auto-discovery of table's new definition. So its type was
        changed from bool to enum.
    find_locked_table()/find_write_locked_table():
      Accept head of list of TABLE objects as first argument and use this
      list instead of always searching in THD::open_tables list.
      Also added auxiliary for finding write-locked locked tables.
    reopen_table():
      Adjusted function to work with new MDL subsystem and to properly
      manuipulate with lists of used/unused TABLE instaces in TABLE_SHARE.
    reopen_tables():
      Removed mark_share_as_old parameter. Instead of relying on it and
      related behavior FLUSH TABLES WITH READ LOCK now takes global shared
      metadata lock. Changed code after removing separate table cache.
    drop_locked_tables()/abort_locked_tables():
      Got rid of functions which are no longer needed.
    unlock_locked_tables():
      Moved this function from sql_parse.cc and changed it to release
      memory which was used for allocating metadata lock requests for
      tables open and locked by LOCK TABLES.
    tdc_open_view():
      Intoduced function for opening a view by getting its definition
      from disk (and table cache in future).
    reopen_table_entry():
      Introduced function for opening table definitions while holding
      exclusive metatadata lock on it.
    open_unireg_entry():
      Got rid of this function. Most of its functionality is relocated
      to open_table() and open_table_fini() functions, and some of it to
      reopen_table_entry() and tdc_open_view(). Also code resposible for
      auto-repair and auto-discovery of tables was moved to separate function.
    open_table_entry_fini():
      Introduced function which contains common actions which finalize
      process of TABLE object creation.
    auto_repair_table():
      Moved code responsible for auto-repair of table being opened here.
    handle_failed_open_table_attempt()
      Moved code responsible for handling failing attempt to open table
      to one place (retry due to lock conflict/old version, auto-discovery
      and repair). 
    open_tables():
      - Flush open HANDLER tables if they have old version of if there is
        conflicting metadata lock against them (before this moment we had
        this code in open_table()).
      - When we open view which should be processed via derived table on the
        second execution of prepared statement or stored routine we still
        should call open_table() for it in order to obtain metadata lock
        on it and prepare its security context.
      - In cases when we discover that some special handling of failure to
        open table is needed call handle_failed_open_table_attempt() which
        handles all such scenarios.
    open_ltable():
      Handling of various special scenarios of failure to open a table was
      moved to separate handle_failed_open_table_attempt() function.
    remove_db_from_cache():
      Removed this function as it is no longer used.
    notify_thread_having_shared_lock():
      Added callback which is invoked by MDL subsystem when acquiring an
      exclusive lock, for each thread that has a conflicting shared metadata
      lock.
    expel_table_from_cache():
      Introduced function for removing unused TABLE instances. Unlike
      remove_table_from_cache() it relies on caller following MDL
      protocol and having appropriate locks when calling it and
      thus does not do any waiting if table is still in use.
    tdc_wait_for_old_version():
      Added function which allows open_tables() to wait in cases
      when we discover that we should back-off due to presence of
      old version of table.
    abort_and_upgrade_lock():
      Use new MDL calls.
    mysql_wait_completed_table():
      Got rid of unused function.
    open_system_tables_for_read/for_update()/performance_schema_table():
      Allocate MDL_LOCK objects on execution memory root in cases when
      TABLE_LIST objects for corresponding tables is allocated on stack.
    close_performance_schema_table():
      Release metadata locks after closing tables.

  sql/sql_binlog.cc@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +1 -1
    Use Relay_log_info::slave_close_thread_tables() method to enforce that
    we always close tables open for RBR before deallocating TABLE_LIST
    elements and MDL_LOCK objects for them.

  sql/sql_class.cc@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +15 -5
    Added meta-data locking contexts as part of Open_tables_state context.
    Also introduced THD::locked_tables_root memory root which is to be
    used for allocating MDL_LOCK objects for tables in LOCK TABLES statement
    (end of lifetime for such objects is UNLOCK TABLES so we can't use 
    statement or execution root for them). 

  sql/sql_class.h@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +11 -2
    Added meta-data locking contexts as part of Open_tables_state context.
    Also introduced THD::locked_tables_root memory root which is to be
    used for allocating MDL_LOCK objects for tables in LOCK TABLES statement
    (end of lifetime for such objects is UNLOCK TABLES so we can't use 
    statement or execution root for them). 

  sql/sql_db.cc@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +0 -5
    mysql_rm_db() does not really need to call remove_db_from_cache()
    as it drops each table in the database using mysql_rm_table_part2(),
    which performs all necessary operations on table (definition) cache.

  sql/sql_delete.cc@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +18 -7
    Use new metadata locking subsystem in implementation of TRUNCATE.

  sql/sql_handler.cc@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +81 -59
    Changed HANDLER implementation to use new metadata locking subsystem.
    Note that MDL_LOCK objects for HANDLER tables are allocated in the
    same chunk of heap memory as TABLE_LIST object for those tables.

  sql/sql_insert.cc@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +8 -5
    mysql_insert():
      find_locked_table() now takes head of list of TABLE object as its argument
      instead of always scanning through THD::open_tables list.
    handle_delayed_insert():
      Allocate metadata lock request object for table open by delayed insert
      thread on execution memroot. 
    create_table_from_items():
      We no longer allocate dummy TABLE objects for tables being created if
      they don't exist. As consequence reopen_name_locked_table() no longer
      has link_in argument.
      open_table() now has one more argument which is not relevant for
      temporary tables.

  sql/sql_parse.cc@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +86 -34
    - Moved unlock_locked_tables() routine to sql_base.cc and made available
      it in other files. Got rid of some redundant code by using this function.
    - Replaced boolean TABLE_LIST::create member with enum open_table_type
      member.
    - Use special memory root for allocating MDL_LOCK objects for tables
      open and locked by LOCK TABLES (these object should live till UNLOCK
      TABLES so we can't allocate them on statement nor execution memory
      root). Also properly set metadata lock upgradability attribure for
      those tables. 
    - Under LOCK TABLES it is no longer allowed to flush tables which are
      not write-locked as this breaks metadata locking protocol and thus
      potentially might lead to deadlock.
    - Added auxiliary adjust_mdl_locks_upgradability() function.

  sql/sql_partition.cc@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +5 -5
    Adjusted code to the fact that reopen_tables() no longer has 
    "mark_share_as_old" argument. Got rid of comments which are
    no longer true.

  sql/sql_plist.h@stripped, 2008-05-22 13:21:15+04:00, dlenev@stripped +120 -0
    Added I_P_List template class for parametrized intrusive doubly linked
    lists and I_P_List_iterator for corresponding iterator. Unlike for I_List<>
    list elements of such list can participate in several lists. Unlike List<>
    such lists are dobly-linked and intrusive.

  sql/sql_plist.h@stripped, 2008-05-22 13:21:15+04:00, dlenev@stripped +0 -0

  sql/sql_plugin.cc@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +4 -0
    Allocate metadata lock requests objects (MDL_LOCK) on execution memory
    root in cases when we use stack TABLE_LIST objects to open tables.

  sql/sql_prepare.cc@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +1 -1
    Replaced boolean TABLE_LIST::create member with enum open_table_type member.
    This allows easily handle situation in which instead of opening the table
    we want only to take exclusive metadata lock on it.

  sql/sql_rename.cc@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +8 -9
    Use new metadata locking subsystem in implementation of RENAME TABLE.

  sql/sql_servers.cc@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +5 -6
    Allocate metadata lock requests objects (MDL_LOCK) on execution memory
    root in cases when we use stack TABLE_LIST objects to open tables.
    Got rid of redundant code by using unlock_locked_tables() function.

  sql/sql_show.cc@stripped, 2008-05-22 13:21:13+04:00, dlenev@stripped +45 -11
    Acquire shared metadata lock when we are getting information for I_S
    table directly from TABLE_SHARE without doing full-blown table open.
    We use high priority lock request in this situation in order to avoid
    deadlocks.
    Also allocate metadata lock requests objects (MDL_LOCK) on execution
    memory root in cases when TABLE_LIST objects are also allocated
    there.

  sql/sql_table.cc@stripped, 2008-05-22 13:21:14+04:00, dlenev@stripped +245 -187
    mysql_rm_table():
      Removed comment which is no longer relevant.
    mysql_rm_table_part2():
      Now caller of mysql_ha_rm_tables() should not own LOCK_open.
      Adjusted code to use new metadata locking subsystem instead of
      name-locks.
    lock_table_name_if_not_cached():
      Moved this function from sql_base.cc to this file and reimplemented
      it using metadata locking API.
    mysql_create_table():
      Adjusted code to use new MDL API.
    wait_while_table_is_used():
      Changed function to use new MDL subsystem. Made thread waiting in
      it killable (this also led to introduction of return value so
      caller can distinguish successful executions from situations when
      waiting was aborted).
    close_cached_tables():
      Thread waiting in this function is killable now. As result it has
      return value for distinguishing between succes and failure.
      Got rid of redundant boradcast_refresh() call.
    prepare_for_repair():
      Use MDL subsystem instead of name-locks.
    mysql_admin_table():
      mysql_ha_rm_tables() now always assumes that caller doesn't own LOCK_open.
    mysql_repair_table():
      We should mark all elements of table list as requiring upgradable metadata
      locks.
    mysql_create_table_like():
      Use new MDL subsystem instead of name-locks.
    create_temporary_tables():
      We don't need to obtain metadata locks when creating temporary table.
    mysql_fast_or_online_alter_table():
      Thread waiting in wait_while_table_is_used() is now killable.
    mysql_alter_table():
      Adjusted code to work with new MDL subsystem and to the fact that
      threads waiting in what_while_table_is_used() and close_cached_table()
      are now killable.

  sql/sql_test.cc@stripped, 2008-05-22 13:21:14+04:00, dlenev@stripped +23 -14
    We no longer have separate table cache. TABLE instances are now
    associated with/linked to TABLE_SHARE objects in table definition
    cache.

  sql/sql_trigger.cc@stripped, 2008-05-22 13:21:14+04:00, dlenev@stripped +25 -21
    Adjusted code to work with new metadata locking subsystem.
    Also reopen_tables() no longer has mark_share_as_old argument
    (Instead of relying on this parameter and related behavior
    FLUSH TABLES WITH READ LOCK now takes global shared metadata
    lock).

  sql/sql_udf.cc@stripped, 2008-05-22 13:21:14+04:00, dlenev@stripped +3 -0
    Allocate metadata lock requests objects (MDL_LOCK) on execution memory
    root in cases when we use stack TABLE_LIST objects to open tables.

  sql/sql_update.cc@stripped, 2008-05-22 13:21:14+04:00, dlenev@stripped +2 -2
    Adjusted code to work with new meta-data locking subsystem.

  sql/sql_view.cc@stripped, 2008-05-22 13:21:14+04:00, dlenev@stripped +54 -38
    Added proper meta-data locking to implementations of CREATE/ALTER/DROP VIEW
    statements. Now we obtain exclusive meta-data lock on a view before creating/
    changing/dropping it. This ensures that all concurrent statements that use
    this view will finish before our statement will proceed and therefore we
    will get correct order of statements in the binary log.
    Also ensure that TABLE_LIST::mdl_upgradable attribute is properly propagated
    for underlying tables of view.

  sql/table.cc@stripped, 2008-05-22 13:21:14+04:00, dlenev@stripped +14 -0
    Added auxiliary alloc_mdl_locks() function for allocating metadata lock
    request objects for all elements of table list.

  sql/table.h@stripped, 2008-05-22 13:21:15+04:00, dlenev@stripped +39 -13
    TABLE_SHARE:
      Got rid of unused members. Introduced members for storing lists of used
      and unused TABLE objects for this share.
    TABLE:
      Added members for linking TABLE objects into per-share lists of used
      and unused TABLE instances. Added member for holding pointer to metadata
      lock for this table.
    TABLE_LIST:
      Replaced boolean TABLE_LIST::create member with enum open_table_type member.
      This allows easily handle situation in which instead of opening the table
      we want only to take exclusive meta-data lock on it (we need this in order
      to handle ALTER VIEW and CREATE VIEW statements).
      Introduced new mdl_upgradable member for marking elements
      of table list for which we need to take upgradable shared metadata
      lock instead of plain shared metadata lock.
      Added pointer for holding pointer to MDL_LOCK for the table.
    Added auxiliary alloc_mdl_locks() function for allocating metadata lock
    requests objects for all elements of table list.
    Added auxiliary set_all_mdl_upgradable() function for marking all
    elements in table list as requiring upgradable metadata locks.

  storage/myisammrg/ha_myisammrg.cc@stripped, 2008-05-22 13:21:15+04:00,
dlenev@stripped +6 -0
    Allocate MDL_LOCK objects for underlying tables of MERGE table.
    To be reworked once Ingo pushes his patch for WL4144.

diff -Nrup a/libmysqld/Makefile.am b/libmysqld/Makefile.am
--- a/libmysqld/Makefile.am	2008-03-28 01:40:58 +03:00
+++ b/libmysqld/Makefile.am	2008-05-22 13:21:10 +04:00
@@ -82,7 +82,8 @@ sqlsources = derror.cc field.cc field_co
 	rpl_filter.cc sql_partition.cc sql_builtin.cc sql_plugin.cc \
 	sql_tablespace.cc \
 	rpl_injector.cc my_user.c partition_info.cc \
-	sql_servers.cc ddl_blocker.cc si_objects.cc sql_audit.cc
+	sql_servers.cc ddl_blocker.cc si_objects.cc sql_audit.cc \
+	mdl.cc
 
 libmysqld_int_a_SOURCES= $(libmysqld_sources)
 nodist_libmysqld_int_a_SOURCES= $(libmysqlsources) $(sqlsources)
diff -Nrup a/mysql-test/include/handler.inc b/mysql-test/include/handler.inc
--- a/mysql-test/include/handler.inc	2007-11-20 20:17:51 +03:00
+++ b/mysql-test/include/handler.inc	2008-05-22 13:21:10 +04:00
@@ -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
diff -Nrup a/mysql-test/include/locktrans.inc b/mysql-test/include/locktrans.inc
--- a/mysql-test/include/locktrans.inc	2007-10-03 04:27:21 +04:00
+++ b/mysql-test/include/locktrans.inc	2008-05-22 13:21:11 +04:00
@@ -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 #
diff -Nrup a/mysql-test/r/create.result b/mysql-test/r/create.result
--- a/mysql-test/r/create.result	2008-03-27 21:58:36 +03:00
+++ b/mysql-test/r/create.result	2008-05-22 13:21:11 +04:00
@@ -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
diff -Nrup a/mysql-test/r/flush.result b/mysql-test/r/flush.result
--- a/mysql-test/r/flush.result	2008-03-23 19:34:24 +03:00
+++ b/mysql-test/r/flush.result	2008-05-22 13:21:11 +04:00
@@ -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;
diff -Nrup a/mysql-test/r/flush_table.result b/mysql-test/r/flush_table.result
--- a/mysql-test/r/flush_table.result	2006-10-05 01:48:21 +04:00
+++ b/mysql-test/r/flush_table.result	2008-05-22 13:21:11 +04:00
@@ -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;
diff -Nrup a/mysql-test/r/handler_innodb.result b/mysql-test/r/handler_innodb.result
--- a/mysql-test/r/handler_innodb.result	2007-11-20 20:17:51 +03:00
+++ b/mysql-test/r/handler_innodb.result	2008-05-22 13:21:11 +04:00
@@ -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;
diff -Nrup a/mysql-test/r/handler_myisam.result b/mysql-test/r/handler_myisam.result
--- a/mysql-test/r/handler_myisam.result	2007-11-20 20:17:51 +03:00
+++ b/mysql-test/r/handler_myisam.result	2008-05-22 13:21:11 +04:00
@@ -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;
diff -Nrup a/mysql-test/r/information_schema.result
b/mysql-test/r/information_schema.result
--- a/mysql-test/r/information_schema.result	2008-04-20 00:34:53 +04:00
+++ b/mysql-test/r/information_schema.result	2008-05-22 13:21:11 +04:00
@@ -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;
diff -Nrup a/mysql-test/r/kill.result b/mysql-test/r/kill.result
--- a/mysql-test/r/kill.result	2008-03-27 21:58:36 +03:00
+++ b/mysql-test/r/kill.result	2008-05-22 13:21:11 +04:00
@@ -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;
diff -Nrup a/mysql-test/r/lock.result b/mysql-test/r/lock.result
--- a/mysql-test/r/lock.result	2007-10-03 04:27:22 +04:00
+++ b/mysql-test/r/lock.result	2008-05-22 13:21:11 +04:00
@@ -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
diff -Nrup a/mysql-test/r/locktrans_innodb.result b/mysql-test/r/locktrans_innodb.result
--- a/mysql-test/r/locktrans_innodb.result	2008-04-02 13:33:18 +04:00
+++ b/mysql-test/r/locktrans_innodb.result	2008-05-22 13:21:11 +04:00
@@ -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.
diff -Nrup a/mysql-test/r/locktrans_myisam.result b/mysql-test/r/locktrans_myisam.result
--- a/mysql-test/r/locktrans_myisam.result	2008-04-14 14:09:56 +04:00
+++ b/mysql-test/r/locktrans_myisam.result	2008-05-22 13:21:11 +04:00
@@ -43,6 +43,7 @@ Warning	1613	Converted to non-transactio
 LOCK TABLE v1 IN EXCLUSIVE MODE;
 Warnings:
 Warning	1613	Converted to non-transactional lock on 'v1'
+UNLOCK TABLES;
 DROP VIEW v1;
 #
 # Locking INFORMATION_SCHEMA fails on missing privileges.
diff -Nrup a/mysql-test/r/sp.result b/mysql-test/r/sp.result
--- a/mysql-test/r/sp.result	2008-03-27 12:56:01 +03:00
+++ b/mysql-test/r/sp.result	2008-05-22 13:21:11 +04:00
@@ -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()|
diff -Nrup a/mysql-test/r/view.result b/mysql-test/r/view.result
--- a/mysql-test/r/view.result	2008-04-14 14:09:57 +04:00
+++ b/mysql-test/r/view.result	2008-05-22 13:21:11 +04:00
@@ -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;
diff -Nrup a/mysql-test/r/view_grant.result b/mysql-test/r/view_grant.result
--- a/mysql-test/r/view_grant.result	2008-03-22 11:02:23 +03:00
+++ b/mysql-test/r/view_grant.result	2008-05-22 13:21:11 +04:00
@@ -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;
diff -Nrup a/mysql-test/r/view_multi.result b/mysql-test/r/view_multi.result
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/mysql-test/r/view_multi.result	2008-05-22 13:21:15 +04:00
@@ -0,0 +1,48 @@
+reset master;
+create table t1 (i int);
+create table t2 (i int);
+create view v1 as select * from t1;
+select get_lock("lock_bg25144", 1);
+get_lock("lock_bg25144", 1)
+1
+insert into v1 values (get_lock("lock_bg25144", 100));;
+drop view v1;;
+select release_lock("lock_bg25144");
+release_lock("lock_bg25144")
+1
+select release_lock("lock_bg25144");
+release_lock("lock_bg25144")
+1
+select * from t1;
+i
+1
+create view v1 as select * from t1;
+select get_lock("lock_bg25144", 1);
+get_lock("lock_bg25144", 1)
+1
+insert into v1 values (get_lock("lock_bg25144", 100));;
+alter view v1 as select * from t2;;
+select release_lock("lock_bg25144");
+release_lock("lock_bg25144")
+1
+select release_lock("lock_bg25144");
+release_lock("lock_bg25144")
+1
+select * from t1;
+i
+1
+1
+select * from t2;
+i
+show binlog events in 'master-bin.000001' from 107;
+Log_name	Pos	Event_type	Server_id	End_log_pos	Info
+master-bin.000001	#	Query	1	#	use `test`; create table t1 (i int)
+master-bin.000001	#	Query	1	#	use `test`; create table t2 (i int)
+master-bin.000001	#	Query	1	#	use `test`; CREATE ALGORITHM=UNDEFINED
DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select * from t1
+master-bin.000001	#	Query	1	#	use `test`; insert into v1 values (get_lock("lock_bg25144",
100))
+master-bin.000001	#	Query	1	#	use `test`; drop view v1
+master-bin.000001	#	Query	1	#	use `test`; CREATE ALGORITHM=UNDEFINED
DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select * from t1
+master-bin.000001	#	Query	1	#	use `test`; insert into v1 values (get_lock("lock_bg25144",
100))
+master-bin.000001	#	Query	1	#	use `test`; ALTER ALGORITHM=UNDEFINED
DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select * from t2
+drop table t1, t2;
+drop view v1;
diff -Nrup a/mysql-test/suite/rpl/r/rpl_locktrans_innodb.result
b/mysql-test/suite/rpl/r/rpl_locktrans_innodb.result
--- a/mysql-test/suite/rpl/r/rpl_locktrans_innodb.result	2008-04-02 13:33:18 +04:00
+++ b/mysql-test/suite/rpl/r/rpl_locktrans_innodb.result	2008-05-22 13:21:11 +04:00
@@ -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.
diff -Nrup a/mysql-test/suite/rpl/r/rpl_locktrans_myisam.result
b/mysql-test/suite/rpl/r/rpl_locktrans_myisam.result
--- a/mysql-test/suite/rpl/r/rpl_locktrans_myisam.result	2008-04-14 14:09:57 +04:00
+++ b/mysql-test/suite/rpl/r/rpl_locktrans_myisam.result	2008-05-22 13:21:11 +04:00
@@ -49,6 +49,7 @@ Warning	1613	Converted to non-transactio
 LOCK TABLE v1 IN EXCLUSIVE MODE;
 Warnings:
 Warning	1613	Converted to non-transactional lock on 'v1'
+UNLOCK TABLES;
 DROP VIEW v1;
 #
 # Locking INFORMATION_SCHEMA fails on missing privileges.
diff -Nrup a/mysql-test/t/create.test b/mysql-test/t/create.test
--- a/mysql-test/t/create.test	2008-03-27 21:58:36 +03:00
+++ b/mysql-test/t/create.test	2008-05-22 13:21:11 +04:00
@@ -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
diff -Nrup a/mysql-test/t/flush.test b/mysql-test/t/flush.test
--- a/mysql-test/t/flush.test	2008-03-23 19:34:57 +03:00
+++ b/mysql-test/t/flush.test	2008-05-22 13:21:11 +04:00
@@ -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;
 
diff -Nrup a/mysql-test/t/flush_table.test b/mysql-test/t/flush_table.test
--- a/mysql-test/t/flush_table.test	2006-01-04 13:18:53 +03:00
+++ b/mysql-test/t/flush_table.test	2008-05-22 13:21:11 +04:00
@@ -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;
diff -Nrup a/mysql-test/t/information_schema.test b/mysql-test/t/information_schema.test
--- a/mysql-test/t/information_schema.test	2008-04-20 00:34:53 +04:00
+++ b/mysql-test/t/information_schema.test	2008-05-22 13:21:11 +04:00
@@ -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;
diff -Nrup a/mysql-test/t/kill.test b/mysql-test/t/kill.test
--- a/mysql-test/t/kill.test	2008-03-27 21:58:37 +03:00
+++ b/mysql-test/t/kill.test	2008-05-22 13:21:11 +04:00
@@ -328,6 +328,232 @@ 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";
+--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";
+--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";
+--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";
+--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";
+--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";
+--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";
+--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";
+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)";
+--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";
+--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";
+--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.
diff -Nrup a/mysql-test/t/lock.test b/mysql-test/t/lock.test
--- a/mysql-test/t/lock.test	2007-10-03 04:27:23 +04:00
+++ b/mysql-test/t/lock.test	2008-05-22 13:21:11 +04:00
@@ -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
diff -Nrup a/mysql-test/t/lock_multi.test b/mysql-test/t/lock_multi.test
--- a/mysql-test/t/lock_multi.test	2008-03-05 13:19:37 +03:00
+++ b/mysql-test/t/lock_multi.test	2008-05-22 13:21:11 +04:00
@@ -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;
diff -Nrup a/mysql-test/t/sp.test b/mysql-test/t/sp.test
--- a/mysql-test/t/sp.test	2008-03-27 12:56:02 +03:00
+++ b/mysql-test/t/sp.test	2008-05-22 13:21:11 +04:00
@@ -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|
 
diff -Nrup a/mysql-test/t/trigger_notembedded.test b/mysql-test/t/trigger_notembedded.test
--- a/mysql-test/t/trigger_notembedded.test	2007-12-14 00:18:51 +03:00
+++ b/mysql-test/t/trigger_notembedded.test	2008-05-22 13:21:11 +04:00
@@ -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;
diff -Nrup a/mysql-test/t/view.test b/mysql-test/t/view.test
--- a/mysql-test/t/view.test	2008-04-14 14:09:57 +04:00
+++ b/mysql-test/t/view.test	2008-05-22 13:21:11 +04:00
@@ -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;
 
 #
diff -Nrup a/mysql-test/t/view_grant.test b/mysql-test/t/view_grant.test
--- a/mysql-test/t/view_grant.test	2008-03-04 20:35:39 +03:00
+++ b/mysql-test/t/view_grant.test	2008-05-22 13:21:11 +04:00
@@ -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;
diff -Nrup a/mysql-test/t/view_multi.test b/mysql-test/t/view_multi.test
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/mysql-test/t/view_multi.test	2008-05-22 13:21:15 +04:00
@@ -0,0 +1,110 @@
+#
+# QQ: Should we find a better place for this test?
+#     May be binlog or rpl suites ?
+#
+--source include/have_log_bin.inc
+--source include/have_binlog_format_mixed_or_statement.inc
+
+#
+# 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.
+#
+--disable_parsing
+drop table if not exists t1, t2;
+drop view if exists v1;
+--enable_parsing
+
+# We are going to use binary log later to check that statements are
+# logged in proper order, so it is good idea to reset it here.
+reset master;
+
+connect (addconn1,localhost,root,,);
+connect (addconn2,localhost,root,,);
+connection default;
+
+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);
+
+connection addconn1;
+--send insert into v1 values (get_lock("lock_bg25144", 100));
+
+connection addconn2;
+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;
+
+connection default;
+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");
+
+connection addconn1;
+--reap
+select release_lock("lock_bg25144");
+
+connection addconn2;
+--reap
+
+connection default;
+# Check that insertion through view did happen.
+select * from t1;
+# At the end of test we will check that statements were
+# logged in proper order.
+
+# 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);
+
+connection addconn1;
+--send insert into v1 values (get_lock("lock_bg25144", 100));
+
+connection addconn2;
+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;
+
+connection default;
+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");
+
+connection addconn1;
+--reap
+select release_lock("lock_bg25144");
+
+connection addconn2;
+--reap
+
+connection default;
+
+# Second insertion should go to t1 as well.
+select * from t1;
+select * from t2;
+
+# Now let us check that statements were logged in proper order
+--replace_column 2 # 5 #
+show binlog events in 'master-bin.000001' from 107;
+
+drop table t1, t2;
+drop view v1;
diff -Nrup a/sql/Makefile.am b/sql/Makefile.am
--- a/sql/Makefile.am	2008-03-28 01:40:59 +03:00
+++ b/sql/Makefile.am	2008-05-22 13:21:11 +04:00
@@ -88,7 +88,8 @@ noinst_HEADERS =	item.h item_func.h item
 			event_data_objects.h event_scheduler.h \
 			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
+			contributors.h sql_servers.h ddl_blocker.h \
+			si_objects.h sql_plist.h mdl.h
 
 mysqld_SOURCES =	sql_lex.cc sql_handler.cc sql_partition.cc \
 			item.cc item_sum.cc item_buff.cc item_func.cc \
@@ -133,7 +134,7 @@ mysqld_SOURCES =	sql_lex.cc sql_handler.
 			sql_plugin.cc sql_binlog.cc \
 			sql_builtin.cc sql_tablespace.cc partition_info.cc \
 			sql_servers.cc sql_audit.cc sha2.cc \
-			ddl_blocker.cc si_objects.cc
+			ddl_blocker.cc si_objects.cc mdl.cc
 
 if HAVE_DTRACE
   mysqld_SOURCES += probes.d
diff -Nrup a/sql/backup/backup_progress.cc b/sql/backup/backup_progress.cc
--- a/sql/backup/backup_progress.cc	2008-03-21 18:13:01 +03:00
+++ b/sql/backup/backup_progress.cc	2008-05-22 13:21:15 +04:00
@@ -55,6 +55,7 @@ my_bool check_ob_progress_tables(THD *th
 
   /* Check mysql.online_backup */
   tables.init_one_table("mysql", "online_backup", TL_READ);
+  alloc_mdl_locks(&tables, thd->mem_root);
   if (simple_open_n_lock_tables(thd, &tables))
   {
     ret= TRUE;
@@ -65,6 +66,7 @@ my_bool check_ob_progress_tables(THD *th
 
   /* Check mysql.online_backup_progress */
   tables.init_one_table("mysql", "online_backup_progress", TL_READ);
+  alloc_mdl_locks(&tables, thd->mem_root);
   if (simple_open_n_lock_tables(thd, &tables))
   {
     ret= TRUE;
@@ -165,6 +167,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 +321,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 +471,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)))
diff -Nrup a/sql/backup/kernel.cc b/sql/backup/kernel.cc
--- a/sql/backup/kernel.cc	2008-04-20 00:45:03 +04:00
+++ b/sql/backup/kernel.cc	2008-05-22 13:21:15 +04:00
@@ -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 *mdl_lock;
+    char *keybuff;
+
+    my_multi_malloc(MYF(MY_WME), &ptr, sizeof(TABLE_LIST),
+                    &mdl_lock, sizeof(MDL_LOCK), &keybuff, MAX_DBKEY_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, keybuff, 0, ptr->db, ptr->table_name);
+    ptr->mdl_lock= mdl_lock;
 
     // and add it to the list
 
diff -Nrup a/sql/event_db_repository.cc b/sql/event_db_repository.cc
--- a/sql/event_db_repository.cc	2008-02-07 13:47:37 +03:00
+++ b/sql/event_db_repository.cc	2008-05-22 13:21:12 +04:00
@@ -553,6 +553,7 @@ Event_db_repository::open_event_table(TH
   DBUG_ENTER("Event_db_repository::open_event_table");
 
   tables.init_one_table("mysql", "event", lock_type);
+  alloc_mdl_locks(&tables, thd->mem_root);
 
   if (simple_open_n_lock_tables(thd, &tables))
   {
@@ -1095,6 +1096,7 @@ Event_db_repository::check_system_tables
 
   /* Check mysql.db */
   tables.init_one_table("mysql", "db", TL_READ);
+  alloc_mdl_locks(&tables, thd->mem_root);
 
   if (simple_open_n_lock_tables(thd, &tables))
   {
@@ -1112,6 +1114,7 @@ Event_db_repository::check_system_tables
   }
   /* Check mysql.user */
   tables.init_one_table("mysql", "user", TL_READ);
+  alloc_mdl_locks(&tables, thd->mem_root);
 
   if (simple_open_n_lock_tables(thd, &tables))
   {
@@ -1132,6 +1135,7 @@ Event_db_repository::check_system_tables
   }
   /* Check mysql.event */
   tables.init_one_table("mysql", "event", TL_READ);
+  alloc_mdl_locks(&tables, thd->mem_root);
 
   if (simple_open_n_lock_tables(thd, &tables))
   {
diff -Nrup a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc
--- a/sql/ha_ndbcluster.cc	2008-04-01 17:44:51 +04:00
+++ b/sql/ha_ndbcluster.cc	2008-05-22 13:21:12 +04:00
@@ -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
     VOID(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)
     VOID(pthread_mutex_unlock(&LOCK_open));    
 
diff -Nrup a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc
--- a/sql/ha_ndbcluster_binlog.cc	2008-02-26 19:54:30 +03:00
+++ b/sql/ha_ndbcluster_binlog.cc	2008-05-22 13:21:12 +04:00
@@ -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   binlog_mdl_lock;
+static char       binlog_mdlkey[MAX_DBKEY_LENGTH];
 
 /*
   Helper functions
@@ -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, binlog_mdlkey, 0, tables->db,
+                tables->table_name);
+  tables->mdl_lock= &binlog_mdl_lock;
   THD_SET_PROC_INFO(thd, "Opening " NDB_REP_DB "." NDB_REP_TABLE);
   tables->required_type= FRMTYPE_TABLE;
   uint counter;
@@ -2546,7 +2557,7 @@ 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;
@@ -2557,7 +2568,7 @@ ndb_add_ndb_binlog_index(THD *thd, ndb_b
       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;
       }
@@ -4444,13 +4455,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;
diff -Nrup a/sql/handler.cc b/sql/handler.cc
--- a/sql/handler.cc	2008-04-07 19:26:37 +04:00
+++ b/sql/handler.cc	2008-05-22 13:21:12 +04:00
@@ -2825,20 +2825,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)
diff -Nrup a/sql/lock.cc b/sql/lock.cc
--- a/sql/lock.cc	2008-04-01 17:44:52 +04:00
+++ b/sql/lock.cc	2008-05-22 13:21:12 +04:00
@@ -940,361 +940,56 @@ static MYSQL_LOCK *get_lock_data(THD *th
 *****************************************************************************/
 
 /**
-  Lock and wait for the named lock.
+   Obtain exclusive metadata locks on the list of tables.
 
-  @param thd			Thread handler
-  @param table_list		Lock first table in this list
+   @param thd         Thread handle
+   @param table_list  List of tables to 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
-    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);
-  VOID(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.
-
-  @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
-
-  @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 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 *mdl_lock;
 
   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= mdl_alloc_lock(0, lock_table->db, lock_table->table_name,
+                                   thd->mem_root)))
+      goto end;
+    mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE);
+    mdl_add_lock(&thd->mdl_context, mdl_lock);
   }
-
-  /* If some table was in use, wait until we got the lock */
-  if (!got_all_locks && wait_for_locked_table_names(thd, table_list))
-    goto end;
+  if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+    return 1;
   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.
-
-  @param thd        Thread handle.
-  @param table_list Names of tables to lock.
-
-  @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.
-*/
-
-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.
+   Release all metadata locks previously obtained by lock_table_names().
 
-  @param[in] thd        The current thread handler.
-  @param[in] key
-  @param[in] key_length
+   @param thd  Thread handle.
 
-  @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 ?)
+   @note Cannot be called while holding LOCK_open mutex.
 */
 
-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 +1135,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 +1183,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)
@@ -1676,7 +1400,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);
diff -Nrup a/sql/log_event.cc b/sql/log_event.cc
--- a/sql/log_event.cc	2008-04-14 14:09:58 +04:00
+++ b/sql/log_event.cc	2008-05-22 13:21:12 +04:00
@@ -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);
   }
@@ -6492,7 +6491,7 @@ int Rows_log_event::do_apply_event(Relay
                       "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);
       }
 
@@ -6511,7 +6510,7 @@ int Rows_log_event::do_apply_event(Relay
        */
       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)))
@@ -6529,7 +6528,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(error);
       }
     }
@@ -6551,7 +6550,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);
         }
       }
@@ -6748,12 +6747,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;
   
@@ -7294,7 +7287,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 *mdl_lock;
   size_t dummy_len;
   void *memory;
   DBUG_ENTER("Table_map_log_event::do_apply_event(Relay_log_info*)");
@@ -7309,6 +7303,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, sizeof(MDL_LOCK),
+                                &mdlkey, MAX_DBKEY_LENGTH,
                                 NullS)))
     DBUG_RETURN(HA_ERR_OUT_OF_MEM);
 
@@ -7321,6 +7317,8 @@ 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, mdlkey, 0, table_list->db, table_list->table_name);
+  table_list->mdl_lock= mdl_lock;
 
   int error= 0;
 
diff -Nrup a/sql/log_event_old.cc b/sql/log_event_old.cc
--- a/sql/log_event_old.cc	2008-03-25 18:10:47 +03:00
+++ b/sql/log_event_old.cc	2008-05-22 13:21:12 +04:00
@@ -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);
   }
@@ -88,7 +87,7 @@ Old_rows_log_event::do_apply_event(Old_r
                       "Error in %s event: when locking tables",
                       ev->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);
       }
 
@@ -107,7 +106,7 @@ Old_rows_log_event::do_apply_event(Old_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)))
@@ -125,7 +124,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(error);
       }
     }
@@ -144,10 +143,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);
         }
       }
@@ -271,13 +268,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(),
@@ -1483,8 +1473,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);
   }
@@ -1539,7 +1528,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);
       }
 
@@ -1558,7 +1547,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)))
@@ -1576,7 +1565,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);
       }
     }
@@ -1595,10 +1584,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);
         }
       }
@@ -1765,13 +1752,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 */
diff -Nrup a/sql/mdl.cc b/sql/mdl.cc
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/sql/mdl.cc	2008-05-22 13:21:15 +04:00
@@ -0,0 +1,1342 @@
+/* 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"
+
+/*
+  TODO: Remove this dependency on mysql_priv.h. It's not
+  trivial step at the moment since currently we access to
+  some of THD members and use some of its methods here.
+*/
+#include "mysql_priv.h"
+
+
+/**
+   The lock context. Created internally for an acquired lock.
+   For a given name, there exists only one MDL_LOCK_DATA 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_DATA
+{
+  I_P_List<MDL_LOCK, MDL_LOCK_lock> active_shared;
+  /*
+    There can be several upgraders and active exclusive
+    belonging to the same context.
+  */
+  I_P_List<MDL_LOCK, MDL_LOCK_lock> active_shared_waiting_upgrade;
+  I_P_List<MDL_LOCK, MDL_LOCK_lock> active_exclusive;
+  I_P_List<MDL_LOCK, MDL_LOCK_lock> waiting_exclusive;
+  uint   users;
+  void   *cached_object;
+  mdl_cached_object_release_hook cached_object_release_hook;
+
+  MDL_LOCK_DATA() : cached_object(0), cached_object_release_hook(0) {}
+
+  MDL_LOCK *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_no_other_users()
+  {
+    return (users == 1);
+  }
+};
+
+
+pthread_mutex_t LOCK_mdl;
+pthread_cond_t  COND_mdl;
+HASH mdl_locks;
+uint global_shared_locks_pending;
+uint global_shared_locks_acquired;
+uint global_intention_exclusive_locks_acquired;
+
+
+
+extern "C" uchar *mdl_locks_key(const uchar *record, size_t *length,
+                                my_bool not_used __attribute__((unused)))
+{
+  MDL_LOCK_DATA *entry=(MDL_LOCK_DATA*) 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_shared_locks_pending= global_shared_locks_acquired= 0;
+  global_intention_exclusive_locks_acquired= 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 *l;
+
+  DBUG_ASSERT(dst->thd == src->thd);
+
+  if (!src->locks.is_empty())
+  {
+    I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(src->locks);
+    while ((l= it++))
+    {
+      DBUG_ASSERT(l->ctx);
+      l->ctx= dst;
+      dst->locks.push_front(l);
+    }
+    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  mdl        Pointer to an MDL_LOCK 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_el 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 and
+   normal priority.
+
+   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 *mdl, char *key, int type, const char *db,
+                   const char *name)
+{
+  int4store(key, type);
+  mdl->key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
+  mdl->key= key;
+  mdl->type= MDL_SHARED;
+  mdl->state= MDL_PENDING;
+  mdl->prio= MDL_NORMAL_PRIO;
+  mdl->upgradable= FALSE;
+#ifndef DBUG_OFF
+  mdl->ctx= 0;
+  mdl->lock_data= 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 and
+         normal priority.
+
+   @retval 0      Error
+   @retval non-0  Pointer to an object representing a lock request
+*/
+
+MDL_LOCK *mdl_alloc_lock(int type, const char *db, const char *name,
+                       MEM_ROOT *root)
+{
+  MDL_LOCK *lock;
+  char *key;
+
+  if (!multi_alloc_root(root, &lock, sizeof(MDL_LOCK), &key,
+                        MAX_DBKEY_LENGTH, NULL))
+    return NULL;
+
+  mdl_init_lock(lock, key, type, db, name);
+
+  return lock;
+}
+
+
+/**
+   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       The lock request to be added.
+*/
+
+void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK *lock)
+{
+  DBUG_ENTER("mdl_add_lock");
+  DBUG_ASSERT(lock->state == MDL_PENDING);
+  DBUG_ASSERT(!lock->ctx);
+  lock->ctx= context;
+  context->locks.push_front(lock);
+  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.
+   sets type and priority to MDL_SHARED and MDL_NORMAL_PRIO).
+
+   @param context Context to be cleared.
+*/
+
+void mdl_remove_all_locks(MDL_CONTEXT *context)
+{
+  MDL_LOCK *l;
+  I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+  while ((l= it++))
+  {
+    /* Reset lock request back to its initial state. */
+    l->type= MDL_SHARED;
+    l->prio= MDL_NORMAL_PRIO;
+    l->upgradable= FALSE;
+#ifndef DBUG_OFF
+    l->ctx= 0;
+#endif
+  }
+  context->locks.empty();
+}
+
+
+/**
+   Auxiliary functions needed for creation/destruction of MDL_LOCK_DATA
+   objects.
+
+   @todo This naive implementation should be replaced with one that saves
+         on memory allocation by reusing released objects.
+*/
+
+static MDL_LOCK_DATA* get_lock_data_object(void)
+{
+  return new MDL_LOCK_DATA();
+}
+
+
+static void release_lock_data_object(MDL_LOCK_DATA *lock)
+{
+  delete lock;
+}
+
+
+/**
+   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 lock  [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_LOCK *l, bool *retry)
+{
+  MDL_LOCK_DATA *lock_data;
+  *retry= FALSE;
+
+  DBUG_ASSERT(l->type == MDL_SHARED && l->state == MDL_PENDING);
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  if (l->ctx->has_global_shared_lock && l->upgradable)
+  {
+    my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0));
+    return TRUE;
+  }
+
+  pthread_mutex_lock(&LOCK_mdl);
+
+  if (l->upgradable &&
+      (global_shared_locks_acquired || global_shared_locks_pending))
+  {
+    pthread_mutex_unlock(&LOCK_mdl);
+    *retry= TRUE;
+    return TRUE;
+  }
+
+  if (!(lock_data= (MDL_LOCK_DATA *)hash_search(&mdl_locks, (uchar*)l->key,
+                                                l->key_length)))
+  {
+    lock_data= get_lock_data_object();
+    lock_data->active_shared.push_front(l);
+    lock_data->users= 1;
+    my_hash_insert(&mdl_locks, (uchar*)lock_data);
+    l->state= MDL_ACQUIRED;
+    l->lock_data= lock_data;
+    if (l->upgradable)
+      global_intention_exclusive_locks_acquired++;
+  }
+  else
+  {
+    if ((lock_data->active_exclusive.is_empty() &&
+         (l->prio == MDL_HIGH_PRIO ||
+          lock_data->waiting_exclusive.is_empty() &&
+          lock_data->active_shared_waiting_upgrade.is_empty())) ||
+        (!lock_data->active_exclusive.is_empty() &&
+         lock_data->active_exclusive.head()->ctx == l->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 ....
+      */
+      lock_data->active_shared.push_front(l);
+      lock_data->users++;
+      l->state= MDL_ACQUIRED;
+      l->lock_data= lock_data;
+      if (l->upgradable)
+        global_intention_exclusive_locks_acquired++;
+    }
+    else
+      *retry= TRUE;
+  }
+  pthread_mutex_unlock(&LOCK_mdl);
+
+  return *retry;
+}
+
+
+static void release_lock(MDL_LOCK *l);
+
+
+/**
+   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.
+
+   @note In case of failure (for example, if our thread was killed)
+         resets lock requests back to their initial state (MDL_SHARED
+         and MDL_NORMAL_PRIO).
+
+   @retval FALSE  Success
+   @retval TRUE   Failure
+*/
+
+bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context)
+{
+  MDL_LOCK *l, *lh;
+  MDL_LOCK_DATA *lock_data;
+  bool signalled= FALSE;
+  const char *old_msg;
+  I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+  THD *thd= context->thd;
+
+  DBUG_ASSERT(thd == current_thd);
+
+  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= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table");
+
+  while ((l= it++))
+  {
+    DBUG_ASSERT(l->type == MDL_EXCLUSIVE && l->state == MDL_PENDING);
+    if (!(lock_data= (MDL_LOCK_DATA *)hash_search(&mdl_locks, (uchar*)l->key,
+                                                  l->key_length)))
+    {
+      lock_data= get_lock_data_object();
+      lock_data->waiting_exclusive.push_front(l);
+      lock_data->users= 1;
+      my_hash_insert(&mdl_locks, (uchar*)lock_data);
+      l->lock_data= lock_data;
+    }
+    else
+    {
+      lock_data->waiting_exclusive.push_front(l);
+      lock_data->users++;
+      l->lock_data= lock_data;
+    }
+  }
+
+  while (1)
+  {
+    it.rewind();
+    while ((l= it++))
+    {
+      lock_data= l->lock_data;
+
+      if (global_shared_locks_acquired || global_shared_locks_pending)
+      {
+        /*
+          There is active or pending global shared lock we have
+          to wait until it goes away.
+        */
+        signalled= TRUE;
+        break;
+      }
+      else if (!lock_data->active_exclusive.is_empty() ||
+               !lock_data->active_shared_waiting_upgrade.is_empty())
+      {
+        /*
+          Exclusive MDL owner won't wait on table-level lock the same
+          applies to shared lock waiting upgrade (in this cases we already
+          have some table-level lock).
+        */
+        signalled= TRUE;
+        break;
+      }
+      else if ((lh= lock_data->active_shared.head()))
+      {
+        signalled= notify_thread_having_shared_lock(thd, lh->ctx->thd);
+        break;
+      }
+    }
+    if (!l)
+      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 (thd->killed)
+    {
+      /* Remove our pending lock requests from the locks. */
+      it.rewind();
+      while ((l= it++))
+      {
+        DBUG_ASSERT(l->type == MDL_EXCLUSIVE && l->state == MDL_PENDING);
+        release_lock(l);
+        /* Return lock request to its initial state. */
+        l->type= MDL_SHARED;
+        l->prio= MDL_NORMAL_PRIO;
+        l->upgradable= FALSE;
+        context->locks.remove(l);
+      }
+      /* Pending requests for shared locks can be satisfied now. */
+      pthread_cond_broadcast(&COND_mdl);
+      thd->exit_cond(old_msg);
+      return TRUE;
+    }
+  }
+  it.rewind();
+  while ((l= it++))
+  {
+    global_intention_exclusive_locks_acquired++;
+    lock_data= l->lock_data;
+    lock_data->waiting_exclusive.remove(l);
+    lock_data->active_exclusive.push_front(l);
+    l->state= MDL_ACQUIRED;
+    if (lock_data->cached_object)
+      (*lock_data->cached_object_release_hook)(lock_data->cached_object);
+    lock_data->cached_object= NULL;
+  }
+  /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+  thd->exit_cond(old_msg);
+  return FALSE; 
+}
+
+
+/**
+   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 long belongs
+   @param type    Id of object type
+   @param db      Name of the database
+   @param name    Name of the object
+
+   @note In case of failure to upgrade locks (e.g. because upgrader
+         was killed) leaves locks in their 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, int type,
+                                          const char *db, const char *name)
+{
+  char key[MAX_DBKEY_LENGTH];
+  uint key_length;
+  bool signalled= FALSE;
+  MDL_LOCK *l, *lh;
+  MDL_LOCK_DATA *lock_data;
+  I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+  const char *old_msg;
+  THD *thd= context->thd;
+
+  DBUG_ENTER("mdl_upgrade_shared_lock_to_exclusive");
+  DBUG_PRINT("enter", ("db=%s name=%s", db, name));
+
+  DBUG_ASSERT(thd == current_thd);
+
+  int4store(key, type);
+  key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  pthread_mutex_lock(&LOCK_mdl);
+
+  old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table");
+
+  while ((l= it++))
+    if (l->key_length == key_length && !memcmp(l->key, key, key_length)
&&
+        l->type == MDL_SHARED)
+    {
+      DBUG_PRINT("info", ("found shared lock for upgrade"));
+      DBUG_ASSERT(l->state == MDL_ACQUIRED);
+      DBUG_ASSERT(l->upgradable);
+      l->state= MDL_PENDING_UPGRADE;
+      lock_data= l->lock_data;
+      lock_data->active_shared.remove(l);
+      lock_data->active_shared_waiting_upgrade.push_front(l);
+    }
+
+  while (1)
+  {
+    DBUG_PRINT("info", ("looking at conflicting locks"));
+    it.rewind();
+    while ((l= it++))
+    {
+      if (l->state == MDL_PENDING_UPGRADE)
+      {
+        DBUG_ASSERT(l->type == MDL_SHARED);
+
+        lock_data= l->lock_data;
+
+        DBUG_ASSERT(global_shared_locks_acquired == 0 &&
+                    global_intention_exclusive_locks_acquired);
+
+        if ((lh= lock_data->active_shared.head()))
+        {
+          DBUG_PRINT("info", ("found active shared locks"));
+          signalled= notify_thread_having_shared_lock(thd, lh->ctx->thd);
+          break;
+        }
+        else if (!lock_data->active_exclusive.is_empty())
+        {
+          DBUG_PRINT("info", ("found active exclusive locks"));
+          signalled= TRUE;
+          break;
+        }
+      }
+    }
+    if (!l)
+      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);
+      DBUG_PRINT("info", ("Failed to wake-up from table-level lock ... sleeping"));
+      pthread_cond_timedwait(&COND_mdl, &LOCK_mdl, &abstime);
+    }
+    if (thd->killed)
+    {
+      it.rewind();
+      while ((l= it++))
+        if (l->state == MDL_PENDING_UPGRADE)
+        {
+          DBUG_ASSERT(l->type == MDL_SHARED);
+          l->state= MDL_ACQUIRED;
+          lock_data= l->lock_data;
+          lock_data->active_shared_waiting_upgrade.remove(l);
+          lock_data->active_shared.push_front(l);
+        }
+      /* Pending requests for shared locks can be satisfied now. */
+      pthread_cond_broadcast(&COND_mdl);
+      thd->exit_cond(old_msg);
+      DBUG_RETURN(TRUE);
+    }
+  }
+
+  it.rewind();
+  while ((l= it++))
+    if (l->state == MDL_PENDING_UPGRADE)
+    {
+      DBUG_ASSERT(l->type == MDL_SHARED);
+      lock_data= l->lock_data;
+      lock_data->active_shared_waiting_upgrade.remove(l);
+      lock_data->active_exclusive.push_front(l);
+      l->type= MDL_EXCLUSIVE;
+      l->state= MDL_ACQUIRED;
+      if (lock_data->cached_object)
+        (*lock_data->cached_object_release_hook)(lock_data->cached_object);
+      lock_data->cached_object= 0;
+    }
+
+  /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+  thd->exit_cond(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
+
+   @retval FALSE the lock was granted
+   @retval TRUE  there were conflicting locks.
+
+   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 *l)
+{
+  MDL_LOCK_DATA *lock_data;
+
+  DBUG_ASSERT(l->type == MDL_EXCLUSIVE && l->state == MDL_PENDING);
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  pthread_mutex_lock(&LOCK_mdl);
+
+  if (!(lock_data= (MDL_LOCK_DATA *)hash_search(&mdl_locks, (uchar*)l->key,
+                                                l->key_length)))
+  {
+    lock_data= get_lock_data_object();
+    lock_data->active_exclusive.push_front(l);
+    lock_data->users= 1;
+    my_hash_insert(&mdl_locks, (uchar*)lock_data);
+    l->state= MDL_ACQUIRED;
+    l->lock_data= lock_data;
+    lock_data= 0;
+    global_intention_exclusive_locks_acquired++;
+  }
+  pthread_mutex_unlock(&LOCK_mdl);
+
+  /*
+    FIXME: We can't leave pending MDL_EXCLUSIVE lock request in the list since
+           for such locks we assume that they have MDL_LOCK::lock properly set.
+           Long term we should clearly define relation between lock types,
+           presence in the context lists and MDL_LOCK::lock values.
+  */
+  if (lock_data)
+    context->locks.remove(l);
+
+  return lock_data;
+}
+
+
+/**
+   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
+   (see MDL_LOCK::upgradable).
+
+   @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)
+{
+  THD *thd= context->thd;
+  const char *old_msg;
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+  DBUG_ASSERT(thd == current_thd);
+  DBUG_ASSERT(!context->has_global_shared_lock);
+
+  pthread_mutex_lock(&LOCK_mdl);
+
+  global_shared_locks_pending++;
+  old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table");
+
+  while (!thd->killed && global_intention_exclusive_locks_acquired)
+    pthread_cond_wait(&COND_mdl, &LOCK_mdl);
+
+  global_shared_locks_pending--;
+  if (thd->killed)
+  {
+    /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+    thd->exit_cond(old_msg);
+    return TRUE;
+  }
+  global_shared_locks_acquired++;
+  context->has_global_shared_lock= TRUE;
+  /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+  thd->exit_cond(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 *l;
+  MDL_LOCK_DATA *lock_data;
+  I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+  const char *old_msg;
+  THD *thd= context->thd;
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+  DBUG_ASSERT(thd == current_thd);
+
+  while (!thd->killed)
+  {
+    /*
+      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= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table");
+    it.rewind();
+    while ((l= it++))
+    {
+      DBUG_ASSERT(l->state == MDL_PENDING);
+      if ((l->upgradable || l->type == MDL_EXCLUSIVE) &&
+          (global_shared_locks_acquired || global_shared_locks_pending))
+        break;
+      /*
+        To avoid starvation we don't wait if we have pending MDL_EXCLUSIVE lock.
+      */
+      if (l->type == MDL_SHARED &&
+          (lock_data= (MDL_LOCK_DATA *)hash_search(&mdl_locks, (uchar*)l->key,
+                                                   l->key_length)) &&
+          !(lock_data->active_exclusive.is_empty() &&
+            lock_data->active_shared_waiting_upgrade.is_empty() &&
+            lock_data->waiting_exclusive.is_empty()))
+        break;
+    }
+    if (!l)
+    {
+      pthread_mutex_unlock(&LOCK_mdl);
+      break;
+    }
+    pthread_cond_wait(&COND_mdl, &LOCK_mdl);
+    /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+    thd->exit_cond(old_msg);
+  }
+  return thd->killed;
+}
+
+
+/**
+   Auxiliary function which allows to release particular lock
+   ownership of which is represented by lock request object.
+*/
+
+static void release_lock(MDL_LOCK *l)
+{
+  MDL_LOCK_DATA *lock_data;
+
+  DBUG_ENTER("release_lock");
+  DBUG_PRINT("enter", ("db=%s name=%s", l->key + 4,
+                        l->key + 4 + strlen(l->key + 4) + 1));
+
+  lock_data= l->lock_data;
+  if (lock_data->has_no_other_users())
+  {
+    hash_delete(&mdl_locks, (uchar *)lock_data);
+    DBUG_PRINT("info", ("releasing cached_object cached_object=%p",
+                        lock_data->cached_object));
+    if (lock_data->cached_object)
+      (*lock_data->cached_object_release_hook)(lock_data->cached_object);
+    release_lock_data_object(lock_data);
+    if (l->type == MDL_EXCLUSIVE && l->state == MDL_ACQUIRED ||
+        l->type == MDL_SHARED && l->state == MDL_ACQUIRED &&
l->upgradable)
+      global_intention_exclusive_locks_acquired--;
+  }
+  else
+  {
+    switch (l->type)
+    {
+      case MDL_SHARED:
+        lock_data->active_shared.remove(l);
+        if (l->upgradable)
+          global_intention_exclusive_locks_acquired--;
+        break;
+      case MDL_EXCLUSIVE:
+        if (l->state == MDL_PENDING)
+          lock_data->waiting_exclusive.remove(l);
+        else
+        {
+          lock_data->active_exclusive.remove(l);
+          global_intention_exclusive_locks_acquired--;
+        }
+        break;
+      default:
+        /* TODO Really? How about problems during lock upgrade ? */
+        DBUG_ASSERT(0);
+    }
+    lock_data->users--;
+  }
+
+  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 *l;
+  I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+  DBUG_ENTER("mdl_release_locks");
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  pthread_mutex_lock(&LOCK_mdl);
+  while ((l= it++))
+  {
+    DBUG_PRINT("info", ("found lock to release l=%p", l));
+    /*
+      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 (!(l->type == MDL_SHARED && l->state == MDL_PENDING))
+    {
+      release_lock(l);
+      l->state= MDL_PENDING;
+#ifndef DBUG_OFF
+      l->lock_data= 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 and if it is upgradable 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 all exclusive locks associated with context.
+   Removes the locks from the context.
+
+   @param context Context with exclusive locks.
+
+   @note Shared locks are left intact.
+   @note Resets lock requests for locks released back to their
+         initial state (i.e.sets type and priority to MDL_SHARED
+         and MDL_NORMAL_PRIO).
+*/
+
+void mdl_release_exclusive_locks(MDL_CONTEXT *context)
+{
+  MDL_LOCK *l;
+  I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  pthread_mutex_lock(&LOCK_mdl);
+  while ((l= it++))
+  {
+    if (l->type == MDL_EXCLUSIVE)
+    {
+      DBUG_ASSERT(l->state == MDL_ACQUIRED);
+      release_lock(l);
+#ifndef DBUG_OFF
+      l->ctx= 0;
+      l->lock_data= 0;
+#endif
+      l->state= MDL_PENDING;
+      /* Return lock request to its initial state. */
+      l->type= MDL_SHARED;
+      l->prio= MDL_NORMAL_PRIO;
+      l->upgradable= FALSE;
+      context->locks.remove(l);
+    }
+  }
+  pthread_cond_broadcast(&COND_mdl);
+  pthread_mutex_unlock(&LOCK_mdl);
+}
+
+
+/**
+   Release a lock.
+   Removes the lock from the context.
+
+   @param context Context containing lock in question
+   @param lock    Lock to be released
+
+   @note Resets lock request for lock released back to its initial state
+         (i.e.sets type and priority to MDL_SHARED and MDL_NORMAL_PRIO).
+*/
+
+void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK *lr)
+{
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  pthread_mutex_lock(&LOCK_mdl);
+  release_lock(lr);
+#ifndef DBUG_OFF
+  lr->ctx= 0;
+  lr->lock_data= 0;
+#endif
+  lr->state= MDL_PENDING;
+  /* Return lock request to its initial state. */
+  lr->type= MDL_SHARED;
+  lr->prio= MDL_NORMAL_PRIO;
+  lr->upgradable= FALSE;
+  context->locks.remove(lr);
+  pthread_cond_broadcast(&COND_mdl);
+  pthread_mutex_unlock(&LOCK_mdl);
+}
+
+
+/**
+   Downgrade all exclusive locks in the context to
+   shared.
+
+   @param context A context with exclusive locks.
+*/
+
+void mdl_downgrade_exclusive_locks(MDL_CONTEXT *context)
+{
+  MDL_LOCK *l;
+  MDL_LOCK_DATA *lock_data;
+  I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  pthread_mutex_lock(&LOCK_mdl);
+  while ((l= it++))
+    if (l->type == MDL_EXCLUSIVE)
+    {
+      DBUG_ASSERT(l->state == MDL_ACQUIRED);
+      if (!l->upgradable)
+        global_intention_exclusive_locks_acquired--;
+      lock_data= l->lock_data;
+      lock_data->active_exclusive.remove(l);
+      l->type= MDL_SHARED;
+      lock_data->active_shared.push_front(l);
+    }
+  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_shared_locks_acquired--;
+  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_DBKEY_LENGTH];
+  uint key_length;
+  MDL_LOCK *l;
+  I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+
+  int4store(key, type);
+  key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
+
+  while ((l= it++) && (l->key_length != key_length || memcmp(l->key, key,
key_length)))
+    continue;
+  return (l && l->type == MDL_EXCLUSIVE && l->state ==
MDL_ACQUIRED);
+}
+
+
+/**
+   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_DBKEY_LENGTH];
+  uint key_length;
+  MDL_LOCK *l;
+  I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+
+  int4store(key, type);
+  key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
+
+  while ((l= it++) && (l->key_length != key_length ||
+                       memcmp(l->key, key, key_length) ||
+                       l->state == MDL_PENDING))
+    continue;
+
+  return l;
+}
+
+
+/**
+   Check if we have any pending exclusive locks which conflict with
+   existing shared lock.
+
+   @param l 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 *l)
+{
+  bool result;
+
+  DBUG_ASSERT(l->type == MDL_SHARED && l->state == MDL_ACQUIRED);
+  safe_mutex_assert_not_owner(&LOCK_open);
+
+  pthread_mutex_lock(&LOCK_mdl);
+  result= !(l->lock_data->waiting_exclusive.is_empty() &&
+            l->lock_data->active_shared_waiting_upgrade.is_empty());
+  pthread_mutex_unlock(&LOCK_mdl);
+  return result;
+}
+
+
+/**
+   Associate pointer to an opaque object with a lock.
+
+   @param l             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 *l, 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", l->key + 4,
+                       l->key + 4 + strlen(l->key + 4) + 1,
+                       cached_object));
+
+  DBUG_ASSERT(l->state == MDL_ACQUIRED || l->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(!l->lock_data->cached_object);
+
+  l->lock_data->cached_object= cached_object;
+  l->lock_data->cached_object_release_hook= release_hook;
+
+  DBUG_VOID_RETURN;
+}
+
+
+/**
+   Get a pointer to an opaque object that associated with the lock.
+
+   @param  l 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 *l)
+{
+  DBUG_ASSERT(l->state == MDL_ACQUIRED || l->state == MDL_PENDING_UPGRADE);
+  return l->lock_data->cached_object;
+}
diff -Nrup a/sql/mdl.h b/sql/mdl.h
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/sql/mdl.h	2008-05-22 13:21:15 +04:00
@@ -0,0 +1,260 @@
+#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>
+
+class THD;
+
+struct MDL_LOCK;
+struct MDL_LOCK_DATA;
+struct MDL_CONTEXT;
+
+/** Type of metadata lock request. */
+
+enum enum_mdl_type {MDL_SHARED=0, MDL_EXCLUSIVE};
+
+
+/** States which metadata lock request can have. */
+
+enum enum_mdl_state {MDL_PENDING=0, MDL_ACQUIRED, MDL_PENDING_UPGRADE};
+
+
+/**
+   Priority of metadata lock requests. High priority attribute is
+   applicable only to requests for shared locks and indicates that
+   such request should ignore pending requests for exclusive locks
+   and for upgrading of shared locks to exclusive.
+*/
+
+enum enum_mdl_prio {MDL_NORMAL_PRIO=0, MDL_HIGH_PRIO};
+
+
+/**
+   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
+{
+  char          *key;
+  uint          key_length;
+  enum          enum_mdl_type type;
+  enum          enum_mdl_state state;
+  enum          enum_mdl_prio prio;
+  /**
+     TRUE -- if shared lock corresponding to this lock request at some
+     point might be upgraded to an exclusive lock and therefore conflicts
+     with global shared lock, FALSE -- otherwise.
+  */
+  bool          upgradable;
+
+private:
+  /**
+     Pointers for participating in the list of lock requests for this context.
+  */
+  MDL_LOCK      *next_context;
+  MDL_LOCK      **prev_context;
+  /**
+     Pointers for participating in the list of satisfied/pending requests
+     for the lock.
+  */
+  MDL_LOCK      *next_lock;
+  MDL_LOCK      **prev_lock;
+
+  friend struct MDL_LOCK_context;
+  friend struct MDL_LOCK_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_DATA *lock_data;
+  MDL_CONTEXT   *ctx;
+};
+
+
+/**
+   Helper class which specifies which members of MDL_LOCK are used for
+   participation in the list lock requests belonging to one context.
+*/
+
+struct MDL_LOCK_context
+{
+  static inline MDL_LOCK **next_ptr(MDL_LOCK *l)
+  {
+    return &l->next_context;
+  }
+  static inline MDL_LOCK ***prev_ptr(MDL_LOCK *l)
+  {
+    return &l->prev_context;
+  }
+};
+
+
+/**
+   Helper class which specifies which members of MDL_LOCK are used for
+   participation in the list of satisfied/pending requests for the lock.
+*/
+
+struct MDL_LOCK_lock
+{
+  static inline MDL_LOCK **next_ptr(MDL_LOCK *l)
+  {
+    return &l->next_lock;
+  }
+  static inline MDL_LOCK ***prev_ptr(MDL_LOCK *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, MDL_LOCK_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);
+
+void mdl_init_lock(MDL_LOCK *mdl, char *key, int type, const char *db,
+                   const char *name);
+MDL_LOCK *mdl_alloc_lock(int type, const char *db, const char *name,
+                       MEM_ROOT *root);
+void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK *lock);
+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 *lock, enum_mdl_type lock_type)
+{
+  DBUG_ASSERT(lock->state == MDL_PENDING);
+  lock->type= lock_type;
+}
+
+/**
+   Set priority for lock request. High priority can be only set
+   for shared locks.
+*/
+
+inline void mdl_set_lock_priority(MDL_LOCK *lock, enum_mdl_prio prio)
+{
+  DBUG_ASSERT(lock->type == MDL_SHARED && lock->state == MDL_PENDING);
+  lock->prio= prio;
+}
+
+/**
+   Mark request for shared lock as upgradable. Can be only applied
+   to pending locks.
+*/
+
+inline void mdl_set_upgradable(MDL_LOCK *lock)
+{
+  DBUG_ASSERT(lock->type == MDL_SHARED && lock->state == MDL_PENDING);
+  lock->upgradable= TRUE;
+}
+
+bool mdl_acquire_shared_lock(MDL_LOCK *l, bool *retry);
+bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context);
+bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type,
+                                          const char *db, const char *name);
+bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, MDL_LOCK *lock);
+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_exclusive_locks(MDL_CONTEXT *context);
+void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK *lock);
+void mdl_downgrade_exclusive_locks(MDL_CONTEXT *context);
+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 *l);
+
+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, MDL_LOCK_context> mdl_get_locks(MDL_CONTEXT
*ctx)
+{
+  I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> result(ctx->locks);
+  return result;
+}
+
+/**
+   Give metadata lock request object for the table get table definition
+   cache key corresponding to it.
+
+   @param l   [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 *l, LEX_STRING *key)
+{
+  key->str= l->key + 4;
+  key->length= l->key_length - 4;
+}
+
+
+typedef void (* mdl_cached_object_release_hook)(void *);
+void* mdl_get_cached_object(MDL_LOCK *l);
+void mdl_set_cached_object(MDL_LOCK *l, void *cached_object,
+                           mdl_cached_object_release_hook release_hook);
+
+#endif
diff -Nrup a/sql/mysql_priv.h b/sql/mysql_priv.h
--- a/sql/mysql_priv.h	2008-04-14 14:09:59 +04:00
+++ b/sql/mysql_priv.h	2008-05-22 13:21:12 +04:00
@@ -764,7 +764,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);
@@ -1058,7 +1058,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,
@@ -1099,10 +1098,8 @@ 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,
+bool 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);
@@ -1284,20 +1281,23 @@ void release_table_share(TABLE_SHARE *sh
 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);
+enum enum_open_table_action {OT_NO_ACTION= 0, OT_BACK_OFF_AND_RETRY,
+                             OT_DISCOVER, OT_REPAIR};
 TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem,
-		  bool *refresh, uint flags);
+		  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);
 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);
+bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list);
+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);
+bool reopen_tables(THD *thd, bool get_locks);
 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);
@@ -1307,8 +1307,7 @@ bool open_new_frm(THD *thd, TABLE_SHARE 
                   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 unlock_locked_tables(THD *thd);
 void execute_init_command(THD *thd, sys_var_str *init_command_var,
 			  rw_lock_t *var_mutex);
 extern Field *not_found_field;
@@ -1442,7 +1441,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 */
@@ -1582,7 +1581,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,
@@ -1624,6 +1623,9 @@ uint prep_alter_part_table(THD *thd, TAB
 #define RTFC_CHECK_KILLED_FLAG      0x0004
 bool remove_table_from_cache(THD *thd, const char *db, const char *table,
                              uint flags);
+bool notify_thread_having_shared_lock(THD *thd, THD *in_use);
+void expel_table_from_cache(THD *leave_thd, const char *db,
+                            const char *table_name);
 
 #define NORMAL_PART_NAME 0
 #define TEMP_PART_NAME 1
@@ -1755,7 +1757,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);
@@ -2063,8 +2065,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;
@@ -2133,18 +2136,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 */
diff -Nrup a/sql/mysqld.cc b/sql/mysqld.cc
--- a/sql/mysqld.cc	2008-04-20 12:06:48 +04:00
+++ b/sql/mysqld.cc	2008-05-22 13:21:12 +04:00
@@ -1304,9 +1304,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();
@@ -3781,7 +3781,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);
diff -Nrup a/sql/rpl_rli.cc b/sql/rpl_rli.cc
--- a/sql/rpl_rli.cc	2008-03-27 21:58:38 +03:00
+++ b/sql/rpl_rli.cc	2008-05-22 13:21:12 +04:00
@@ -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
diff -Nrup a/sql/rpl_rli.h b/sql/rpl_rli.h
--- a/sql/rpl_rli.h	2008-03-27 21:58:38 +03:00
+++ b/sql/rpl_rli.h	2008-05-22 13:21:12 +04:00
@@ -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();
 
   /*
diff -Nrup a/sql/set_var.cc b/sql/set_var.cc
--- a/sql/set_var.cc	2008-04-14 15:30:04 +04:00
+++ b/sql/set_var.cc	2008-05-22 13:21:12 +04:00
@@ -4043,7 +4043,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))
diff -Nrup a/sql/si_objects.cc b/sql/si_objects.cc
--- a/sql/si_objects.cc	2008-04-16 11:53:13 +04:00
+++ b/sql/si_objects.cc	2008-05-22 13:21:12 +04:00
@@ -1906,6 +1906,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);
 
diff -Nrup a/sql/sp_head.cc b/sql/sp_head.cc
--- a/sql/sp_head.cc	2008-04-08 19:37:10 +04:00
+++ b/sql/sp_head.cc	2008-05-22 13:21:13 +04:00
@@ -3908,6 +3908,9 @@ 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= mdl_alloc_lock(0, table->db, table->table_name,
+                                      thd->mdl_el_root ? thd->mdl_el_root :
+                                                         thd->mem_root);
 
       /* Everyting else should be zeroed */
 
@@ -3951,6 +3954,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= mdl_alloc_lock(0, table->db, table->table_name,
+                                  thd->mdl_el_root ? thd->mdl_el_root :
+                                                     thd->mem_root);
 
   lex->add_to_query_tables(table);
   return table;
diff -Nrup a/sql/sql_acl.cc b/sql/sql_acl.cc
--- a/sql/sql_acl.cc	2008-04-14 15:30:04 +04:00
+++ b/sql/sql_acl.cc	2008-05-22 13:21:13 +04:00
@@ -676,12 +676,7 @@ 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);
-  }
+  unlock_locked_tables(thd);  // Can't have locked tables here
 
   /*
     To avoid deadlocks we should obtain table locks before
@@ -697,6 +692,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 +1588,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 +3022,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 +3248,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 +3390,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 +3724,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 +3791,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 +5120,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
   /*
diff -Nrup a/sql/sql_base.cc b/sql/sql_base.cc
--- a/sql/sql_base.cc	2008-04-14 14:10:00 +04:00
+++ b/sql/sql_base.cc	2008-05-22 13:21:13 +04:00
@@ -87,55 +87,41 @@ 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 reopen_table_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list,
+                               const char *alias, char *cache_key,
+                               uint cache_key_length);
+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 +129,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 +141,39 @@ 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);
+    for (entry= share->free_tables; entry; entry= entry->share_next)
+    {
+      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++;
+    }
+    for (entry= share->used_tables; entry; entry= entry->share_next)
+    {
+      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()
@@ -269,6 +250,29 @@ static void table_def_free_entry(TABLE_S
 }
 
 
+/*
+  Auxiliary routines for handling per-share used/unused TABLE objects lists.
+  TODO: Use generic intrusive list template here, make functions more loaded.
+*/
+
+static void table_def_list_add_element(TABLE **list, TABLE *element)
+{
+  element->share_next= *list;
+  if (*list)
+    (*list)->share_prev= &element->share_next;
+  element->share_prev= list;
+  *list= element;
+}
+
+
+static void table_def_list_unlink_element(TABLE *element)
+{
+  *(element->share_prev)= element->share_next;
+  if (element->share_next)
+    element->share_next->share_prev= element->share_prev;
+}
+
+
 bool table_def_init(void)
 {
   table_def_inited= 1;
@@ -287,8 +291,11 @@ 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;
@@ -334,6 +341,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)))
@@ -606,6 +620,26 @@ TABLE_SHARE *get_cached_table_share(cons
 }  
 
 
+/**
+   @brief Mark table share as having one more user (increase its reference
+          count).
+
+   @param share Table share for which reference count should be increased.
+*/
+
+static void reference_table_share(TABLE_SHARE *share)
+{
+  DBUG_ENTER("reference_table_share");
+  DBUG_ASSERT(share->ref_count);
+  pthread_mutex_lock(&share->mutex);
+  share->ref_count++;
+  pthread_mutex_unlock(&share->mutex);
+  DBUG_PRINT("exit", ("share: 0x%lx  ref_count: %u",
+                     (ulong) share, share->ref_count));
+  DBUG_VOID_RETURN;
+}
+
+
 /*
   Close file handle, but leave the table in the table cache
 
@@ -657,9 +691,12 @@ void close_handle_and_leave_table_as_loc
     detach_merge_children(table, FALSE);
   table->file->close();
   table->db_stat= 0;                            // Mark file closed
+  /* Unlink table from the used tables list for the share. */
+  table_def_list_unlink_element(table);
   release_table_share(table->s, RELEASE_NORMAL);
   table->s= share;
   table->file->change_table_ptr(table, table->s);
+  table_def_list_add_element(&share->used_tables, table);
 
   DBUG_VOID_RETURN;
 }
@@ -696,11 +733,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 +749,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 +760,10 @@ 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;
+    for (TABLE *table= share->used_tables; table; table= table->share_next)
+      ++(*start_list)->in_use;
+    (*start_list)->locked= (share->version == 0) ? 1 : 0;
     start_list= &(*start_list)->next;
     *start_list=0;
   }
@@ -785,11 +808,23 @@ static void free_cache_entry(TABLE *tabl
 
   /* Assert that MERGE children are not attached before final close. */
   DBUG_ASSERT(!table->is_children_attached());
+  table_cache_count--;
+
+  /*
+    Remove from either per-share chain of unused TABLE objects
+    or from similar chain of used objects. This should be done
+    before releasing table share.
+  */
+  table_def_list_unlink_element(table);
 
   intern_close_table(table);
   if (!table->in_use)
   {
-    table->next->prev=table->prev;		/* remove from used chain */
+    /*
+      If TABLE object was not used we should also remove it from
+      global unused chain.
+    */
+    table->next->prev=table->prev;
     table->prev->next=table->next;
     if (table == unused_tables)
     {
@@ -818,6 +853,36 @@ 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.
+*/
+
+static void kill_delayed_threads_for_table(TABLE_SHARE *share)
+{
+  for (TABLE *tab= share->used_tables; tab; tab= tab->share_next)
+  {
+    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 +890,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 +915,179 @@ 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);
-      VOID(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();
   }
   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)
+      {
+        share->version= 0;
+        kill_delayed_threads_for_table(share);
 	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)
+
+  /*
+    Get rid of all unused TABLE and TABLE_SHARE instances. By doing
+    this we automatically close all tables which were marked as "old".
+
+    FIXME: Do not close all unused TABLE instances when flushing
+           particular table.
+  */
+  while (unused_tables)
+    free_cache_entry(unused_tables);
+  /* Free table shares */
+  while (oldest_unused_share->next)
+  {
+    pthread_mutex_lock(&oldest_unused_share->mutex);
+    VOID(hash_delete(&table_def_cache, (uchar*) oldest_unused_share));
+  }
+
+  if (!wait_for_refresh)
+  {
+    if (!have_lock)
+      VOID(pthread_mutex_unlock(&LOCK_open));
+    DBUG_RETURN(result);
+  }
+
+  DBUG_ASSERT(!have_lock);
+  VOID(pthread_mutex_unlock(&LOCK_open));
+
+  if (thd->locked_tables)
   {
     /*
-      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 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");
+    if (!tables)
+    {
+      for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
+      {
+        /*
+          Checking TABLE::db_stat is essential in case when we have
+          several instances of the table open and locked.
+        */
+        if (tab->db_stat)
+        {
+          char dbname[NAME_LEN+1];
+          char tname[NAME_LEN+1];
+          /*
+            Since close_data_files_and_morph_locks() frees share's memroot
+            we need to make copies of database and table names.
+          */
+          strmov(dbname, tab->s->db.str);
+          strmov(tname, tab->s->table_name.str);
+          if (wait_while_table_is_used(thd, tab, HA_EXTRA_FORCE_REOPEN))
+          {
+            result= TRUE;
+            goto err_with_reopen;
+          }
+          VOID(pthread_mutex_lock(&LOCK_open));
+          close_data_files_and_morph_locks(thd, dbname, tname);
+          VOID(pthread_mutex_unlock(&LOCK_open));
+        }
+      }
+    }
+    else
+    {
+      for (TABLE_LIST *table= tables; table; table= table->next_local)
+      {
+        TABLE *tab= find_locked_table(thd->open_tables, table->db,
+                                      table->table_name);
+        /*
+          Checking TABLE::db_stat is essential in case when we have
+          several instances of the table open and locked.
+        */
+        if (tab->db_stat)
+        {
+          if (wait_while_table_is_used(thd, tab, HA_EXTRA_FORCE_REOPEN))
+          {
+            result= TRUE;
+            goto err_with_reopen;
+          }
+          VOID(pthread_mutex_lock(&LOCK_open));
+          close_data_files_and_morph_locks(thd, table->db, table->table_name);
+          VOID(pthread_mutex_unlock(&LOCK_open));
+        }
+      }
+    }
+  }
 
-    close_old_data_files(thd,thd->open_tables,1,1);
+  /* Wait until all threads have closed all the tables we are flushing. */
+  DBUG_PRINT("info", ("Waiting for other threads to close their open tables"));
+
+  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)
+    VOID(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)
+  {
+    VOID(pthread_mutex_lock(&LOCK_open));
     /*
       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);
+    result|= reopen_tables(thd, 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)
     VOID(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);
+    mdl_downgrade_exclusive_locks(&thd->mdl_context);
   }
   DBUG_RETURN(result);
 }
@@ -1055,7 +1140,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)
     VOID(pthread_mutex_unlock(&LOCK_open));
@@ -1163,8 +1248,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)
-    VOID(hash_delete(&open_cache,(uchar*) unused_tables)); /* purecov: tested */
+  while (table_cache_count > table_cache_size && unused_tables)
+    free_cache_entry(unused_tables);
   check_unused();
   if (found_old_table)
   {
@@ -1194,7 +1279,8 @@ 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;
@@ -1283,6 +1369,10 @@ void close_thread_tables(THD *thd)
     if (prelocked_mode == PRELOCKED_UNDER_LOCK_TABLES)
       DBUG_VOID_RETURN;
 
+    /*
+      Note that we are leaving prelocked mode so we don't need
+      to care about THD::locked_tables_root.
+    */
     thd->lock= thd->locked_tables;
     thd->locked_tables= 0;
     /* Fallthrough */
@@ -1313,6 +1403,12 @@ void close_thread_tables(THD *thd)
   if (thd->open_tables)
     close_open_tables(thd);
 
+  mdl_release_locks(&thd->mdl_context);
+  if (!skip_mdl)
+  {
+    mdl_remove_all_locks(&thd->mdl_context);
+  }
+
   if (prelocked_mode == PRELOCKED)
   {
     /*
@@ -1333,11 +1429,11 @@ bool close_thread_table(THD *thd, TABLE 
 {
   bool found_old_table= 0;
   TABLE *table= *table_ptr;
+  TABLE_SHARE *share= table->s;
   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' 0x%lx", table->s->db.str,
-                        table->s->table_name.str, (long) table));
+  safe_mutex_assert_owner(&LOCK_open);
 
   *table_ptr=table->next;
   /*
@@ -1347,10 +1443,11 @@ bool close_thread_table(THD *thd, TABLE 
   if (table->child_l || table->parent)
     detach_merge_children(table, TRUE);
 
+  table->mdl_lock= 0;
   if (table->needs_reopen_or_name_lock() ||
       thd->version != refresh_version || !table->db_stat)
   {
-    VOID(hash_delete(&open_cache,(uchar*) table));
+    free_cache_entry(table);
     found_old_table=1;
   }
   else
@@ -1367,9 +1464,14 @@ bool close_thread_table(THD *thd, TABLE 
     /* Free memory and reset for next loop */
     table->file->ha_reset();
     table->in_use=0;
+    /* Remove table from the list of tables used in this share. */
+    table_def_list_unlink_element(table);
+    /* Add table to the list of unused TABLE objects for this share. */
+    table_def_list_add_element(&share->free_tables, table);
+    /* Also link it last in the global list of unused TABLE objects. */
     if (unused_tables)
     {
-      table->next=unused_tables;		/* Link in last */
+      table->next=unused_tables;
       table->prev=unused_tables->prev;
       unused_tables->prev=table;
       table->prev->next=table;
@@ -2064,7 +2166,7 @@ void unlink_open_table(THD *thd, TABLE *
       /* Remove table from open_tables list. */
       *prev= list->next;
       /* Close table. */
-      VOID(hash_delete(&open_cache,(uchar*) list)); // Close table
+      free_cache_entry(list);
     }
     else
     {
@@ -2174,43 +2276,34 @@ void wait_for_condition(THD *thd, pthrea
 
 bool name_lock_locked_table(THD *thd, TABLE_LIST *tables)
 {
+  bool result= TRUE;
+
   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);
+  tables->table= find_write_locked_table(thd->open_tables, 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
+  if (tables->table)
   {
     /*
       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);
+    result= wait_while_table_is_used(thd, tables->table, HA_EXTRA_FORCE_REOPEN);
   }
 
-  DBUG_RETURN(TRUE);
+  DBUG_RETURN(result);
 }
 
 
 /*
-  Open table which is already name-locked by this thread.
+  Open table for which this thread has exclusive meta-data lock.
 
   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.
+      table_list  TABLE_LIST object for table to be open.
 
   NOTE
     This function assumes that its caller already acquired LOCK_open mutex.
@@ -2220,33 +2313,32 @@ bool name_lock_locked_table(THD *thd, TA
     TRUE  - Error
 */
 
-bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in)
+bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list)
 {
-  TABLE *table= table_list->table;
+  TABLE *table;
   TABLE_SHARE *share;
+  char key[MAX_DBKEY_LENGTH];
+  uint key_length;
   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)
+  if (thd->killed)
     DBUG_RETURN(TRUE);
 
-  orig_table= *table;
+  key_length= create_table_def_key(thd, key, table_list, 0);
 
-  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))
+  if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
+    DBUG_RETURN(TRUE);
+
+  if (reopen_table_entry(thd, table, table_list, table_name, key, key_length))
   {
-    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;
+    my_free((uchar*)table, MYF(0));
     DBUG_RETURN(TRUE);
   }
 
@@ -2263,128 +2355,19 @@ bool reopen_name_locked_table(THD* thd, 
   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_cache_count;
+
+  table_def_list_add_element(&share->used_tables, table);
+
+  table->next= thd->open_tables;
+  thd->open_tables= table;
 
   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;
-  VOID(pthread_mutex_lock(&LOCK_open));
-
-  if (hash_search(&open_cache, (uchar *)key, key_length))
-  {
-    VOID(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)))
-  {
-    VOID(pthread_mutex_unlock(&LOCK_open));
-    DBUG_RETURN(TRUE);
-  }
-  (*table)->open_placeholder= 1;
-  (*table)->next= thd->open_tables;
-  thd->open_tables= *table;
-  VOID(pthread_mutex_unlock(&LOCK_open));
+  table_list->table= table;
   DBUG_RETURN(FALSE);
 }
 
@@ -2453,6 +2436,20 @@ 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)
+{
+  VOID(pthread_mutex_lock(&LOCK_open));
+  release_table_share((TABLE_SHARE*)share, RELEASE_NORMAL);
+  broadcast_refresh();
+  VOID(pthread_mutex_unlock(&LOCK_open));
+}
+
+
 /*
   Open a table.
 
@@ -2460,13 +2457,17 @@ 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.
+    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.
                         If this is a NULL pointer, then the table is not
                         put in the thread-open-list.
     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.
@@ -2487,21 +2488,23 @@ bool check_if_table_exists(THD *thd, TAB
 
 
 TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
-		  bool *refresh, uint flags)
+		  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 *mdl_lock;
+  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;
+  if (action)
+    *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))
@@ -2643,7 +2646,13 @@ 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;
@@ -2651,21 +2660,12 @@ TABLE *open_table(THD *thd, TABLE_LIST *
                            table_list->db, table_list->table_name, reg_ext, 0);
       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;
-        VOID(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);
-          VOID(pthread_mutex_unlock(&LOCK_open));
           DBUG_RETURN(0); // VIEW
         }
-        VOID(pthread_mutex_unlock(&LOCK_open));
       }
     }
     /*
@@ -2698,6 +2698,32 @@ TABLE *open_table(THD *thd, TABLE_LIST *
     on disk.
   */
 
+  mdl_lock= table_list->mdl_lock;
+  mdl_add_lock(&thd->mdl_context, mdl_lock);
+
+  if (table_list->open_table_type)
+  {
+    mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE);
+    /* TODO: This case can be significantly optimized. */
+    if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+      DBUG_RETURN(0);
+  }
+  else
+  {
+    bool retry;
+
+    if (table_list->mdl_upgradable)
+      mdl_set_upgradable(mdl_lock);
+    mdl_set_lock_priority(mdl_lock, (flags & MYSQL_LOCK_IGNORE_FLUSH) ?
+                                    MDL_HIGH_PRIO : MDL_NORMAL_PRIO);
+    if (mdl_acquire_shared_lock(mdl_lock, &retry))
+    {
+      if (action && retry)
+        *action= OT_BACK_OFF_AND_RETRY;
+      DBUG_RETURN(0);
+    }
+  }
+
   VOID(pthread_mutex_lock(&LOCK_open));
 
   /*
@@ -2714,131 +2740,108 @@ 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;
+    if (action)
+      *action= OT_BACK_OFF_AND_RETRY;
     VOID(pthread_mutex_unlock(&LOCK_open));
     DBUG_RETURN(0);
   }
 
-  /*
-    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_table_type == TABLE_LIST::OPEN_OR_CREATE)
   {
-    DBUG_PRINT("tcache", ("in_use table: '%s'.'%s' 0x%lx", table->s->db.str,
-                          table->s->table_name.str, (long) 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())
+    bool exists;
+
+    if (check_if_table_exists(thd, table_list, &exists))
+      goto err_unlock2;
+
+    if (!exists)
     {
-      DBUG_PRINT("note",
-                 ("Found table '%s.%s' with different refresh version",
-                  table_list->db, table_list->table_name));
+      VOID(pthread_mutex_unlock(&LOCK_open));
+      DBUG_RETURN(0);
+    }
+    /* Table exists. Let us try to open it. */
+  }
+  else if (table_list->open_table_type == TABLE_LIST::TAKE_EXCLUSIVE_MDL)
+  {
+    VOID(pthread_mutex_unlock(&LOCK_open));
+    DBUG_RETURN(0);
+  }
 
-      if (flags & MYSQL_LOCK_IGNORE_FLUSH)
-      {
-        /* Force close at once after usage */
-        thd->version= table->s->version;
-        continue;
-      }
+  if (!(share= (TABLE_SHARE *)mdl_get_cached_object(mdl_lock)))
+  {
+    if (!(share= get_table_share_with_create(thd, table_list, key,
+                                             key_length, OPEN_VIEW,
+                                             &error)))
+      goto err_unlock2;
+
+    if (share->is_view)
+    {
+      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;
 
-      /* Avoid self-deadlocks by detecting self-dependencies. */
-      if (table->open_placeholder && table->in_use == thd)
-      {
-	VOID(pthread_mutex_unlock(&LOCK_open));
-        my_error(ER_UPDATE_TABLE_USED, MYF(0), table->s->table_name.str);
-        DBUG_RETURN(0);
-      }
+      /* TODO: Don't free this */
+      release_table_share(share, RELEASE_NORMAL);
 
-      /*
-        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.
-      */
-      if (table->in_use != thd)
+      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
       {
-	VOID(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;
+
+      VOID(pthread_mutex_unlock(&LOCK_open));
       DBUG_RETURN(0);
     }
+
+    /*
+      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, share, table_share_release_hook);
   }
-  if (table)
+  else
   {
-    DBUG_PRINT("tcache", ("unused table: '%s'.'%s' 0x%lx", table->s->db.str,
-                          table->s->table_name.str, (long) table));
-    /* Unlink the table from "unused_tables" list. */
+    /*
+      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))
+    {
+      if (action)
+        *action= OT_BACK_OFF_AND_RETRY;
+      release_table_share(share, RELEASE_NORMAL);
+      VOID(pthread_mutex_unlock(&LOCK_open));
+      DBUG_RETURN(0);
+    }
+    /* Force close at once after usage */
+    thd->version= share->version;
+  }
+
+  if ((table= share->free_tables))
+  {
+    /* Unlink table from list of unused tables for this share. */
+    table_def_list_unlink_element(table);
+    /* Unlink able from global unused tables list. */
     if (table == unused_tables)
     {						// First unused
       unused_tables=unused_tables->next;	// Remove from link
@@ -2847,89 +2850,77 @@ TABLE *open_table(THD *thd, TABLE_LIST *
     }
     table->prev->next=table->next;		/* Remove from unused list */
     table->next->prev=table->prev;
+    /* Add table to list of used tables for this share. */
+    table_def_list_add_element(&share->used_tables, table);
+
     table->in_use= thd;
+
+    /* We need to release share as we have EXTRA reference to it in our hands. */
+    release_table_share(share, RELEASE_NORMAL);
   }
   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)
-      VOID(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)
-    {
-      bool exists;
+    /* make a new table */
+    if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
+      goto err_unlock;
 
-      if (check_if_table_exists(thd, table_list, &exists))
-      {
-        VOID(pthread_mutex_unlock(&LOCK_open));
-        DBUG_RETURN(NULL);
-      }
+    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 (!exists)
+    if (error)
+    {
+      my_free(table, MYF(0));
+
+      if (action)
       {
-        /*
-          Table to be created, so we need to create placeholder in table-cache.
-        */
-        if (!(table= table_cache_insert_placeholder(thd, key, key_length)))
+        if (error == 7)
         {
-          VOID(pthread_mutex_unlock(&LOCK_open));
-          DBUG_RETURN(NULL);
+          share->version= 0;
+          *action= OT_DISCOVER;
+        }
+        else if (share->crashed)
+        {
+          share->version= 0;
+          *action= OT_REPAIR;
         }
-        /*
-          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;
-        VOID(pthread_mutex_unlock(&LOCK_open));
-        DBUG_RETURN(table);
       }
-      /* Table exists. Let us try to open it. */
-    }
 
-    /* make a new table */
-    if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
-    {
-      VOID(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))
     {
-      my_free((uchar*)table, MYF(0));
-      VOID(pthread_mutex_unlock(&LOCK_open));
-      DBUG_RETURN(NULL);
+      closefrm(table, 0);
+      my_free(table, MYF(0));
+      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));
-      VOID(pthread_mutex_unlock(&LOCK_open));
-      DBUG_RETURN(0); // VIEW
-    }
-    DBUG_PRINT("info", ("inserting table '%s'.'%s' 0x%lx into the cache",
-                        table->s->db.str, table->s->table_name.str,
-                        (long) table));
-    VOID(my_hash_insert(&open_cache,(uchar*) table));
+    /* Add table to the share's used tables list. */
+    table_def_list_add_element(&share->used_tables, table);
+
+    table_cache_count++;
   }
 
   check_unused();				// Debugging call
 
   VOID(pthread_mutex_unlock(&LOCK_open));
-  if (refresh)
+
+  // Table existed
+  if (table_list->open_table_type == TABLE_LIST::OPEN_OR_CREATE)
+    mdl_downgrade_exclusive_locks(&thd->mdl_context);
+
+  table->mdl_lock= mdl_lock;
+  if (action)
   {
     table->next=thd->open_tables;		/* Link into simple list */
     thd->open_tables=table;
@@ -2969,15 +2960,32 @@ TABLE *open_table(THD *thd, TABLE_LIST *
   table->clear_column_bitmaps();
   DBUG_ASSERT(table->key_read == 0);
   DBUG_RETURN(table);
+
+err_unlock:
+  release_table_share(share, RELEASE_NORMAL);
+err_unlock2:
+  VOID(pthread_mutex_unlock(&LOCK_open));
+  mdl_release_lock(&thd->mdl_context, mdl_lock);
+  DBUG_RETURN(0);
 }
 
 
-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,6 +2995,41 @@ TABLE *find_locked_table(THD *thd, const
 }
 
 
+/**
+   Find write locked instance of table in the list of open tables,
+   emit error if no such instance found.
+
+   @param thd        List of TABLE objects to be searched
+   @param db         Database name.
+   @param table_name Name of table.
+
+   @return Pointer to write-locked TABLE instance, 0 - otherwise.
+*/
+
+TABLE *find_write_locked_table(TABLE *list, const char *db, const char *table_name)
+{
+  TABLE *tab= find_locked_table(list, db, table_name);
+
+  if (!tab)
+  {
+    my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_name);
+    return 0;
+  }
+  else
+  {
+    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;
+    }
+  }
+  return tab;
+}
+
+
 /*
   Reopen an table because the definition has changed.
 
@@ -3029,14 +3072,10 @@ bool reopen_table(TABLE *table)
   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))
+  if (reopen_table_entry(thd, &tmp, &table_list,
+                         table->alias,
+                         table->s->table_cache_key.str,
+                         table->s->table_cache_key.length))
     goto end;
 
   /* This list copies variables set by open_table */
@@ -3068,6 +3107,8 @@ bool reopen_table(TABLE *table)
     VOID(closefrm(&tmp, 1)); // close file, free everything
     goto end;
   }
+  tmp.mdl_lock=         table->mdl_lock;
+  table_def_list_unlink_element(table);
 
   delete table->triggers;
   if (table->file)
@@ -3077,6 +3118,8 @@ bool reopen_table(TABLE *table)
   table->default_column_bitmaps();
   table->file->change_table_ptr(table, table->s);
 
+  table_def_list_add_element(&table->s->used_tables, table);
+
   DBUG_ASSERT(table->alias != 0);
   for (field=table->field ; *field ; field++)
   {
@@ -3167,6 +3210,7 @@ void close_data_files_and_morph_locks(TH
           mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
       }
       table->open_placeholder= 1;
+      table->s->version= 0;
       close_handle_and_leave_table_as_lock(table);
     }
   }
@@ -3235,8 +3279,6 @@ static bool reattach_merge(THD *thd, TAB
 
     @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.
 
     @note Since this function can't properly handle prelocking and
           create placeholders it should be used in very special
@@ -3250,11 +3292,11 @@ static bool reattach_merge(THD *thd, TAB
     @return FALSE in case of success, TRUE - otherwise.
 */
 
-bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old)
+bool reopen_tables(THD *thd, bool get_locks)
 {
   TABLE *table,*next,**prev;
   TABLE **tables,**tables_ptr;			// For locks
-  TABLE *err_tables= NULL;
+  TABLE *err_tables= NULL, *err_tab_tmp;
   bool error=0, not_used;
   bool merge_table_found= FALSE;
   const uint flags= MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN |
@@ -3309,7 +3351,7 @@ bool reopen_tables(THD *thd, bool get_lo
       */
       if (table->child_l || table->parent)
         detach_merge_children(table, TRUE);
-      VOID(hash_delete(&open_cache,(uchar*) table));
+      free_cache_entry(table);
       error=1;
     }
     else
@@ -3320,11 +3362,15 @@ bool reopen_tables(THD *thd, bool get_lo
       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;
+	*tables_ptr++= table;			// need new lock on this
+        /*
+          We rely on having exclusive metadata lock on the table to be
+          able safely re-acquire table locks on it.
+        */
+        DBUG_ASSERT(mdl_is_exclusive_lock_owner(&thd->mdl_context, 0,
+                                                table->s->db.str,
+                                                table->s->table_name.str));
       }
     }
   }
@@ -3338,8 +3384,9 @@ bool reopen_tables(THD *thd, bool get_lo
   {
     while (err_tables)
     {
-      VOID(hash_delete(&open_cache, (uchar*) err_tables));
-      err_tables= err_tables->next;
+      err_tab_tmp= err_tables->next;
+      free_cache_entry(err_tables);
+      err_tables= err_tab_tmp;
     }
   }
   DBUG_PRINT("tcache", ("open tables to lock: %u",
@@ -3487,41 +3534,23 @@ bool table_is_used(TABLE *table, bool wa
   {
     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: 0x%lx  "
-                          "open_placeholder: %d  locked_by_name: %d "
-                          "db_stat: %u  version: %lu",
-                          (ulong) 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);
-    }
+    /* Note that 'table' can use artificial TABLE_SHARE object. */
+    TABLE_SHARE *share= (TABLE_SHARE*)hash_search(&table_def_cache,
+                                                  (uchar*) key, key_length);
+    if (share && share->used_tables && share->version !=
refresh_version)
+      DBUG_RETURN(1);
   } while ((table=table->next));
   DBUG_RETURN(0);
 }
 
 
-/* Wait until all used tables are refreshed */
+/*
+  Wait until all used tables are refreshed.
+
+  FIXME We should remove this function since for several functions which
+        are invoked by it new scenarios of usage are introduced, while
+        this function implements optimization useful only in rare cases.
+*/
 
 bool wait_for_tables(THD *thd)
 {
@@ -3546,7 +3575,7 @@ bool wait_for_tables(THD *thd)
     /* 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);
+    result=reopen_tables(thd, 0);
   }
   pthread_mutex_unlock(&LOCK_open);
   thd_proc_info(thd, 0);
@@ -3554,111 +3583,27 @@ bool wait_for_tables(THD *thd)
 }
 
 
-/*
-  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
-
-    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.
+/**
+   Unlock and close tables open and locked by LOCK TABLES statement.
 
-  RETURN
-    #    If table existed, return table
-    0	 Table was not locked
+   @param thd Current thread's context.
 */
 
-
-TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name)
+void unlock_locked_tables(THD *thd)
 {
-  TABLE *table,*next,**prev, *found= 0;
-  prev= &thd->open_tables;
-  DBUG_ENTER("drop_locked_tables");
-
-  /*
-    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)
-  {
-    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);
+  DBUG_ASSERT(!thd->in_sub_stmt &&
+              !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
 
-      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 */
-        VOID(hash_delete(&open_cache,(uchar*) table));
-      }
-    }
-    else
-    {
-      *prev=table;
-      prev= &table->next;
-    }
-  }
-  *prev=0;
-  if (found)
-    broadcast_refresh();
-  if (thd->locked_tables && thd->locked_tables->table_count == 0)
+  if (thd->locked_tables)
   {
-    my_free((uchar*) thd->locked_tables,MYF(0));
+    thd->lock= thd->locked_tables;
     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))
-    {
-      /* If MERGE child, forward lock handling to parent. */
-      mysql_lock_abort(thd, table->parent ? table->parent : table, TRUE);
-      break;
-    }
+    close_thread_tables(thd);
+    /*
+      After closing tables we can free memory used for storing lock
+      request objects for metadata locks
+     */
+    free_root(&thd->locked_tables_root, MYF(MY_MARK_BLOCKS_FREE));
   }
 }
 
@@ -3718,41 +3663,98 @@ void assign_new_table_id(TABLE_SHARE *sh
   DBUG_VOID_RETURN;
 }
 
-/*
-  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.
+*/
+
+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;
+
+  VOID(pthread_mutex_lock(&LOCK_open));
+
+  if (!(share= get_table_share_with_create(thd, table_list, cache_key,
+                                           cache_key_length, 
+                                           OPEN_VIEW, &error)))
+    goto err;
+
+  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))
+  {
+    release_table_share(share, RELEASE_NORMAL);
+    VOID(pthread_mutex_unlock(&LOCK_open));
+    return FALSE;
+  }
+
+  my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str, "VIEW");
+  release_table_share(share, RELEASE_NORMAL);
+err:
+  VOID(pthread_mutex_unlock(&LOCK_open));
+  return TRUE;
+}
+
+
+/**
+   Load table definition from file and open table while holding exclusive
+   meta-data lock on it.
+
+   @param thd               Thread handle
+   @param entry             Memory for TABLE object to be created
+   @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
+
+   @note This auxiliary function is mostly inteded for re-opening table
+         in situations when we hold exclusive meta-data lock. It is not
+         intended for normal case in which we have only shared meta-data
+         lock on the table to be open.
+
+   @note Extra argument for open is taken from thd->open_options.
+
+   @note One must have a lock on LOCK_open as well as exclusive meta-data
+         lock on the table when calling this function.
+
+   @return FALSE in case of 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)
+static bool reopen_table_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list,
+                               const char *alias, char *cache_key,
+                               uint cache_key_length)
 {
   int error;
   TABLE_SHARE *share;
   uint discover_retry_count= 0;
-  DBUG_ENTER("open_unireg_entry");
+  DBUG_ENTER("reopen_table_entry");
 
   safe_mutex_assert_owner(&LOCK_open);
+  DBUG_ASSERT(mdl_is_exclusive_lock_owner(&thd->mdl_context, 0,
+                                          table_list->db,
+                                          table_list->table_name));
+
 retry:
   if (!(share= get_table_share_with_create(thd, table_list, cache_key,
                                            cache_key_length, 
@@ -3765,20 +3767,11 @@ retry:
   {
     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 */
+    /* Attempt to reopen view will bring havoc to upper layers anyway. */
     release_table_share(share, RELEASE_NORMAL);
-    DBUG_RETURN((flags & OPEN_VIEW_NO_PARSE)? -1 : 0);
+    my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str,
+             "BASE TABLE");
+    DBUG_RETURN(1);
   }
 
   if (table_list->i_s_requested_object &  OPEN_VIEW_ONLY)
@@ -3800,89 +3793,68 @@ retry:
         goto err;
 
       /*
-        TODO:
-        Here we should wait until all threads has released the table.
-        For now we do one retry. This may cause a deadlock if there
-        is other threads waiting for other tables used by this thread.
-        
-        Proper fix would be to if the second retry failed:
-        - Mark that table def changed
-        - Return from open table
-        - Close all tables used by this thread
-        - Start waiting that the share is released
-        - Retry by opening all tables again
+        Since we have exclusive metadata lock on the table here the only
+        practical case when share->ref_count != 1 is when we have several
+        instances of the table opened by this thread (i.e we are under LOCK
+        TABLES).
       */
+      if (share->ref_count != 1)
+        goto err;
+
+      release_table_share(share, RELEASE_NORMAL);
+
       if (ha_create_table_from_engine(thd, table_list->db,
                                       table_list->table_name))
         goto err;
-      /*
-        TO BE FIXED
-        To avoid deadlock, only wait for release if no one else is
-        using the share.
-      */
-      if (share->ref_count != 1)
-        goto err;
-      /* Free share and wait until it's released by all threads */
-      release_table_share(share, RELEASE_WAIT_FOR_DROP);
-      if (!thd->killed)
-      {
-        mysql_reset_errors(thd, 1);         // Clear warnings
-        thd->clear_error();                 // Clear error message
-        goto retry;
-      }
-      DBUG_RETURN(1);
+
+      mysql_reset_errors(thd, 1);         // Clear warnings
+      thd->clear_error();                 // Clear error message
+
+      goto retry;
     }
     if (!entry->s || !entry->s->crashed)
       goto err;
-     // Code below is for repairing a crashed file
-     if ((error= lock_table_name(thd, table_list, TRUE)))
-     {
-       if (error < 0)
- 	goto err;
-       if (wait_for_locked_table_names(thd, table_list))
-       {
- 	unlock_table_name(thd, table_list);
- 	goto err;
-       }
-     }
-     pthread_mutex_unlock(&LOCK_open);
-     thd->clear_error();				// Clear error message
-     error= 0;
-     if (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,
-                               ha_open_options | HA_OPEN_FOR_REPAIR,
-                               entry, OTM_OPEN) || ! entry->file ||
-        (entry->file->is_crashed() &&
entry->file->ha_check_and_repair(thd)))
-     {
-       /* Give right error message */
-       thd->clear_error();
-       my_error(ER_NOT_KEYFILE, MYF(0), share->table_name.str, my_errno);
-       sql_print_error("Couldn't repair table: %s.%s", share->db.str,
-                       share->table_name.str);
-       if (entry->file)
- 	closefrm(entry, 0);
-       error=1;
-     }
-     else
-       thd->clear_error();			// Clear error message
-     pthread_mutex_lock(&LOCK_open);
-     unlock_table_name(thd, table_list);
- 
-     if (error)
-       goto err;
-     break;
-   }
 
-  if (Table_triggers_list::check_n_load(thd, share->db.str,
-                                        share->table_name.str, entry, 0))
+    entry->s->version= 0;
+
+    /* TODO: We don't need to release share here. */
+    release_table_share(share, RELEASE_NORMAL);
+    pthread_mutex_unlock(&LOCK_open);
+    error= (int)auto_repair_table(thd, table_list);
+    pthread_mutex_lock(&LOCK_open);
+
+    if (error)
+      goto err;
+
+    goto retry;
+  }
+
+  if (open_table_entry_fini(thd, share, entry))
   {
     closefrm(entry, 0);
     goto err;
   }
 
+  DBUG_RETURN(0);
+
+err:
+  release_table_share(share, RELEASE_NORMAL);
+  DBUG_RETURN(1);
+}
+
+
+/**
+   Auxiliary routine which finalizes process of TABLE object creation
+   by loading triggers and handling implicitly emptied tables.
+*/
+
+static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry)
+{
+
+  if (Table_triggers_list::check_n_load(thd, share->db.str,
+                                        share->table_name.str, entry, 0))
+    return TRUE;
+
   /*
     If we are here, there was no fatal error (but error may be still
     unitialized).
@@ -3912,18 +3884,141 @@ retry:
         */
         sql_print_error("When opening HEAP table, could not allocate memory "
                         "to write 'DELETE FROM `%s`.`%s`' to the binary log",
-                        table_list->db, table_list->table_name);
+                        share->db.str, share->table_name.str);
         delete entry->triggers;
-        closefrm(entry, 0);
-        goto err;
+        return TRUE;
       }
     }
   }
-  DBUG_RETURN(0);
+  return FALSE;
+}
 
-err:
+
+/**
+   Auxiliary routine which is used for performing automatical table repair.
+*/
+
+static bool auto_repair_table(THD *thd, TABLE_LIST *table_list)
+{
+  char	cache_key[MAX_DBKEY_LENGTH];
+  uint	cache_key_length;
+  TABLE_SHARE *share;
+  TABLE *entry;
+  int not_used;
+  bool result= FALSE;
+
+  cache_key_length= create_table_def_key(thd, cache_key, table_list, 0);
+
+  thd->clear_error();
+
+  pthread_mutex_lock(&LOCK_open);
+
+  if (!(share= get_table_share_with_create(thd, table_list, cache_key,
+                                           cache_key_length,
+                                           OPEN_VIEW, &not_used)))
+  {
+    pthread_mutex_unlock(&LOCK_open);
+    return TRUE;
+  }
+
+  if (share->is_view)
+    goto end_with_lock_open;
+
+  if (!(entry= (TABLE*)my_malloc(sizeof(TABLE), MYF(MY_WME))))
+  {
+    result= TRUE;
+    goto end_with_lock_open;
+  }
+  share->version= 0;
+  pthread_mutex_unlock(&LOCK_open);
+
+  if (open_table_from_share(thd, share, table_list->alias,
+                            (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
+                                    HA_GET_INDEX |
+                                    HA_TRY_READ_ONLY),
+                            READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD,
+                            ha_open_options | HA_OPEN_FOR_REPAIR,
+                            entry, OTM_OPEN) || ! entry->file ||
+      (entry->file->is_crashed() &&
entry->file->ha_check_and_repair(thd)))
+  {
+    /* Give right error message */
+    thd->clear_error();
+    my_error(ER_NOT_KEYFILE, MYF(0), share->table_name.str, my_errno);
+    sql_print_error("Couldn't repair table: %s.%s", share->db.str,
+                    share->table_name.str);
+    if (entry->file)
+      closefrm(entry, 0);
+    result= TRUE;
+  }
+  else
+  {
+    thd->clear_error();			// Clear error message
+    closefrm(entry, 0);
+  }
+  my_free(entry, MYF(0));
+
+  pthread_mutex_lock(&LOCK_open);
+
+end_with_lock_open:
   release_table_share(share, RELEASE_NORMAL);
-  DBUG_RETURN(1);
+  pthread_mutex_unlock(&LOCK_open);
+  return result;
+}
+
+
+/**
+   Handle failed attempt ot open table by performing requested action.
+
+   @param  thd     Thread context
+   @param  table   Table list element for table that caused problem
+   @param  action  Type of action requested by failed open_table() call
+
+   @retval FALSE - Success. One should try to open tables once again.
+   @retval TRUE  - Error
+*/
+
+static bool handle_failed_open_table_attempt(THD *thd, TABLE_LIST *table,
+                                             enum_open_table_action action)
+{
+  bool result= FALSE;
+
+  switch (action)
+  {
+    case OT_BACK_OFF_AND_RETRY:
+      result= (mdl_wait_for_locks(&thd->mdl_context) ||
+               tdc_wait_for_old_versions(thd, &thd->mdl_context));
+      mdl_remove_all_locks(&thd->mdl_context);
+      break;
+    case OT_DISCOVER:
+      mdl_set_lock_type(table->mdl_lock, MDL_EXCLUSIVE);
+      mdl_add_lock(&thd->mdl_context, table->mdl_lock);
+      if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+        return TRUE;
+      pthread_mutex_lock(&LOCK_open);
+      expel_table_from_cache(0, table->db, table->table_name);
+      ha_create_table_from_engine(thd, table->db, table->table_name);
+      pthread_mutex_unlock(&LOCK_open);
+
+      mysql_reset_errors(thd, 1);         // Clear warnings
+      thd->clear_error();                 // Clear error message
+      mdl_release_exclusive_locks(&thd->mdl_context);
+      break;
+    case OT_REPAIR:
+      mdl_set_lock_type(table->mdl_lock, MDL_EXCLUSIVE);
+      mdl_add_lock(&thd->mdl_context, table->mdl_lock);
+      if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+        return TRUE;
+      pthread_mutex_lock(&LOCK_open);
+      expel_table_from_cache(0, table->db, table->table_name);
+      pthread_mutex_unlock(&LOCK_open);
+
+      result= auto_repair_table(thd, table);
+      mdl_release_exclusive_locks(&thd->mdl_context);
+      break;
+    default:
+      DBUG_ASSERT(0);
+  }
+  return result;
 }
 
 
@@ -4292,7 +4387,7 @@ bool fix_merge_after_open(TABLE_LIST *ol
 int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
 {
   TABLE_LIST *tables= NULL;
-  bool refresh;
+  enum_open_table_action action;
   int result=0;
   MEM_ROOT new_frm_mem;
   /* Also used for indicating that prelocking is need */
@@ -4313,6 +4408,18 @@ int open_tables(THD *thd, TABLE_LIST **s
   thd_proc_info(thd, "Opening tables");
 
   /*
+    Close HANDLER tables which are marked for flush or against which there
+    are pending exclusive metadata locks. Note that we do this not to avoid
+    deadlocks (calls to mysql_ha_flush() in mdl_wait_for_locks() and
+    tdc_wait_for_old_version() are enough for this) but in order to have
+    a point during statement execution at which such HANDLERs are closed
+    even if they don't create problems for current thread (i.e. to avoid
+    having DDL blocked by HANDLERs opened for long time).
+  */
+  if (thd->handler_tables)
+    mysql_ha_flush(thd);
+
+  /*
     If we are not already executing prelocked statement and don't have
     statement for which table list for prelocking is already built, let
     us cache routines and try to build such table list.
@@ -4364,9 +4471,18 @@ int open_tables(THD *thd, TABLE_LIST **s
      */
     if (tables->derived)
     {
-      if (tables->view)
-        goto process_view_routines;
-      continue;
+      if (!tables->view)
+        continue;
+      /*
+        We restore view's name and database wiped out by derived tables
+        processing and fall back to standard open process in order to
+        obtain proper metadata locks and do other necessary steps like
+        stored routine processing.
+      */
+      tables->db= tables->view_db.str;
+      tables->db_length= tables->view_db.length;
+      tables->table_name= tables->view_name.str;
+      tables->table_name_length= tables->view_name.length;
     }
     /*
       If this TABLE_LIST object is a placeholder for an information_schema
@@ -4398,12 +4514,12 @@ int open_tables(THD *thd, TABLE_LIST **s
         */
         Prelock_error_handler prelock_handler;
         thd->push_internal_handler(& prelock_handler);
-        tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags);
+        tables->table= open_table(thd, tables, &new_frm_mem, &action, flags);
         thd->pop_internal_handler();
         safe_to_ignore_table= prelock_handler.safely_trapped_errors();
       }
       else
-        tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags);
+        tables->table= open_table(thd, tables, &new_frm_mem, &action, flags);
     }
     else
       DBUG_PRINT("tcache", ("referenced table: '%s'.'%s' 0x%lx",
@@ -4453,7 +4569,16 @@ int open_tables(THD *thd, TABLE_LIST **s
         parent_l->next_global= *parent_l->table->child_last_l;
       }
 
-      if (refresh)				// Refresh in progress
+      /*
+        FIXME This is a temporary hack. Actually we need check that will
+        allow us to differentiate between error while opening/creating
+        table and successful table creation.
+        ...
+      */
+      if (tables->open_table_type)
+        continue;
+
+      if (action)
       {
         /*
           We have met name-locked or old version of table. Now we have
@@ -4471,7 +4596,17 @@ int open_tables(THD *thd, TABLE_LIST **s
         */
         if (query_tables_last_own)
           thd->lex->mark_as_requiring_prelocking(query_tables_last_own);
-        close_tables_for_reopen(thd, start);
+        close_tables_for_reopen(thd, start, (action == OT_BACK_OFF_AND_RETRY));
+        /*
+          Here we rely on the fact that 'tables' still points to the valid
+          TABLE_LIST element. Altough currently this assumption is valid
+          it may change in future.
+        */
+        if (handle_failed_open_table_attempt(thd, tables, action))
+        {
+          result= -1;
+          goto err;
+        }
 	goto restart;
       }
 
@@ -4711,6 +4846,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST 
                    uint lock_flags)
 {
   TABLE *table;
+  enum_open_table_action action;
   bool refresh;
   DBUG_ENTER("open_ltable");
 
@@ -4718,9 +4854,14 @@ TABLE *open_ltable(THD *thd, TABLE_LIST 
   thd->current_tablenr= 0;
   /* open_ltable can be used only for BASIC TABLEs */
   table_list->required_type= FRMTYPE_TABLE;
-  while (!(table= open_table(thd, table_list, thd->mem_root, &refresh, 0))
&&
-         refresh)
-    ;
+
+retry:
+  while (!(table= open_table(thd, table_list, thd->mem_root, &action, 0))
&&
+         action)
+  {
+    if (handle_failed_open_table_attempt(thd, table_list, action))
+      break;
+  }
 
   if (table)
   {
@@ -4748,8 +4889,22 @@ TABLE *open_ltable(THD *thd, TABLE_LIST 
       DBUG_ASSERT(thd->lock == 0);	// You must lock everything at once
       if ((table->reginfo.lock_type= lock_type) != TL_UNLOCK)
 	if (! (thd->lock= mysql_lock_tables(thd, &table_list->table, 1,
-                                            lock_flags, &refresh)))
-	  table= 0;
+                                            (lock_flags |
+                                             MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN),
+                                            &refresh)))
+        {
+          /*
+            FIXME: Actually we should get rid of MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN option
+                   as all reopening should happen outside of mysql_lock_tables() code.
+          */
+          if (refresh)
+          {
+            close_thread_tables(thd);
+            goto retry;
+          }
+          else
+            table= 0;
+        }
     }
   }
 
@@ -4805,7 +4960,7 @@ int open_and_lock_tables_derived(THD *th
       break;
     if (!need_reopen)
       DBUG_RETURN(-1);
-    close_tables_for_reopen(thd, &tables);
+    close_tables_for_reopen(thd, &tables, FALSE);
   }
   if (derived &&
       (mysql_handle_derived(thd->lex, &mysql_derived_prepare) ||
@@ -5138,6 +5293,10 @@ int lock_tables(THD *thd, TABLE_LIST *ta
           table->table->query_id= thd->query_id;
           if (check_lock_and_start_stmt(thd, table->table, table->lock_type))
           {
+            /*
+              This was an attempt to enter prelocked mode so there is no
+              need to care about THD::locked_tables_root here.
+            */
             mysql_unlock_tables(thd, thd->locked_tables);
             thd->locked_tables= 0;
             thd->options&= ~(OPTION_TABLE_LOCK);
@@ -5203,7 +5362,7 @@ int lock_tables(THD *thd, TABLE_LIST *ta
 
 */
 
-void close_tables_for_reopen(THD *thd, TABLE_LIST **tables)
+void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, bool skip_mdl)
 {
   /*
     If table list consists only from tables from prelocking set, table list
@@ -5215,7 +5374,7 @@ void close_tables_for_reopen(THD *thd, T
   sp_remove_not_own_routines(thd->lex);
   for (TABLE_LIST *tmp= *tables; tmp; tmp= tmp->next_global)
     tmp->table= 0;
-  close_thread_tables(thd);
+  close_thread_tables(thd, skip_mdl);
 }
 
 
@@ -8065,36 +8224,6 @@ my_bool mysql_rm_tmp_tables(void)
 *****************************************************************************/
 
 /*
-  Invalidate any cache entries that are for some DB
-
-  SYNOPSIS
-    remove_db_from_cache()
-    db		Database name. This will be in lower case if
-		lower_case_table_name is set
-
-  NOTE:
-  We can't use hash_delete when looping hash_elements. We mark them first
-  and afterwards delete those marked unused.
-*/
-
-void remove_db_from_cache(const char *db)
-{
-  for (uint idx=0 ; idx < open_cache.records ; idx++)
-  {
-    TABLE *table=(TABLE*) hash_element(&open_cache,idx);
-    if (!strcmp(table->s->db.str, db))
-    {
-      table->s->version= 0L;			/* Free when thread is ready */
-      if (!table->in_use)
-	relink_unused(table);
-    }
-  }
-  while (unused_tables && !unused_tables->s->version)
-    VOID(hash_delete(&open_cache,(uchar*) unused_tables));
-}
-
-
-/*
   free all unused tables
 
   NOTE
@@ -8106,7 +8235,7 @@ void flush_tables()
 {
   (void) pthread_mutex_lock(&LOCK_open);
   while (unused_tables)
-    hash_delete(&open_cache,(uchar*) unused_tables);
+    free_cache_entry(unused_tables);
   (void) pthread_mutex_unlock(&LOCK_open);
 }
 
@@ -8140,90 +8269,80 @@ bool remove_table_from_cache(THD *thd, c
   key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1;
   for (;;)
   {
-    HASH_SEARCH_STATE state;
     result= signalled= 0;
 
-    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 ((share= (TABLE_SHARE*)hash_search(&table_def_cache, (uchar*) key,
+                                          key_length)))
     {
-      THD *in_use;
-      DBUG_PRINT("tcache", ("found table: '%s'.'%s' 0x%lx", table->s->db.str,
-                            table->s->table_name.str, (long) table));
-
-      table->s->version=0L;		/* Free when thread is ready */
-      if (!(in_use=table->in_use))
-      {
-        DBUG_PRINT("info",("Table was not in use"));
+      share->version= 0;
+      for (table= share->free_tables; table; table= table->share_next)
         relink_unused(table);
-      }
-      else if (in_use != thd)
+
+      for (table= share->used_tables; table; table= table->share_next)
       {
-        DBUG_PRINT("info", ("Table was in use by other thread"));
-        /*
-          Mark that table is going to be deleted from cache. This will
-          force threads that are in mysql_lock_tables() (but not yet
-          in thr_multi_lock()) to abort it's locks, close all tables and retry
-        */
-        in_use->some_tables_deleted= 1;
-        if (table->is_name_opened())
-        {
-          DBUG_PRINT("info", ("Found another active instance of the table"));
-  	  result=1;
-        }
-        /* Kill delayed insert threads */
-        if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
-            ! in_use->killed)
+        THD *in_use= table->in_use;
+        DBUG_ASSERT(in_use);
+        if (in_use != thd)
         {
-	  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);
-            signalled= 1;
-	    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);
+          DBUG_PRINT("info", ("Table was in use by other thread"));
+          /*
+            Mark that table is going to be deleted from cache. This will
+            force threads that are in mysql_lock_tables() (but not yet
+            in thr_multi_lock()) to abort it's locks, close all tables and retry
+          */
+          in_use->some_tables_deleted= 1;
+
+          if (table->is_name_opened())
+          {
+            DBUG_PRINT("info", ("Found another active instance of the table"));
+            result=1;
+          }
+          /* Kill delayed insert threads */
+          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);
+              signalled= 1;
+              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);
+          }
+          /*
+            Now we must abort all tables locks used by this thread
+            as the thread may be waiting to get a lock for another table.
+            Note that we need to hold LOCK_open while going through the
+            list. So that the other thread cannot change it. The other
+            thread must also hold LOCK_open whenever changing the
+            open_tables list. Aborting the MERGE lock after a child was
+            closed and before the parent is closed would be fatal.
+          */
+          for (TABLE *thd_table= in_use->open_tables;
+               thd_table ;
+               thd_table= thd_table->next)
+          {
+            /* Do not handle locks of MERGE children. */
+            if (thd_table->db_stat && !thd_table->parent)	// If table is
open
+              signalled|= mysql_lock_abort_for_thread(thd, thd_table);
+          }
         }
-        /*
-	  Now we must abort all tables locks used by this thread
-	  as the thread may be waiting to get a lock for another table.
-          Note that we need to hold LOCK_open while going through the
-          list. So that the other thread cannot change it. The other
-          thread must also hold LOCK_open whenever changing the
-          open_tables list. Aborting the MERGE lock after a child was
-          closed and before the parent is closed would be fatal.
-        */
-        for (TABLE *thd_table= in_use->open_tables;
-	     thd_table ;
-	     thd_table= thd_table->next)
+        else
         {
-          /* Do not handle locks of MERGE children. */
-	  if (thd_table->db_stat && !thd_table->parent)	// If table is open
-	    signalled|= mysql_lock_abort_for_thread(thd, thd_table);
+          DBUG_PRINT("info", ("Table was in use by current thread. db_stat: %u",
+                     table->db_stat));
+          result= result || (flags & RTFC_OWNED_BY_THD_FLAG);
         }
       }
-      else
-      {
-        DBUG_PRINT("info", ("Table was in use by current thread. db_stat: %u",
-                            table->db_stat));
-        result= result || (flags & RTFC_OWNED_BY_THD_FLAG);
-      }
-    }
-    while (unused_tables && !unused_tables->s->version)
-      VOID(hash_delete(&open_cache,(uchar*) unused_tables));
-
-    DBUG_PRINT("info", ("Removing table from table_def_cache"));
-    /* Remove table from table definition cache if it's not in use */
-    if ((share= (TABLE_SHARE*) hash_search(&table_def_cache,(uchar*) key,
-                                           key_length)))
-    {
+
+      while (unused_tables && !unused_tables->s->version)
+        free_cache_entry(unused_tables);
+
       DBUG_PRINT("info", ("share version: %lu  ref_count: %u",
                           share->version, share->ref_count));
-      share->version= 0;                          // Mark for delete
       if (share->ref_count == 0)
       {
         pthread_mutex_lock(&share->mutex);
@@ -8270,6 +8389,159 @@ bool remove_table_from_cache(THD *thd, c
 }
 
 
+/**
+   A callback to the server internals that is used to address
+   special cases of the locking protocol.
+   Invoked when acquiring an exclusive lock, for each thread that
+   has a conflicting shared metadata lock.
+
+   This function:
+     - aborts waiting of the thread on a data lock, to make it notice
+       the pending exclusive lock and back off.
+     - if the thread is an INSERT DELAYED thread, sends it a KILL
+       signal to terminate it.
+
+   @note This function does not wait for the thread to give away its
+         locks. Waiting is done outside for all threads at once.
+
+   @param thd    Current thread context
+   @param in_use The thread to wake up
+
+   @retval  TRUE  if the thread was woken up
+   @retval  FALSE otherwise (e.g. it was not waiting for a table-level lock).
+
+   @note It is one of two places where border between MDL and the
+         rest of the server is broken.
+*/
+
+bool notify_thread_having_shared_lock(THD *thd, THD *in_use)
+{
+  bool signalled= FALSE;
+  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_cond_broadcast(in_use->mysys_var->current_cond);
+    pthread_mutex_unlock(&in_use->mysys_var->mutex);
+    signalled= TRUE;
+  }
+  pthread_mutex_lock(&LOCK_open);
+  for (TABLE *thd_table= in_use->open_tables;
+       thd_table ;
+       thd_table= thd_table->next)
+  {
+    /* TODO With new MDL check for db_stat is probably a legacy */
+    if (thd_table->db_stat)
+      signalled|= mysql_lock_abort_for_thread(thd, thd_table);
+  }
+  pthread_mutex_unlock(&LOCK_open);
+  return signalled;
+}
+
+
+/**
+   Remove all instances of the table from cache assuming that current thread
+   has exclusive meta-data lock on it (optionally leave instances belonging
+   to the current thread in cache).
+
+   @param  leave_thd  0      If we should remove all instances
+                      non-0  Pointer to current thread context if we should
+                      leave instances belonging to this thread.
+   @param  db         Name of database
+   @param  table_name Name of table
+
+   @note Unlike remove_table_from_cache() it assumes that table instances
+         are already not used by any (other) thread (this should be achieved
+         by using meta-data locks).
+*/
+
+void expel_table_from_cache(THD *leave_thd, const char *db, const char *table_name)
+{
+  char key[MAX_DBKEY_LENGTH];
+  uint key_length;
+  TABLE *table;
+  TABLE_SHARE *share;
+
+  key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1;
+
+  if ((share= (TABLE_SHARE*) hash_search(&table_def_cache,(uchar*) key,
+                                         key_length)))
+  {
+    share->version= 0;
+
+    for (table= share->free_tables; table; table= table->share_next)
+      relink_unused(table);
+  }
+
+  /* This may destroy share so we have to do new look-up later. */
+  while (unused_tables && !unused_tables->s->version)
+    free_cache_entry(unused_tables);
+
+  if ((share= (TABLE_SHARE*) hash_search(&table_def_cache,(uchar*) key,
+                                         key_length)))
+  {
+    DBUG_ASSERT(leave_thd || share->ref_count == 0);
+    if (share->ref_count == 0)
+    {
+      pthread_mutex_lock(&share->mutex);
+      VOID(hash_delete(&table_def_cache, (uchar*) share));
+    }
+  }
+}
+
+
+/**
+   Wait until there are no old versions of tables in the table
+   definition cache for the metadata locks that we try to acquire.
+
+   @param thd      Thread context
+   @param context  Metadata locking context with locks.
+*/
+
+static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context)
+{
+  MDL_LOCK *l;
+  TABLE_SHARE *share;
+  const char *old_msg;
+  LEX_STRING key;
+
+  while (!thd->killed)
+  {
+    /*
+      Here we have situation as in mdl_wait_for_locks() we need to
+      get rid of offending HANDLERs to avoid deadlock.
+      TODO: We should also investigate in which situations we have
+            to broadcast on COND_refresh because of this.
+    */
+    mysql_ha_flush(thd);
+    pthread_mutex_lock(&LOCK_open);
+
+    I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it= mdl_get_locks(context);
+    while ((l= it++))
+    {
+      mdl_get_tdc_key(l, &key);
+      if ((share= (TABLE_SHARE*) hash_search(&table_def_cache, (uchar*) key.str,
+                                             key.length)) &&
+          share->version != refresh_version &&
+          share->used_tables)
+        break;
+    }
+    if (!l)
+    {
+      pthread_mutex_unlock(&LOCK_open);
+      break;
+    }
+    old_msg= thd->enter_cond(&COND_refresh, &LOCK_open, "Waiting for table");
+    pthread_cond_wait(&COND_refresh, &LOCK_open);
+    /* LOCK_open mutex is unlocked by THD::exit_cond() as side-effect. */
+    thd->exit_cond(old_msg);
+  }
+  return thd->killed;
+}
+
+
 int setup_ftfuncs(SELECT_LEX *select_lex)
 {
   List_iterator<Item_func_match> li(*(select_lex->ftfunc_list)),
@@ -8367,7 +8639,6 @@ open_new_frm(THD *thd, TABLE_SHARE *shar
   }
  
 err:
-  bzero(outparam, sizeof(TABLE));	// do not run repair
   DBUG_RETURN(1);
 }
 
@@ -8400,15 +8671,23 @@ bool is_equal(const LEX_STRING *a, const
 
 int abort_and_upgrade_lock(ALTER_PARTITION_PARAM_TYPE *lpt)
 {
-  uint flags= RTFC_WAIT_OTHER_THREAD_FLAG | RTFC_CHECK_KILLED_FLAG;
   DBUG_ENTER("abort_and_upgrade_locks");
 
   lpt->old_lock_type= lpt->table->reginfo.lock_type;
-  VOID(pthread_mutex_lock(&LOCK_open));
   /* If MERGE child, forward lock handling to parent. */
   mysql_lock_abort(lpt->thd, lpt->table->parent ? lpt->table->parent :
                    lpt->table, TRUE);
-  VOID(remove_table_from_cache(lpt->thd, lpt->db, lpt->table_name, flags));
+  if (mdl_upgrade_shared_lock_to_exclusive(&lpt->thd->mdl_context, 0,
+                                           lpt->db, lpt->table_name))
+  {
+    mysql_lock_downgrade_write(lpt->thd,
+                               lpt->table->parent ? lpt->table->parent :
+                                                    lpt->table,
+                               lpt->old_lock_type);
+    DBUG_RETURN(1);
+  }
+  VOID(pthread_mutex_lock(&LOCK_open));
+  expel_table_from_cache(lpt->thd, lpt->db, lpt->table_name);
   VOID(pthread_mutex_unlock(&LOCK_open));
   DBUG_RETURN(0);
 }
@@ -8443,100 +8722,6 @@ void close_open_tables_and_downgrade(ALT
 
 
 /*
-  SYNOPSIS
-    mysql_wait_completed_table()
-    lpt                            Parameter passing struct
-    my_table                       My table object
-    All parameters passed through the ALTER_PARTITION_PARAM object
-  RETURN VALUES
-    TRUE                          Failure
-    FALSE                         Success
-  DESCRIPTION
-    We have changed the frm file and now we want to wait for all users of
-    the old frm to complete before proceeding to ensure that no one
-    remains that uses the old frm definition.
-    Start by ensuring that all users of the table will be removed from cache
-    once they are done. Then abort all that have stumbled on locks and
-    haven't been started yet.
-
-    thd                           Thread object
-    table                         Table object
-    db                            Database name
-    table_name                    Table name
-*/
-
-void mysql_wait_completed_table(ALTER_PARTITION_PARAM_TYPE *lpt, TABLE *my_table)
-{
-  char key[MAX_DBKEY_LENGTH];
-  uint key_length;
-  TABLE *table;
-  DBUG_ENTER("mysql_wait_completed_table");
-
-  key_length=(uint) (strmov(strmov(key,lpt->db)+1,lpt->table_name)-key)+1;
-  VOID(pthread_mutex_lock(&LOCK_open));
-  HASH_SEARCH_STATE state;
-  for (table= (TABLE*) hash_first(&open_cache,(uchar*) key,key_length,
-                                  &state) ;
-       table;
-       table= (TABLE*) hash_next(&open_cache,(uchar*) key,key_length,
-                                 &state))
-  {
-    THD *in_use= table->in_use;
-    table->s->version= 0L;
-    if (!in_use)
-    {
-      relink_unused(table);
-    }
-    else
-    {
-      /* Kill delayed insert threads */
-      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);
-      }
-      /*
-        Now we must abort all tables locks used by this thread
-        as the thread may be waiting to get a lock for another table.
-        Note that we need to hold LOCK_open while going through the
-        list. So that the other thread cannot change it. The other
-        thread must also hold LOCK_open whenever changing the
-        open_tables list. Aborting the MERGE lock after a child was
-        closed and before the parent is closed would be fatal.
-      */
-      for (TABLE *thd_table= in_use->open_tables;
-           thd_table ;
-           thd_table= thd_table->next)
-      {
-        /* Do not handle locks of MERGE children. */
-        if (thd_table->db_stat && !thd_table->parent) // If table is open
-          mysql_lock_abort_for_thread(lpt->thd, thd_table);
-      }
-    }
-  }
-  /*
-    We start by removing all unused objects from the cache and marking
-    those in use for removal after completion. Now we also need to abort
-    all that are locked and are not progressing due to being locked
-    by our lock. We don't upgrade our lock here.
-    If MERGE child, forward lock handling to parent.
-  */
-  mysql_lock_abort(lpt->thd, my_table->parent ? my_table->parent : my_table,
-                   FALSE);
-  VOID(pthread_mutex_unlock(&LOCK_open));
-  DBUG_VOID_RETURN;
-}
-
-
-/*
   Tells if two (or more) tables have auto_increment columns and we want to
   lock those tables with a write lock.
 
@@ -8612,10 +8797,13 @@ open_system_tables_for_read(THD *thd, TA
 {
   DBUG_ENTER("open_system_tables_for_read");
 
+  alloc_mdl_locks(table_list, thd->mem_root);
+
   thd->reset_n_backup_open_tables_state(backup);
 
   uint count= 0;
-  bool not_used;
+  enum_open_table_action not_used;
+  bool not_used_2;
   for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global)
   {
     TABLE *table= open_table(thd, tables, thd->mem_root, &not_used,
@@ -8638,7 +8826,7 @@ open_system_tables_for_read(THD *thd, TA
       *(ptr++)= tables->table;
 
     thd->lock= mysql_lock_tables(thd, list, count,
-                                 MYSQL_LOCK_IGNORE_FLUSH, &not_used);
+                                 MYSQL_LOCK_IGNORE_FLUSH, &not_used_2);
   }
   if (thd->lock)
     DBUG_RETURN(FALSE);
@@ -8690,6 +8878,8 @@ open_system_table_for_update(THD *thd, T
 {
   DBUG_ENTER("open_system_table_for_update");
 
+  alloc_mdl_locks(one_table, thd->mem_root);
+
   TABLE *table= open_ltable(thd, one_table, one_table->lock_type, 0);
   if (table)
   {
@@ -8726,6 +8916,7 @@ open_performance_schema_table(THD *thd, 
 
   thd->reset_n_backup_open_tables_state(backup);
 
+  alloc_mdl_locks(one_table, thd->mem_root);
   if ((table= open_ltable(thd, one_table, one_table->lock_type, flags)))
   {
     DBUG_ASSERT(table->s->table_category == TABLE_CATEGORY_PERFORMANCE);
@@ -8803,6 +8994,9 @@ void close_performance_schema_table(THD 
     broadcast_refresh();
 
   pthread_mutex_unlock(&LOCK_open);
+
+  mdl_release_locks(&thd->mdl_context);
+  mdl_remove_all_locks(&thd->mdl_context);
 
   thd->restore_backup_open_tables_state(backup);
 }
diff -Nrup a/sql/sql_binlog.cc b/sql/sql_binlog.cc
--- a/sql/sql_binlog.cc	2008-03-05 13:19:53 +03:00
+++ b/sql/sql_binlog.cc	2008-05-22 13:21:13 +04:00
@@ -234,7 +234,7 @@ void mysql_client_binlog_statement(THD* 
   my_ok(thd);
 
 end:
-  thd->rli_fake->clear_tables_to_lock();
+  thd->rli_fake->slave_close_thread_tables(thd);
   my_free(buf, MYF(MY_ALLOW_ZERO_PTR));
   DBUG_VOID_RETURN;
 }
diff -Nrup a/sql/sql_class.cc b/sql/sql_class.cc
--- a/sql/sql_class.cc	2008-04-22 13:53:05 +04:00
+++ b/sql/sql_class.cc	2008-05-22 13:21:13 +04:00
@@ -201,10 +201,10 @@ bool foreign_key_prefix(Key *a, Key *b)
 ** Thread specific functions
 ****************************************************************************/
 
-Open_tables_state::Open_tables_state(ulong version_arg)
+Open_tables_state::Open_tables_state(THD *thd, ulong version_arg)
   :version(version_arg), state_flags(0U)
 {
-  reset_open_tables_state();
+  reset_open_tables_state(thd);
 }
 
 /*
@@ -500,7 +500,7 @@ Diagnostics_area::disable_status()
 THD::THD()
    :Statement(&main_lex, &main_mem_root, CONVENTIONAL_EXECUTION,
               /* statement id */ 0),
-   Open_tables_state(refresh_version), rli_fake(0),
+   Open_tables_state(this, refresh_version), rli_fake(0),
    lock_id(&main_lock_id),
    user_time(0), in_sub_stmt(0),
    binlog_table_maps(0), binlog_flags(0UL),
@@ -526,7 +526,8 @@ THD::THD()
           This is needed to ensure the restore (which uses DDL) is not blocked
           when the DDL blocker is engaged.
   */
-   DDL_exception(FALSE)
+   DDL_exception(FALSE),
+   mdl_el_root(NULL)
 {
   ulong tmp;
 
@@ -636,6 +637,8 @@ THD::THD()
   thr_lock_owner_init(&main_lock_id, &lock_info);
 
   m_internal_handler= NULL;
+
+  init_sql_alloc(&locked_tables_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0);
 }
 
 
@@ -905,6 +908,9 @@ THD::~THD()
   if (!cleanup_done)
     cleanup();
 
+  mdl_context_destroy(&mdl_context);
+  mdl_context_destroy(&handler_mdl_context);
+
   ha_close_connection(this);
   mysql_audit_release(this);
   plugin_thdvar_cleanup(this);
@@ -932,6 +938,7 @@ THD::~THD()
 #endif
 
   free_root(&main_mem_root, MYF(0));
+  free_root(&locked_tables_root, MYF(0));
   DBUG_VOID_RETURN;
 }
 
@@ -2818,7 +2825,7 @@ void THD::reset_n_backup_open_tables_sta
 {
   DBUG_ENTER("reset_n_backup_open_tables_state");
   backup->set_open_tables_state(this);
-  reset_open_tables_state();
+  reset_open_tables_state(this);
   state_flags|= Open_tables_state::BACKUPS_AVAIL;
   DBUG_VOID_RETURN;
 }
@@ -2835,6 +2842,9 @@ void THD::restore_backup_open_tables_sta
               handler_tables == 0 && derived_tables == 0 &&
               lock == 0 && locked_tables == 0 &&
               prelocked_mode == NON_PRELOCKED);
+  mdl_context_destroy(&mdl_context);
+  mdl_context_destroy(&handler_mdl_context);
+
   set_open_tables_state(backup);
   DBUG_VOID_RETURN;
 }
diff -Nrup a/sql/sql_class.h b/sql/sql_class.h
--- a/sql/sql_class.h	2008-04-14 14:10:01 +04:00
+++ b/sql/sql_class.h	2008-05-22 13:21:13 +04:00
@@ -23,6 +23,7 @@
 #include <mysql/plugin_audit.h>
 #include "log.h"
 #include "rpl_tblmap.h"
+#include "mdl.h"
 
 class Relay_log_info;
 
@@ -926,25 +927,30 @@ public:
   */
   uint state_flags;
 
+  MDL_CONTEXT mdl_context;
+  MDL_CONTEXT handler_mdl_context;
+
   /*
     This constructor serves for creation of Open_tables_state instances
     which are used as backup storage.
   */
   Open_tables_state() : state_flags(0U) { }
 
-  Open_tables_state(ulong version_arg);
+  Open_tables_state(THD *thd, ulong version_arg);
 
   void set_open_tables_state(Open_tables_state *state)
   {
     *this= *state;
   }
 
-  void reset_open_tables_state()
+  void reset_open_tables_state(THD *thd)
   {
     open_tables= temporary_tables= handler_tables= derived_tables= 0;
     extra_lock= lock= locked_tables= 0;
     prelocked_mode= NON_PRELOCKED;
     state_flags= 0U;
+    mdl_context_init(&mdl_context, thd);
+    mdl_context_init(&handler_mdl_context, thd);
   }
 };
 
@@ -1812,6 +1818,9 @@ public:
   */
   unsigned long audit_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
 #endif
+
+  MEM_ROOT *mdl_el_root;
+  MEM_ROOT locked_tables_root;
 
   THD();
   ~THD();
diff -Nrup a/sql/sql_db.cc b/sql/sql_db.cc
--- a/sql/sql_db.cc	2008-04-14 14:10:01 +04:00
+++ b/sql/sql_db.cc	2008-05-22 13:21:13 +04:00
@@ -910,11 +910,6 @@ bool mysql_rm_db(THD *thd,char *db,bool 
   }
   else
   {
-    pthread_mutex_lock(&LOCK_open);
-    remove_db_from_cache(db);
-    pthread_mutex_unlock(&LOCK_open);
-
-    
     error= -1;
     if ((deleted= mysql_rm_known_files(thd, dirp, db, path, 0,
                                        &dropped_tables)) >= 0)
diff -Nrup a/sql/sql_delete.cc b/sql/sql_delete.cc
--- a/sql/sql_delete.cc	2008-04-14 14:10:01 +04:00
+++ b/sql/sql_delete.cc	2008-05-22 13:21:13 +04:00
@@ -982,6 +982,7 @@ bool mysql_truncate(THD *thd, TABLE_LIST
   TABLE *table;
   bool error;
   uint path_length;
+  MDL_LOCK *mdl_lock= 0;
   DBUG_ENTER("mysql_truncate");
 
   bzero((char*) &create_info,sizeof(create_info));
@@ -1032,8 +1033,20 @@ bool mysql_truncate(THD *thd, TABLE_LIST
                                       HTON_CAN_RECREATE))
       goto trunc_by_del;
 
-    if (lock_and_wait_for_table_name(thd, table_list))
+    /*
+      FIXME: Actually code of TRUNCATE breaks meta-data locking protocol since
+             tries to get table enging and therefore accesses table in some way
+             without holding any kind of meta-data lock.
+    */
+    mdl_lock= mdl_alloc_lock(0, table_list->db, table_list->table_name,
+                             thd->mem_root);
+    mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE);
+    mdl_add_lock(&thd->mdl_context, mdl_lock);
+    if (mdl_acquire_exclusive_locks(&thd->mdl_context))
       DBUG_RETURN(TRUE);
+    VOID(pthread_mutex_lock(&LOCK_open));
+    expel_table_from_cache(0, table_list->db, table_list->table_name);
+    VOID(pthread_mutex_unlock(&LOCK_open));
   }
 
   // Remove the .frm extension AIX 5.2 64-bit compiler bug (BUG#16155): this
@@ -1058,15 +1071,13 @@ end:
       write_bin_log(thd, TRUE, thd->query, thd->query_length);
       my_ok(thd);		// This should return record count
     }
-    VOID(pthread_mutex_lock(&LOCK_open));
-    unlock_table_name(thd, table_list);
-    VOID(pthread_mutex_unlock(&LOCK_open));
+    if (mdl_lock)
+      mdl_release_lock(&thd->mdl_context, mdl_lock);
   }
   else if (error)
   {
-    VOID(pthread_mutex_lock(&LOCK_open));
-    unlock_table_name(thd, table_list);
-    VOID(pthread_mutex_unlock(&LOCK_open));
+    if (mdl_lock)
+      mdl_release_lock(&thd->mdl_context, mdl_lock);
   }
   DBUG_RETURN(error);
 
diff -Nrup a/sql/sql_handler.cc b/sql/sql_handler.cc
--- a/sql/sql_handler.cc	2008-02-19 15:59:07 +03:00
+++ b/sql/sql_handler.cc	2008-05-22 13:21:13 +04:00
@@ -116,17 +116,16 @@ static void mysql_ha_hash_free(TABLE_LIS
 
   @param thd Thread identifier.
   @param tables A list of tables with the first entry to close.
-  @param is_locked If LOCK_open is locked.
 
   @note Though this function takes a list of tables, only the first list entry
   will be closed.
   @note Broadcasts refresh if it closed a table with old version.
 */
 
-static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables,
-                                 bool is_locked)
+static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables)
 {
   TABLE **table_ptr;
+  MDL_LOCK *mdl_lock;
 
   /*
     Though we could take the table pointer from hash_tables->table,
@@ -142,15 +141,15 @@ static void mysql_ha_close_table(THD *th
   if (*table_ptr)
   {
     (*table_ptr)->file->ha_index_or_rnd_end();
-    if (! is_locked)
-      VOID(pthread_mutex_lock(&LOCK_open));
+    mdl_lock= (*table_ptr)->mdl_lock;
+    VOID(pthread_mutex_lock(&LOCK_open));
     if (close_thread_table(thd, table_ptr))
     {
       /* Tell threads waiting for refresh that something has happened */
       broadcast_refresh();
     }
-    if (! is_locked)
-      VOID(pthread_mutex_unlock(&LOCK_open));
+    VOID(pthread_mutex_unlock(&LOCK_open));
+    mdl_release_lock(&thd->handler_mdl_context, mdl_lock);
   }
   else if (tables->table)
   {
@@ -188,10 +187,12 @@ static void mysql_ha_close_table(THD *th
 bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
 {
   TABLE_LIST    *hash_tables = NULL;
-  char          *db, *name, *alias;
+  MDL_LOCK      *mdl_lock;
+  char          *db, *name, *alias, *mdlkey;
   uint          dblen, namelen, aliaslen, counter;
   int           error;
   TABLE         *backup_open_tables;
+  MDL_CONTEXT   backup_mdl_context;
   DBUG_ENTER("mysql_ha_open");
   DBUG_PRINT("enter",("'%s'.'%s' as '%s'  reopen: %d",
                       tables->db, tables->table_name, tables->alias,
@@ -206,7 +207,10 @@ bool mysql_ha_open(THD *thd, TABLE_LIST 
                   HANDLER_TABLES_HASH_SIZE, 0, 0,
                   (hash_get_key) mysql_ha_hash_get_key,
                   (hash_free_key) mysql_ha_hash_free, 0))
-      goto err;
+    {
+      DBUG_PRINT("exit",("ERROR"));
+      DBUG_RETURN(TRUE);
+    }
   }
   else if (! reopen) /* Otherwise we have 'tables' already. */
   {
@@ -214,12 +218,52 @@ bool mysql_ha_open(THD *thd, TABLE_LIST 
                     strlen(tables->alias) + 1))
     {
       DBUG_PRINT("info",("duplicate '%s'", tables->alias));
-      if (! reopen)
-        my_error(ER_NONUNIQ_TABLE, MYF(0), tables->alias);
-      goto err;
+      DBUG_PRINT("exit",("ERROR"));
+      my_error(ER_NONUNIQ_TABLE, MYF(0), tables->alias);
+      DBUG_RETURN(TRUE);
     }
   }
 
+  if (! reopen)
+  {
+    /* copy the TABLE_LIST struct */
+    dblen= strlen(tables->db) + 1;
+    namelen= strlen(tables->table_name) + 1;
+    aliaslen= strlen(tables->alias) + 1;
+    if (!(my_multi_malloc(MYF(MY_WME),
+                          &hash_tables, (uint) sizeof(*hash_tables),
+                          &db, (uint) dblen,
+                          &name, (uint) namelen,
+                          &alias, (uint) aliaslen,
+                          &mdl_lock, sizeof(MDL_LOCK),
+                          &mdlkey, MAX_DBKEY_LENGTH,
+                          NullS)))
+    {
+      DBUG_PRINT("exit",("ERROR"));
+      DBUG_RETURN(TRUE);
+    }
+    /* structure copy */
+    *hash_tables= *tables;
+    hash_tables->db= db;
+    hash_tables->table_name= name;
+    hash_tables->alias= alias;
+    memcpy(hash_tables->db, tables->db, dblen);
+    memcpy(hash_tables->table_name, tables->table_name, namelen);
+    memcpy(hash_tables->alias, tables->alias, aliaslen);
+    mdl_init_lock(mdl_lock, mdlkey, 0, db, name);
+    hash_tables->mdl_lock= mdl_lock;
+
+    /* add to hash */
+    if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables))
+    {
+      my_free((char*) hash_tables, MYF(0));
+      DBUG_PRINT("exit",("ERROR"));
+      DBUG_RETURN(TRUE);
+    }
+  }
+  else
+    hash_tables= tables;
+
   /*
     Save and reset the open_tables list so that open_tables() won't
     be able to access (or know about) the previous list. And on return
@@ -234,68 +278,44 @@ bool mysql_ha_open(THD *thd, TABLE_LIST 
   */
   backup_open_tables= thd->open_tables;
   thd->open_tables= NULL;
+  mdl_context_backup_and_reset(&thd->mdl_context, &backup_mdl_context);
 
   /*
-    open_tables() will set 'tables->table' if successful.
+    open_tables() will set 'hash_tables->table' if successful.
     It must be NULL for a real open when calling open_tables().
   */
-  DBUG_ASSERT(! tables->table);
+  DBUG_ASSERT(! hash_tables->table);
 
   /* for now HANDLER can be used only for real TABLES */
-  tables->required_type= FRMTYPE_TABLE;
-  error= open_tables(thd, &tables, &counter, 0);
+  hash_tables->required_type= FRMTYPE_TABLE;
+  error= open_tables(thd, &hash_tables, &counter, 0);
   /* restore the state and merge the opened table into handler_tables list */
   if (thd->open_tables)
   {
     thd->open_tables->next= thd->handler_tables;
     thd->handler_tables= thd->open_tables;
   }
+  mdl_context_merge(&thd->handler_mdl_context, &thd->mdl_context);
 
   thd->open_tables= backup_open_tables;
+  mdl_context_restore(&thd->mdl_context, &backup_mdl_context);
 
   if (error)
     goto err;
 
   /* There can be only one table in '*tables'. */
-  if (! (tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER))
+  if (! (hash_tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER))
   {
     if (! reopen)
       my_error(ER_ILLEGAL_HA, MYF(0), tables->alias);
     goto err;
   }
 
-  if (! reopen)
-  {
-    /* copy the TABLE_LIST struct */
-    dblen= strlen(tables->db) + 1;
-    namelen= strlen(tables->table_name) + 1;
-    aliaslen= strlen(tables->alias) + 1;
-    if (!(my_multi_malloc(MYF(MY_WME),
-                          &hash_tables, (uint) sizeof(*hash_tables),
-                          &db, (uint) dblen,
-                          &name, (uint) namelen,
-                          &alias, (uint) aliaslen,
-                          NullS)))
-      goto err;
-    /* structure copy */
-    *hash_tables= *tables;
-    hash_tables->db= db;
-    hash_tables->table_name= name;
-    hash_tables->alias= alias;
-    memcpy(hash_tables->db, tables->db, dblen);
-    memcpy(hash_tables->table_name, tables->table_name, namelen);
-    memcpy(hash_tables->alias, tables->alias, aliaslen);
-
-    /* add to hash */
-    if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables))
-      goto err;
-  }
-
   /*
     If it's a temp table, don't reset table->query_id as the table is
     being used by this handler. Otherwise, no meaning at all.
   */
-  tables->table->open_by_handler= 1;
+  hash_tables->table->open_by_handler= 1;
 
   if (! reopen)
     my_ok(thd);
@@ -303,10 +323,10 @@ bool mysql_ha_open(THD *thd, TABLE_LIST 
   DBUG_RETURN(FALSE);
 
 err:
-  if (hash_tables)
-    my_free((char*) hash_tables, MYF(0));
-  if (tables->table)
-    mysql_ha_close_table(thd, tables, FALSE);
+  if (hash_tables->table)
+    mysql_ha_close_table(thd, hash_tables);
+  if (!reopen)
+    hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables);
   DBUG_PRINT("exit",("ERROR"));
   DBUG_RETURN(TRUE);
 }
@@ -340,7 +360,7 @@ bool mysql_ha_close(THD *thd, TABLE_LIST
                                               (uchar*) tables->alias,
                                               strlen(tables->alias) + 1)))
   {
-    mysql_ha_close_table(thd, hash_tables, FALSE);
+    mysql_ha_close_table(thd, hash_tables);
     hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables);
   }
   else
@@ -479,7 +499,7 @@ retry:
 
   if (need_reopen)
   {
-    mysql_ha_close_table(thd, tables, FALSE);
+    mysql_ha_close_table(thd, tables);
     hash_tables->table= NULL;
     /*
       The lock might have been aborted, we need to manually reset
@@ -714,12 +734,11 @@ static TABLE_LIST *mysql_ha_find(THD *th
 
   @param thd Thread identifier.
   @param tables The list of tables to remove.
-  @param is_locked If LOCK_open is locked.
 
   @note Broadcasts refresh if it closed a table with old version.
 */
 
-void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables, bool is_locked)
+void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables)
 {
   TABLE_LIST *hash_tables, *next;
   DBUG_ENTER("mysql_ha_rm_tables");
@@ -732,7 +751,7 @@ void mysql_ha_rm_tables(THD *thd, TABLE_
   {
     next= hash_tables->next_local;
     if (hash_tables->table)
-      mysql_ha_close_table(thd, hash_tables, is_locked);
+      mysql_ha_close_table(thd, hash_tables);
     hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables);
     hash_tables= next;
   }
@@ -755,14 +774,17 @@ void mysql_ha_flush(THD *thd)
   TABLE_LIST *hash_tables;
   DBUG_ENTER("mysql_ha_flush");
 
-  safe_mutex_assert_owner(&LOCK_open);
+  safe_mutex_assert_not_owner(&LOCK_open);
 
   for (uint i= 0; i < thd->handler_tables_hash.records; i++)
   {
     hash_tables= (TABLE_LIST*) hash_element(&thd->handler_tables_hash, i);
-    if (hash_tables->table &&
hash_tables->table->needs_reopen_or_name_lock())
+    if (hash_tables->table &&
+        (hash_tables->table->mdl_lock &&
+         mdl_has_pending_conflicting_lock(hash_tables->table->mdl_lock) ||
+         hash_tables->table->needs_reopen_or_name_lock()))
     {
-      mysql_ha_close_table(thd, hash_tables, TRUE);
+      mysql_ha_close_table(thd, hash_tables);
       /* Mark table as closed, ready for re-open. */
       hash_tables->table= NULL;
     }
@@ -789,8 +811,8 @@ void mysql_ha_cleanup(THD *thd)
   {
     hash_tables= (TABLE_LIST*) hash_element(&thd->handler_tables_hash, i);
     if (hash_tables->table)
-      mysql_ha_close_table(thd, hash_tables, FALSE);
-   }
+      mysql_ha_close_table(thd, hash_tables);
+  }
 
   hash_free(&thd->handler_tables_hash);
 
diff -Nrup a/sql/sql_insert.cc b/sql/sql_insert.cc
--- a/sql/sql_insert.cc	2008-04-14 14:10:01 +04:00
+++ b/sql/sql_insert.cc	2008-05-22 13:21:13 +04:00
@@ -598,7 +598,8 @@ bool mysql_insert(THD *thd,TABLE_LIST *t
     upgrade the lock here instead?
   */
   if (table_list->lock_type == TL_WRITE_DELAYED && thd->locked_tables
&&
-      find_locked_table(thd, table_list->db, table_list->table_name))
+      find_locked_table(thd->open_tables, table_list->db,
+                        table_list->table_name))
   {
     my_error(ER_DELAYED_INSERT_TABLE_LOCKED, MYF(0),
              table_list->table_name);
@@ -2299,6 +2300,8 @@ pthread_handler_t handle_delayed_insert(
   thd->lex->set_stmt_unsafe();
   thd->set_current_stmt_binlog_row_based_if_mixed();
 
+  alloc_mdl_locks(&di->table_list, thd->mem_root);
+
   /* Open table */
   if (!(di->table= open_n_lock_single_table(thd, &di->table_list,
                                             TL_WRITE_DELAYED)))
@@ -3316,8 +3319,7 @@ static TABLE *create_table_from_items(TH
 
   DBUG_EXECUTE_IF("sleep_create_select_before_check_if_exists", my_sleep(6000000););
 
-  if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE) &&
-      create_table->table->db_stat)
+  if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE) && 
create_table->table)
   {
     /* Table already exists and was open at open_and_lock_tables() stage. */
     if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
@@ -3410,7 +3412,7 @@ static TABLE *create_table_from_items(TH
       if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
       {
         VOID(pthread_mutex_lock(&LOCK_open));
-        if (reopen_name_locked_table(thd, create_table, FALSE))
+        if (reopen_name_locked_table(thd, create_table))
         {
           quick_rm_table(create_info->db_type, create_table->db,
                          table_case_name(create_info, create_table->table_name),
@@ -3422,7 +3424,8 @@ static TABLE *create_table_from_items(TH
       }
       else
       {
-        if (!(table= open_table(thd, create_table, thd->mem_root, (bool*) 0,
+        if (!(table= open_table(thd, create_table, thd->mem_root,
+                                (enum_open_table_action*) 0,
                                 MYSQL_OPEN_TEMPORARY_ONLY)) &&
             !create_info->table_existed)
         {
diff -Nrup a/sql/sql_parse.cc b/sql/sql_parse.cc
--- a/sql/sql_parse.cc	2008-04-16 11:53:13 +04:00
+++ b/sql/sql_parse.cc	2008-05-22 13:21:13 +04:00
@@ -52,6 +52,7 @@ int execute_backup_command(THD*,LEX*);
    "FUNCTION" : "PROCEDURE")
 
 static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables);
+static void adjust_mdl_locks_upgradability(TABLE_LIST *tables);
 
 const char *any_db="*any*";	// Special symbol for check_access
 
@@ -95,17 +96,6 @@ const char *xa_state_names[]={
 
 extern DDL_blocker_class *DDL_blocker;
 
-static void unlock_locked_tables(THD *thd)
-{
-  if (thd->locked_tables)
-  {
-    thd->lock=thd->locked_tables;
-    thd->locked_tables=0;			// Will be automatically closed
-    close_thread_tables(thd);			// Free tables
-  }
-}
-
-
 bool end_active_trans(THD *thd)
 {
   int error=0;
@@ -146,12 +136,9 @@ bool begin_trans(THD *thd)
     my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
     return 1;
   }
-  if (thd->locked_tables)
-  {
-    thd->lock=thd->locked_tables;
-    thd->locked_tables=0;			// Will be automatically closed
-    close_thread_tables(thd);			// Free tables
-  }
+
+  unlock_locked_tables(thd);
+
   if (end_active_trans(thd))
     error= -1;
   else
@@ -1149,6 +1136,7 @@ bool dispatch_command(enum enum_server_c
       select_lex.table_list.link_in_list((uchar*) &table_list,
                                          (uchar**) &table_list.next_local);
     thd->lex->add_to_query_tables(&table_list);
+    alloc_mdl_locks(&table_list, thd->mem_root);
 
     /* switch on VIEW optimisation: do not fill temporary tables */
     thd->lex->sql_command= SQLCOM_SHOW_FIELDS;
@@ -2354,7 +2342,7 @@ mysql_execute_command(THD *thd)
       if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE))
       {
         lex->link_first_table_back(create_table, link_to_local);
-        create_table->create= TRUE;
+        create_table->open_table_type= TABLE_LIST::OPEN_OR_CREATE;
       }
 
       if (!(res= open_and_lock_tables(thd, lex->query_tables)))
@@ -3321,6 +3309,9 @@ end_with_restore_list:
       goto error;
     thd->in_lock_tables=1;
     thd->options|= OPTION_TABLE_LOCK;
+    alloc_mdl_locks(all_tables, &thd->locked_tables_root);
+    thd->mdl_el_root= &thd->locked_tables_root;
+    adjust_mdl_locks_upgradability(all_tables);
 
     if (!(res= simple_open_n_lock_tables(thd, all_tables)))
     {
@@ -3347,6 +3338,7 @@ end_with_restore_list:
       thd->options&= ~(OPTION_TABLE_LOCK);
     }
     thd->in_lock_tables=0;
+    thd->mdl_el_root= 0;
     break;
   case SQLCOM_CREATE_DB:
   {
@@ -6118,6 +6110,9 @@ TABLE_LIST *st_select_lex::add_table_to_
   ptr->next_name_resolution_table= NULL;
   /* Link table in global list (all used tables) */
   lex->add_to_query_tables(ptr);
+  ptr->mdl_lock= mdl_alloc_lock(0 , ptr->db, ptr->table_name,
+                                thd->mdl_el_root ? thd->mdl_el_root :
+                                                   thd->mem_root);
   DBUG_RETURN(ptr);
 }
 
@@ -6647,23 +6642,15 @@ bool reload_acl_and_cache(THD *thd, ulon
     if ((options & REFRESH_READ_LOCK) && thd)
     {
       /*
-        We must not try to aspire a global read lock if we have a write
-        locked table. This would lead to a deadlock when trying to
-        reopen (and re-lock) the table after the flush.
+        On the first hand we need write lock on the tables to be flushed,
+        on the other hand we must not try to aspire a global read lock
+        if we have a write locked table as this would lead to a deadlock
+        when trying to reopen (and re-lock) the table after the flush.
       */
       if (thd->locked_tables)
       {
-        THR_LOCK_DATA **lock_p= thd->locked_tables->locks;
-        THR_LOCK_DATA **end_p= lock_p + thd->locked_tables->lock_count;
-
-        for (; lock_p < end_p; lock_p++)
-        {
-          if ((*lock_p)->type >= TL_WRITE_ALLOW_WRITE)
-          {
-            my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
-            return 1;
-          }
-        }
+        my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+        return 1;
       }
       /*
 	Writing to the binlog could cause deadlocks, as we don't log
@@ -6673,7 +6660,7 @@ bool reload_acl_and_cache(THD *thd, ulon
       if (lock_global_read_lock(thd))
 	return 1;                               // Killed
       result= close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ?
-                                  FALSE : TRUE, TRUE);
+                                  FALSE : TRUE);
       if (make_global_read_lock_block_commit(thd)) // Killed
       {
         /* Don't leave things in a half-locked state */
@@ -6682,8 +6669,37 @@ bool reload_acl_and_cache(THD *thd, ulon
       }
     }
     else
+    {
+      if (thd && thd->locked_tables)
+      {
+        /*
+          If we are under LOCK TABLES we should have a write
+          lock on tables which we are going to flush.
+        */
+        if (tables)
+        {
+          for (TABLE_LIST *t= tables; t; t= t->next_local)
+            if (!find_write_locked_table(thd->open_tables, t->db,
+                                         t->table_name))
+              return 1;
+        }
+        else
+        {
+          for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
+          {
+            if (tab->reginfo.lock_type < TL_WRITE_ALLOW_WRITE)
+            {
+              my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0),
+                       tab->s->table_name.str);
+              return 1;
+            }
+          }
+        }
+      }
+
       result= close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ?
-                                  FALSE : TRUE, FALSE);
+                                  FALSE : TRUE);
+    }
     my_dbopt_cleanup();
   }
   if (options & REFRESH_HOSTS)
@@ -7631,6 +7647,42 @@ bool parse_sql(THD *thd,
   /* That's it. */
 
   return mysql_parse_status || thd->is_fatal_error;
+}
+
+
+/**
+   Auxiliary function which marks metadata locks for all tables
+   on which we plan to take write lock as upgradable.
+*/
+
+static void adjust_mdl_locks_upgradability(TABLE_LIST *tables)
+{
+  TABLE_LIST *tab, *otab;
+
+  for (tab= tables; tab; tab= tab->next_global)
+  {
+    if (tab->lock_type >= TL_WRITE_ALLOW_WRITE)
+      tab->mdl_upgradable= TRUE;
+    else
+    {
+      /*
+        TODO: To get rid of this loop we need to change our code to do
+              metadata lock upgrade only for those instances of tables
+              which are write locked instead of doing such upgrade for
+              all instances of tables.
+      */
+      for (otab= tables; otab; otab= otab->next_global)
+        if (otab->lock_type >= TL_WRITE_ALLOW_WRITE &&
+            otab->db_length == tab->db_length &&
+            otab->table_name_length == tab->table_name_length &&
+            !strcmp(otab->db, tab->db) &&
+            !strcmp(otab->table_name, tab->table_name))
+        {
+          tab->mdl_upgradable= TRUE;
+          break;
+        }
+    }
+  }
 }
 
 /**
diff -Nrup a/sql/sql_partition.cc b/sql/sql_partition.cc
--- a/sql/sql_partition.cc	2008-03-28 20:47:48 +03:00
+++ b/sql/sql_partition.cc	2008-05-22 13:21:13 +04:00
@@ -5791,7 +5791,7 @@ static void alter_partition_lock_handlin
     */
     pthread_mutex_lock(&LOCK_open);
     lpt->thd->in_lock_tables= 1;
-    err= reopen_tables(lpt->thd, 1, 1);
+    err= reopen_tables(lpt->thd, 1);
     lpt->thd->in_lock_tables= 0;
     if (err)
     {
@@ -6168,7 +6168,7 @@ uint fast_alter_partition_table(THD *thd
         write_log_drop_partition(lpt) ||
         ERROR_INJECT_CRASH("crash_drop_partition_3") ||
         (not_completed= FALSE) ||
-        abort_and_upgrade_lock(lpt) || /* Always returns 0 */
+        abort_and_upgrade_lock(lpt) ||
         ERROR_INJECT_CRASH("crash_drop_partition_4") ||
         alter_close_tables(lpt) ||
         ERROR_INJECT_CRASH("crash_drop_partition_5") ||
@@ -6235,7 +6235,7 @@ uint fast_alter_partition_table(THD *thd
         ERROR_INJECT_CRASH("crash_add_partition_2") ||
         mysql_change_partitions(lpt) ||
         ERROR_INJECT_CRASH("crash_add_partition_3") ||
-        abort_and_upgrade_lock(lpt) || /* Always returns 0 */
+        abort_and_upgrade_lock(lpt) ||
         ERROR_INJECT_CRASH("crash_add_partition_4") ||
         alter_close_tables(lpt) ||
         ERROR_INJECT_CRASH("crash_add_partition_5") ||
@@ -6251,7 +6251,7 @@ uint fast_alter_partition_table(THD *thd
         ERROR_INJECT_CRASH("crash_add_partition_8") ||
         (write_log_completed(lpt, FALSE), FALSE) ||
         ERROR_INJECT_CRASH("crash_add_partition_9") ||
-        (alter_partition_lock_handling(lpt), FALSE)) 
+        (alter_partition_lock_handling(lpt), FALSE))
     {
       handle_alter_part_error(lpt, not_completed, FALSE, frm_install);
       goto err;
@@ -6325,7 +6325,7 @@ uint fast_alter_partition_table(THD *thd
         write_log_final_change_partition(lpt) ||
         ERROR_INJECT_CRASH("crash_change_partition_4") ||
         (not_completed= FALSE) ||
-        abort_and_upgrade_lock(lpt) || /* Always returns 0 */
+        abort_and_upgrade_lock(lpt) ||
         ERROR_INJECT_CRASH("crash_change_partition_5") ||
         alter_close_tables(lpt) ||
         ERROR_INJECT_CRASH("crash_change_partition_6") ||
diff -Nrup a/sql/sql_plist.h b/sql/sql_plist.h
--- /dev/null	Wed Dec 31 16:00:00 196900
+++ b/sql/sql_plist.h	2008-05-22 13:21:15 +04:00
@@ -0,0 +1,120 @@
+#ifndef SQL_PLIST_H
+#define SQL_PLIST_H
+/* Copyright (C) 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 <my_global.h>
+
+template <typename T, typename B> class I_P_List_iterator;
+
+
+/**
+   Intrusive parameterized list.
+
+   Unlike I_List does not require its elements to be descendant of ilink
+   class and therefore allows them to participate in several such lists
+   simultaneously.
+
+   Unlike List is doubly-linked list and thus supports efficient deletion
+   of element without iterator.
+
+   @param T  Type of elements which will belong to list.
+   @param B  Class which via its methods specifies which members
+             of T should be used for participating in this list.
+             Here is typical layout of such class:
+
+             struct B
+             {
+               static inline T **next_ptr(T *el)
+               {
+                 return &el->next;
+               }
+               static inline T ***prev_ptr(T *el)
+               {
+                 return &el->prev;
+               }
+             };
+*/
+
+template <typename T, typename B>
+class I_P_List
+{
+  T *first;
+
+  /*
+    Do not prohibit copying of I_P_List object to simplify their usage in
+    backup/restore scenarios. Note that performing any operations on such
+    is a bad idea.
+  */
+public:
+  I_P_List() : first(NULL) { };
+  inline void empty()      { first= NULL; }
+  inline bool is_empty()   { return (first == NULL); }
+  inline void push_front(T* a)
+  {
+    *B::next_ptr(a)= first;
+    if (first)
+      *B::prev_ptr(first)= B::next_ptr(a);
+    first= a;
+    *B::prev_ptr(a)= &first;
+  }
+  inline void remove(T *a)
+  {
+    T *next= *B::next_ptr(a);
+    if (next)
+      *B::prev_ptr(next)= *B::prev_ptr(a);
+    **B::prev_ptr(a)= next;
+  }
+  inline T* head() { return first; }
+  void swap(I_P_List<T,B> &rhs)
+  {
+    swap_variables(T *, first, rhs.first);
+    if (first)
+      *B::prev_ptr(first)= &first;
+    if (rhs.first)
+      *B::prev_ptr(rhs.first)= &rhs.first;
+  }
+#ifndef _lint
+  friend class I_P_List_iterator<T, B>;
+#endif
+};
+
+
+/**
+   Iterator for I_P_List.
+*/
+
+template <typename T, typename B>
+class I_P_List_iterator
+{
+  I_P_List<T, B> *list;
+  T *current;
+public:
+  I_P_List_iterator(I_P_List<T, B> &a) : list(&a), current(a.first) {}
+  inline T* operator++(int)
+  {
+    T *result= current;
+    if (result)
+      current= *B::next_ptr(current);
+    return result;
+  }
+  inline void rewind()
+  {
+    current= list->first;
+  }
+};
+
+#endif
diff -Nrup a/sql/sql_plugin.cc b/sql/sql_plugin.cc
--- a/sql/sql_plugin.cc	2008-04-14 14:10:02 +04:00
+++ b/sql/sql_plugin.cc	2008-05-22 13:21:13 +04:00
@@ -1343,6 +1343,7 @@ static void plugin_load(MEM_ROOT *tmp_ro
   tables.alias= tables.table_name= (char*)"plugin";
   tables.lock_type= TL_READ;
   tables.db= new_thd->db;
+  alloc_mdl_locks(&tables, tmp_root);
 
 #ifdef EMBEDDED_LIBRARY
   /*
@@ -1642,6 +1643,8 @@ bool mysql_install_plugin(THD *thd, cons
   if (check_table_access(thd, INSERT_ACL, &tables, FALSE, FALSE, 1))
     DBUG_RETURN(TRUE);
 
+  alloc_mdl_locks(&tables, thd->mem_root);
+
   /* need to open before acquiring LOCK_plugin or it will deadlock */
   if (! (table = open_ltable(thd, &tables, TL_WRITE, 0)))
     DBUG_RETURN(TRUE);
@@ -1698,6 +1701,7 @@ bool mysql_uninstall_plugin(THD *thd, co
   bzero(&tables, sizeof(tables));
   tables.db= (char *)"mysql";
   tables.table_name= tables.alias= (char *)"plugin";
+  alloc_mdl_locks(&tables, thd->mem_root);
 
   /* need to open before acquiring LOCK_plugin or it will deadlock */
   if (! (table= open_ltable(thd, &tables, TL_WRITE, 0)))
diff -Nrup a/sql/sql_prepare.cc b/sql/sql_prepare.cc
--- a/sql/sql_prepare.cc	2008-04-16 00:29:39 +04:00
+++ b/sql/sql_prepare.cc	2008-05-22 13:21:13 +04:00
@@ -1500,7 +1500,7 @@ static bool mysql_test_create_table(Prep
     if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE))
     {
       lex->link_first_table_back(create_table, link_to_local);
-      create_table->create= TRUE;
+      create_table->open_table_type= TABLE_LIST::OPEN_OR_CREATE;
     }
 
     if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0))
diff -Nrup a/sql/sql_rename.cc b/sql/sql_rename.cc
--- a/sql/sql_rename.cc	2008-02-19 15:45:17 +03:00
+++ b/sql/sql_rename.cc	2008-05-22 13:21:13 +04:00
@@ -51,7 +51,7 @@ bool mysql_rename_tables(THD *thd, TABLE
     DBUG_RETURN(1);
   }
 
-  mysql_ha_rm_tables(thd, table_list, FALSE);
+  mysql_ha_rm_tables(thd, table_list);
 
   if (wait_if_global_read_lock(thd,0,1))
     DBUG_RETURN(1);
@@ -133,12 +133,13 @@ bool mysql_rename_tables(THD *thd, TABLE
     }
   }
 
-  pthread_mutex_lock(&LOCK_open);
-  if (lock_table_names_exclusively(thd, table_list))
-  {
-    pthread_mutex_unlock(&LOCK_open);
+  if (lock_table_names(thd, table_list))
     goto err;
-  }
+
+  VOID(pthread_mutex_lock(&LOCK_open));
+
+  for (ren_table= table_list; ren_table; ren_table= ren_table->next_local)
+    expel_table_from_cache(0, ren_table->db, ren_table->table_name);
 
   error=0;
   if ((ren_table=rename_tables(thd,table_list,0)))
@@ -184,9 +185,7 @@ bool mysql_rename_tables(THD *thd, TABLE
   if (!error)
     query_cache_invalidate3(thd, table_list, 0);
 
-  pthread_mutex_lock(&LOCK_open);
-  unlock_table_names(thd, table_list, (TABLE_LIST*) 0);
-  pthread_mutex_unlock(&LOCK_open);
+  unlock_table_names(thd);
 
 err:
   start_waiting_global_read_lock(thd);
diff -Nrup a/sql/sql_servers.cc b/sql/sql_servers.cc
--- a/sql/sql_servers.cc	2008-03-28 20:47:48 +03:00
+++ b/sql/sql_servers.cc	2008-05-22 13:21:13 +04:00
@@ -223,12 +223,7 @@ bool servers_reload(THD *thd)
   bool return_val= TRUE;
   DBUG_ENTER("servers_reload");
 
-  if (thd->locked_tables)
-  {					// Can't have locked tables here
-    thd->lock=thd->locked_tables;
-    thd->locked_tables=0;
-    close_thread_tables(thd);
-  }
+  unlock_locked_tables(thd); // Can't have locked tables here
 
   DBUG_PRINT("info", ("locking servers_cache"));
   rw_wrlock(&THR_LOCK_servers);
@@ -237,6 +232,7 @@ bool servers_reload(THD *thd)
   tables[0].alias= tables[0].table_name= (char*) "servers";
   tables[0].db= (char*) "mysql";
   tables[0].lock_type= TL_READ;
+  alloc_mdl_locks(tables, thd->mem_root);
 
   if (simple_open_n_lock_tables(thd, tables))
   {
@@ -367,6 +363,7 @@ insert_server(THD *thd, FOREIGN_SERVER *
   bzero((char*) &tables, sizeof(tables));
   tables.db= (char*) "mysql";
   tables.alias= tables.table_name= (char*) "servers";
+  alloc_mdl_locks(&tables, thd->mem_root);
 
   /* need to open before acquiring THR_LOCK_plugin or it will deadlock */
   if (! (table= open_ltable(thd, &tables, TL_WRITE, 0)))
@@ -585,6 +582,7 @@ int drop_server(THD *thd, LEX_SERVER_OPT
   bzero((char*) &tables, sizeof(tables));
   tables.db= (char*) "mysql";
   tables.alias= tables.table_name= (char*) "servers";
+  alloc_mdl_locks(&tables, thd->mem_root);
 
   rw_wrlock(&THR_LOCK_servers);
 
@@ -708,6 +706,7 @@ int update_server(THD *thd, FOREIGN_SERV
   bzero((char*) &tables, sizeof(tables));
   tables.db= (char*)"mysql";
   tables.alias= tables.table_name= (char*)"servers";
+  alloc_mdl_locks(&tables, thd->mem_root);
 
   if (!(table= open_ltable(thd, &tables, TL_WRITE, 0)))
   {
diff -Nrup a/sql/sql_show.cc b/sql/sql_show.cc
--- a/sql/sql_show.cc	2008-04-14 14:10:03 +04:00
+++ b/sql/sql_show.cc	2008-05-22 13:21:13 +04:00
@@ -2963,7 +2963,7 @@ fill_schema_show_cols_or_idxs(THD *thd, 
                                            table, res, db_name,
                                            table_name));
    thd->temporary_tables= 0;
-   close_tables_for_reopen(thd, &show_table_list);
+   close_tables_for_reopen(thd, &show_table_list, FALSE);
    DBUG_RETURN(error);
 }
 
@@ -3096,12 +3096,42 @@ static int fill_schema_table_from_frm(TH
   int error;
   char key[MAX_DBKEY_LENGTH];
   uint key_length;
+  MDL_LOCK mdl_lock;
+  char mdlkey[MAX_DBKEY_LENGTH];
+  bool retry;
 
   bzero((char*) &table_list, sizeof(TABLE_LIST));
   bzero((char*) &tbl, sizeof(TABLE));
 
   table_list.table_name= table_name->str;
   table_list.db= db_name->str;
+  mdl_init_lock(&mdl_lock, mdlkey, 0, db_name->str, table_name->str);
+  table_list.mdl_lock= &mdl_lock;
+  mdl_add_lock(&thd->mdl_context, &mdl_lock);
+  mdl_set_lock_priority(&mdl_lock, MDL_HIGH_PRIO);
+
+  /*
+    TODO: investigate if in this particular situation we can get by
+          simply obtaining internal lock of data-dictionary (ATM it
+          is LOCK_open) instead of obtaning full-blown metadata lock.
+  */
+  while (1)
+  {
+    if (mdl_acquire_shared_lock(&mdl_lock, &retry))
+    {
+      if (!retry || mdl_wait_for_locks(&thd->mdl_context))
+      {
+        /*
+          Some error occured or we have been killed while waiting
+          for conflicting locks to go away, let the caller to handle
+          the situation.
+        */
+        return 1;
+      }
+      continue;
+    }
+    break;
+  }
 
   if (schema_table->i_s_requested_object & OPEN_TRIGGER_ONLY)
   {
@@ -3115,8 +3145,7 @@ static int fill_schema_table_from_frm(TH
       delete tbl.triggers;
     }
     free_root(&tbl.mem_root, MYF(0));
-    thd->clear_error();
-    return res;
+    goto err;
   }
 
   key_length= create_table_def_key(thd, key, &table_list, 0);
@@ -3126,7 +3155,7 @@ static int fill_schema_table_from_frm(TH
   if (!share)
   {
     res= 0;
-    goto err;
+    goto err_unlock;
   }
  
   if (share->is_view)
@@ -3135,7 +3164,7 @@ static int fill_schema_table_from_frm(TH
     {
       /* skip view processing */
       res= 0;
-      goto err1;
+      goto err_share;
     }
     else if (schema_table->i_s_requested_object & OPEN_VIEW_FULL)
     {
@@ -3144,7 +3173,7 @@ static int fill_schema_table_from_frm(TH
         open_normal_and_derived_tables()
       */
       res= 1;
-      goto err1;
+      goto err_share;
     }
   }
 
@@ -3156,11 +3185,11 @@ static int fill_schema_table_from_frm(TH
                      READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD |
                      OPEN_VIEW_NO_PARSE,
                      thd->open_options, &tbl, &table_list, thd->mem_root))
-      goto err1;
+      goto err_share;
     table_list.view= (st_lex*) share->is_view;
     res= schema_table->process_table(thd, &table_list, table,
                                      res, db_name, table_name);
-    goto err1;
+    goto err_share;
   }
 
   {
@@ -3171,11 +3200,14 @@ static int fill_schema_table_from_frm(TH
                                      res, db_name, table_name);
   }
 
-err1:
+err_share:
   release_table_share(share, RELEASE_NORMAL);
 
-err:
+err_unlock:
   pthread_mutex_unlock(&LOCK_open);
+
+err:
+  mdl_release_lock(&thd->mdl_context, &mdl_lock);
   thd->clear_error();
   return res;
 }
@@ -3422,7 +3454,7 @@ int get_all_tables(THD *thd, TABLE_LIST 
               res= schema_table->process_table(thd, show_table_list, table,
                                                res, &orig_db_name,
                                                &tmp_lex_string);
-              close_tables_for_reopen(thd, &show_table_list);
+              close_tables_for_reopen(thd, &show_table_list, FALSE);
             }
             DBUG_ASSERT(!lex->query_tables_own_last);
             if (res)
@@ -7358,6 +7390,8 @@ bool show_create_trigger(THD *thd, const
   */
 
   uint num_tables; /* NOTE: unused, only to pass to open_tables(). */
+
+  alloc_mdl_locks(lst, thd->mem_root);
 
   if (open_tables(thd, &lst, &num_tables, 0))
   {
diff -Nrup a/sql/sql_table.cc b/sql/sql_table.cc
--- a/sql/sql_table.cc	2008-04-14 14:10:03 +04:00
+++ b/sql/sql_table.cc	2008-05-22 13:21:14 +04:00
@@ -53,6 +53,7 @@ static bool
 mysql_prepare_alter_table(THD *thd, TABLE *table,
                           HA_CREATE_INFO *create_info,
                           Alter_info *alter_info);
+static bool close_cached_table(THD *thd, TABLE *table);
 
 #ifndef DBUG_OFF
 
@@ -1486,11 +1487,6 @@ bool mysql_rm_table(THD *thd,TABLE_LIST 
       DBUG_RETURN(TRUE);
   }
 
-  /*
-    Acquire LOCK_open after wait_if_global_read_lock(). If we would hold
-    LOCK_open during wait_if_global_read_lock(), other threads could not
-    close their tables. This would make a pretty deadlock.
-  */
   error= mysql_rm_table_part2(thd, tables, if_exists, drop_temporary, 0, 0);
 
   if (need_start_waiting)
@@ -1558,16 +1554,14 @@ int mysql_rm_table_part2(THD *thd, TABLE
       built_query.append("DROP TABLE ");
   }
 
-  mysql_ha_rm_tables(thd, tables, FALSE);
-
-  pthread_mutex_lock(&LOCK_open);
+  mysql_ha_rm_tables(thd, tables);
 
   /*
     If we have the table in the definition cache, we don't have to check the
     .frm file to find if the table is a normal table (not view) and what
     engine to use.
   */
-
+  pthread_mutex_lock(&LOCK_open);
   for (table= tables; table; table= table->next_local)
   {
     TABLE_SHARE *share;
@@ -1580,16 +1574,32 @@ int mysql_rm_table_part2(THD *thd, TABLE
         check_if_log_table(table->db_length, table->db,
                            table->table_name_length, table->table_name, 1))
     {
-      my_error(ER_BAD_LOG_STATEMENT, MYF(0), "DROP");
       pthread_mutex_unlock(&LOCK_open);
+      my_error(ER_BAD_LOG_STATEMENT, MYF(0), "DROP");
       DBUG_RETURN(1);
     }
   }
+  pthread_mutex_unlock(&LOCK_open);
 
-  if (!drop_temporary && lock_table_names_exclusively(thd, tables))
+  if (!drop_temporary)
   {
-    pthread_mutex_unlock(&LOCK_open);
-    DBUG_RETURN(1);
+    if (!thd->locked_tables)
+    {
+      if (lock_table_names(thd, tables))
+        DBUG_RETURN(1);
+      pthread_mutex_lock(&LOCK_open);
+      for (table= tables; table; table= table->next_local)
+        expel_table_from_cache(0, table->db, table->table_name);
+      pthread_mutex_unlock(&LOCK_open);
+    }
+    else if (thd->locked_tables)
+    {
+      for (table= tables; table; table= table->next_local)
+        if (!find_temporary_table(thd, table->db, table->table_name) &&
+            !find_write_locked_table(thd->open_tables, table->db,
+                                     table->table_name))
+        DBUG_RETURN(1);
+    }
   }
 
   /* Don't give warnings for not found errors, as we already generate notes */
@@ -1648,17 +1658,21 @@ int mysql_rm_table_part2(THD *thd, TABLE
     table_type= table->db_type;
     if (!drop_temporary)
     {
-      TABLE *locked_table;
-      abort_locked_tables(thd, db, table->table_name);
-      remove_table_from_cache(thd, db, table->table_name,
-                              RTFC_WAIT_OTHER_THREAD_FLAG |
-                              RTFC_CHECK_KILLED_FLAG);
-      /*
-        If the table was used in lock tables, remember it so that
-        unlock_table_names can free it
-      */
-      if ((locked_table= drop_locked_tables(thd, db, table->table_name)))
-        table->table= locked_table;
+      if (thd->locked_tables)
+      {
+        TABLE *tab= find_locked_table(thd->open_tables, db, table->table_name);
+        if (close_cached_table(thd, tab))
+        {
+          error= -1;
+          goto err_with_placeholders;
+        }
+        /*
+          Leave LOCK TABLES mode if we managed to drop all tables
+          which were locked.
+        */
+        if (thd->locked_tables->table_count == 0)
+          unlock_locked_tables(thd);
+      }
 
       if (thd->killed)
       {
@@ -1671,6 +1685,11 @@ int mysql_rm_table_part2(THD *thd, TABLE
                                         table->internal_tmp_table ?
                                         FN_IS_TMP : 0);
     }
+    /*
+      TODO: Investigate what should be done to remove this lock completely.
+            Is exclusive meta-data lock enough ?
+    */
+    pthread_mutex_lock(&LOCK_open);
     if (drop_temporary ||
         (table_type == NULL &&        
          (access(path, F_OK) &&
@@ -1723,6 +1742,7 @@ int mysql_rm_table_part2(THD *thd, TABLE
         error|= new_error;
       }
     }
+    pthread_mutex_unlock(&LOCK_open);
     if (error)
     {
       if (wrong_tables.length())
@@ -1732,11 +1752,6 @@ int mysql_rm_table_part2(THD *thd, TABLE
     DBUG_PRINT("table", ("table: 0x%lx  s: 0x%lx", (long) table->table,
                          table->table ? (long) table->table->s : (long) -1));
   }
-  /*
-    It's safe to unlock LOCK_open: we have an exclusive lock
-    on the table name.
-  */
-  pthread_mutex_unlock(&LOCK_open);
   thd->thread_specific_used|= tmp_table_deleted;
   error= 0;
   if (wrong_tables.length())
@@ -1796,10 +1811,19 @@ int mysql_rm_table_part2(THD *thd, TABLE
       */
     }
   }
-  pthread_mutex_lock(&LOCK_open);
 err_with_placeholders:
-  unlock_table_names(thd, tables, (TABLE_LIST*) 0);
-  pthread_mutex_unlock(&LOCK_open);
+  if (!drop_temporary)
+  {
+    /*
+      Under LOCK TABLES we should release meta-data locks on the tables
+      which were dropped. Otherwise we can rely on close_thread_tables()
+      doing this. Unfortunately in this case we are likely to get more
+      false positives in lock_table_name_if_not_cached() function. So
+      it makes sense to remove exclusive meta-data locks in all cases.
+    */
+    mdl_release_exclusive_locks(&thd->mdl_context);
+  }
+
   thd->no_warnings_for_error= 0;
   DBUG_RETURN(error);
 }
@@ -3617,6 +3641,34 @@ warn:
 }
 
 
+/**
+   Auxiliary function which obtains exclusive meta-data lock on the
+   table if there are no shared or exclusive on it already.
+
+   See mdl_try_acquire_exclusive_lock() function for more info.
+
+   TODO: This function is here mostly to simplify current patch
+         and probably should be removed.
+   TODO: Investigate if it is kosher to leave lock request in the
+         context in the case when we fail to obtain the lock.
+*/
+
+static bool lock_table_name_if_not_cached(THD *thd, const char *db,
+                                          const char *table_name,
+                                          MDL_LOCK **lock)
+{
+  if (!(*lock= mdl_alloc_lock(0, db, table_name, thd->mem_root)))
+    return TRUE;
+  mdl_set_lock_type(*lock, MDL_EXCLUSIVE);
+  mdl_add_lock(&thd->mdl_context, *lock);
+  if (mdl_try_acquire_exclusive_lock(&thd->mdl_context, *lock))
+  {
+    *lock= 0;
+  }
+  return FALSE;
+}
+
+
 /*
   Database locking aware wrapper for mysql_create_table_no_lock(),
 */
@@ -3627,7 +3679,7 @@ bool mysql_create_table(THD *thd, const 
                         bool internal_tmp_table,
                         uint select_field_count)
 {
-  TABLE *name_lock= 0;
+  MDL_LOCK *target_lock= 0;
   bool result;
   DBUG_ENTER("mysql_create_table");
 
@@ -3650,12 +3702,12 @@ bool mysql_create_table(THD *thd, const 
 
   if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
   {
-    if (lock_table_name_if_not_cached(thd, db, table_name, &name_lock))
+    if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock))
     {
       result= TRUE;
       goto unlock;
     }
-    if (!name_lock)
+    if (!target_lock)
     {
       if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
       {
@@ -3680,12 +3732,8 @@ bool mysql_create_table(THD *thd, const 
                                      select_field_count);
 
 unlock:
-  if (name_lock)
-  {
-    pthread_mutex_lock(&LOCK_open);
-    unlink_open_table(thd, name_lock, FALSE);
-    pthread_mutex_unlock(&LOCK_open);
-  }
+  if (target_lock)
+    mdl_release_exclusive_locks(&thd->mdl_context);
   pthread_mutex_lock(&LOCK_lock_db);
   if (!--creating_table && creating_database)
     pthread_cond_signal(&COND_refresh);
@@ -3821,80 +3869,83 @@ mysql_rename_table(handlerton *base, con
 }
 
 
-/*
-  Force all other threads to stop using the table
+/**
+   Force all other threads to stop using the table by upgrading
+   metadata lock on it and remove unused TABLE instances from cache.
 
-  SYNOPSIS
-    wait_while_table_is_used()
-    thd			Thread handler
-    table		Table to remove from cache
-    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
-  NOTES
-   When returning, the table will be unusable for other threads until
-   the table is closed.
+   @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
 
-  PREREQUISITES
-    Lock on LOCK_open
-    Win32 clients must also have a WRITE LOCK on the table !
+   @note When returning, the table will be unusable for other threads
+         until metadata lock is downgraded.
+
+   @retval FALSE Success.
+   @retval TRUE  Failure (e.g. because thread was killed).
 */
 
-void wait_while_table_is_used(THD *thd, TABLE *table,
+bool wait_while_table_is_used(THD *thd, TABLE *table,
                               enum ha_extra_function function)
 {
+  enum thr_lock_type old_lock_type;
+
   DBUG_ENTER("wait_while_table_is_used");
   DBUG_PRINT("enter", ("table: '%s'  share: 0x%lx  db_stat: %u  version: %lu",
                        table->s->table_name.str, (ulong) table->s,
                        table->db_stat, table->s->version));
 
-  safe_mutex_assert_owner(&LOCK_open);
-
   VOID(table->file->extra(function));
-  /* Mark all tables that are in use as 'old' */
+
+  old_lock_type= table->reginfo.lock_type;
   mysql_lock_abort(thd, table, TRUE);	/* end threads waiting on lock */
 
-  /* Wait until all there are no other threads that has this table open */
-  remove_table_from_cache(thd, table->s->db.str,
-                          table->s->table_name.str,
-                          RTFC_WAIT_OTHER_THREAD_FLAG);
-  DBUG_VOID_RETURN;
+  if (mdl_upgrade_shared_lock_to_exclusive(&thd->mdl_context, 0,
+                                           table->s->db.str,
+                                           table->s->table_name.str))
+  {
+    mysql_lock_downgrade_write(thd, table, old_lock_type);
+    DBUG_RETURN(TRUE);
+  }
+
+  VOID(pthread_mutex_lock(&LOCK_open));
+  expel_table_from_cache(thd, table->s->db.str, table->s->table_name.str);
+  VOID(pthread_mutex_unlock(&LOCK_open));
+  DBUG_RETURN(FALSE);
 }
 
-/*
-  Close a cached table
 
-  SYNOPSIS
-    close_cached_table()
-    thd			Thread handler
-    table		Table to remove from cache
+/**
+   Upgrade metadata lock on the table and close all its instances.
 
-  NOTES
-    Function ends by signaling threads waiting for the table to try to
-    reopen the table.
+   @param thd   Thread handler
+   @param table Table to remove from cache
 
-  PREREQUISITES
-    Lock on LOCK_open
-    Win32 clients must also have a WRITE LOCK on the table !
+   @retval FALSE Success.
+   @retval TRUE  Failure (e.g. because thread was killed).
 */
 
-void close_cached_table(THD *thd, TABLE *table)
+static bool close_cached_table(THD *thd, TABLE *table)
 {
   DBUG_ENTER("close_cached_table");
 
-  wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
+  /* FIXME: check if we pass proper parameters everywhere. */
+  if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+    DBUG_RETURN(TRUE);
+
   /* Close lock if this is not got with LOCK TABLES */
   if (thd->lock)
   {
     mysql_unlock_tables(thd, thd->lock);
     thd->lock=0;			// Start locked threads
   }
+
+  VOID(pthread_mutex_lock(&LOCK_open));
   /* Close all copies of 'table'.  This also frees all LOCK TABLES lock */
   unlink_open_table(thd, table, TRUE);
-
-  /* When lock on LOCK_open is freed other threads can continue */
-  broadcast_refresh();
-  DBUG_VOID_RETURN;
+  VOID(pthread_mutex_unlock(&LOCK_open));
+  DBUG_RETURN(FALSE);
 }
 
 static int send_check_errmsg(THD *thd, TABLE_LIST* table,
@@ -3923,6 +3974,7 @@ static int prepare_for_repair(THD *thd, 
   char from[FN_REFLEN],tmp[FN_REFLEN+32];
   const char **ext;
   MY_STAT stat_info;