List:Commits« Previous MessageNext Message »
From:Konstantin Osipov Date:July 27 2010 10:46am
Subject:bzr push into mysql-trunk-runtime branch (kostja:3085 to 3086)
Bug#52044 Bug#55452
View as plain text  
 3086 Konstantin Osipov	2010-07-27
      A pre-requisite patch for the fix for Bug#52044.
      This patch also fixes Bug#55452 "SET PASSWORD is
      replicated twice in RBR mode".
      
      The goal of this patch is to remove the release of 
      metadata locks from close_thread_tables().
      This is necessary to not mistakenly release
      the locks in the course of a multi-step
      operation that involves multiple close_thread_tables()
      or close_tables_for_reopen().
      
      On the same token, move statement commit outside 
      close_thread_tables().
      
      Other cleanups:
      Cleanup COM_FIELD_LIST.
      Don't call close_thread_tables() in COM_SHUTDOWN -- there
      are no open tables there that can be closed (we leave
      the locked tables mode in THD destructor, and this
      close_thread_tables() won't leave it anyway).
      
      Make open_and_lock_tables() and open_and_lock_tables_derived()
      call close_thread_tables() upon failure.
      Remove the calls to close_thread_tables() that are now
      unnecessary.
      
      Simplify the back off condition in Open_table_context.
      
      Streamline metadata lock handling in LOCK TABLES 
      implementation.
      
      Add asserts to ensure correct life cycle of 
      statement transaction in a session.
      
      Remove a piece of dead code that has also become redundant
      after the fix for Bug 37521.
     @ mysql-test/r/variables.result
        Update results: set @@autocommit and statement transaction/
        prelocked mode.
     @ mysql-test/r/view.result
        A harmless change in CHECK TABLE <view> status for a broken view.
        If previously a failure to prelock all functions used in a view 
        would leave the connection in LTM_PRELOCKED mode, now we call
        close_thread_tables() from open_and_lock_tables()
        and leave prelocked mode, thus some check in mysql_admin_table() that
        works only in prelocked/locked tables mode is no longer activated.
     @ mysql-test/suite/rpl/r/rpl_row_implicit_commit_binlog.result
        Fixed Bug#55452 "SET PASSWORD is replicated twice in
        RBR mode": extra binlog events are gone from the
        binary log.
     @ mysql-test/t/variables.test
        Add a test case: set autocommit and statement transaction/prelocked
        mode.
     @ sql/event_data_objects.cc
        Simplify code in Event_job_data::execute().
        Move sp_head memory management to lex_end().
     @ sql/event_db_repository.cc
        Move the release of metadata locks outside
        close_thread_tables().
        Make sure we call close_thread_tables() when
        open_and_lock_tables() fails and remove extra
        code from the events data dictionary.
        Use close_mysql_tables(), a new internal
        function to properly close mysql.* tables
        in the data dictionary.
        Contract Event_db_repository::drop_events_by_field,
        drop_schema_events into one function.
        When dropping all events in a schema,
        make sure we don't mistakenly release all
        locks acquired by DROP DATABASE. These
        include locks on the database name
        and the global intention exclusive
        metadata lock.
     @ sql/event_db_repository.h
        Function open_event_table() does not require an instance 
        of Event_db_repository.
     @ sql/events.cc
        Use close_mysql_tables() instead of close_thread_tables()
        to bootstrap events, since the latter no longer
        releases metadata locks.
     @ sql/ha_ndbcluster.cc
        - mysql_rm_table_part2 no longer releases
        acquired metadata locks. Do it in the caller.
     @ sql/ha_ndbcluster_binlog.cc
        Deploy the new protocol for closing thread
        tables in run_query() and ndb_binlog_index
        code.
     @ sql/handler.cc
        Assert that we never call ha_commit_trans/
        ha_rollback_trans in sub-statement, which
        is now the case.
     @ sql/handler.h
        Add an accessor to check whether THD_TRANS object
        is empty (has no transaction started).
     @ sql/log.cc
        Update a comment.
     @ sql/log_event.cc
        Since now we commit/rollback statement transaction in 
        mysql_execute_command(), we need a mechanism to communicate
        from Query_log_event::do_apply_event() to mysql_execute_command()
        that the statement transaction should be rolled back, not committed.
        Ideally it would be a virtual method of THD. I hesitate
        to make THD a virtual base class in this already large patch.
        Use a thd->variables.option_bits for now.
        
        Remove a call to close_thread_tables() from the slave IO
        thread. It doesn't open any tables, and the protocol
        for closing thread tables is more complicated now.
        
        Make sure we properly close thread tables, however, 
        in Load_data_log_event, which doesn't
        follow the standard server execution procedure
        with mysql_execute_command().
        @todo: this piece should use Server_runnable
        framework instead.
        Remove an unnecessary call to mysql_unlock_tables().
     @ sql/rpl_rli.cc
        Update Relay_log_info::slave_close_thread_tables()
        to follow the new close protocol.
     @ sql/set_var.cc
        Remove an unused header.
     @ sql/slave.cc
        Remove an unnecessary call to
        close_thread_tables().
     @ sql/sp.cc
        Remove unnecessary calls to close_thread_tables()
        from SP DDL implementation. The tables will
        be closed by the caller, in mysql_execute_command().
        When dropping all routines in a database, make sure
        to not mistakenly drop all metadata locks acquired
        so far, they include the scoped lock on the schema.
     @ sql/sp_head.cc
        Correct the protocol that closes thread tables
        in an SP instruction.
        Clear lex->sphead before cleaning up lex
        with lex_end to make sure that we don't
        delete the sphead twice. It's considered
        to be "cleaner" and more in line with
        future changes than calling delete lex->sphead
        in other places that cleanup the lex.
     @ sql/sp_head.h
        When destroying m_lex_keeper of an instruction,
        don't delete the sphead that all lex objects
        share. 
        @todo: don't store a reference to routine's sp_head
        instance in instruction's lex.
     @ sql/sql_acl.cc
        Don't call close_thread_tables() where the caller will
        do that for us.
        Fix Bug#55452 "SET PASSWORD is replicated twice in RBR 
        mode" by disabling RBR replication in change_password()
        function.
        Use close_mysql_tables() in bootstrap and ACL reload
        code to make sure we release all metadata locks.
     @ sql/sql_base.cc
        This is the main part of the patch:
        - remove manipulation with thd->transaction
        and thd->mdl_context from close_thread_tables().
        Now this function is only responsible for closing
        tables, nothing else.
        This is necessary to be able to easily use
        close_thread_tables() in procedures, that
        involve multiple open/close tables, which all
        need to be protected continuously by metadata
        locks.
        Add asserts ensuring that TABLE object
        is only used when is protected by a metadata lock.
        Simplify the back off condition of Open_table_context,
        we no longer need to look at the autocommit mode.
        Make open_and_lock_tables() and open_normal_and_derived_tables()
        close thread tables and release metadata locks acquired so-far 
        upon failure. This simplifies their usage.
        Implement close_mysql_tables().
     @ sql/sql_base.h
        Add declaration for close_mysql_tables().
     @ sql/sql_class.cc
        Remove a piece of dead code that has also become redundant
        after the fix for Bug 37521.
        The code became dead when my_eof() was made a non-protocol method,
        but a method that merely modifies the diagnostics area.
        The code became redundant with the fix for Bug#37521, when 
        we started to cal close_thread_tables() before
        Protocol::end_statement().
     @ sql/sql_do.cc
        Do nothing in DO if inside a substatement
        (the assert moved out of trans_rollback_stmt).
     @ sql/sql_handler.cc
        Add comments.
     @ sql/sql_insert.cc
        Remove dead code. 
        Release metadata locks explicitly at the
        end of the delayed insert thread.
     @ sql/sql_lex.cc
        Add destruction of lex->sphead to lex_end(),
        lex "reset" method called at the end of each statement.
     @ sql/sql_parse.cc
        Move close_thread_tables() and other related
        cleanups to mysql_execute_command()
        from dispatch_command(). This has become
        possible after the fix for Bug#37521.
        Mark federated SERVER statements as DDL.
        
        Next step: make sure that we don't store
        eof packet in the query cache, and move
        the query cache code outside mysql_parse.
        
        Brush up the code of COM_FIELD_LIST.
        Remove unnecessary calls to close_thread_tables().
        
        When killing a query, don't report "OK"
        if it was a suicide.
     @ sql/sql_parse.h
        Remove declaration of a function that is now static.
     @ sql/sql_partition.cc
        Remove an unnecessary call to close_thread_tables().
     @ sql/sql_plugin.cc
        open_and_lock_tables() will clean up
        after itself after a failure.
        Move close_thread_tables() above
        end: label, and replace with close_mysql_tables(),
        which will also release the metadata lock
        on mysql.plugin.
     @ sql/sql_prepare.cc
        Now that we no longer release locks in close_thread_tables()
        statement prepare code has become more straightforward.
        Remove the now redundant check for thd->killed() (used
        only by the backup project) from Execute_server_runnable.
        Reorder code to take into account that now mysql_execute_command()
        performs lex->unit.cleanup() and close_thread_tables().
     @ sql/sql_priv.h
        Add a new option to server options to interact
        between the slave SQL thread and execution
        framework (hack). @todo: use a virtual
        method of class THD instead.
     @ sql/sql_servers.cc
        Due to Bug 25705 replication of 
        DROP/CREATE/ALTER SERVER is broken.
        Make sure at least we do not attempt to 
        replicate these statements using RBR,
        as this violates the assert in close_mysql_tables().
     @ sql/sql_table.cc
        Do not release metadata locks in mysql_rm_table_part2,
        this is done by the caller.
        Do not call close_thread_tables() in mysql_create_table(),
        this is done by the caller. 
        Fix a bug in DROP TABLE under LOCK TABLES when,
        upon error in wait_while_table_is_used() we would mistakenly
        release the metadata lock on a non-dropped table.
        Explicitly release metadata locks when doing an implicit
        commit.
     @ sql/sql_trigger.cc
        Now that we delete lex->sphead in lex_end(),
        zero the trigger's sphead in lex after loading
        the trigger, to avoid double deletion.
     @ sql/sql_udf.cc
        Use close_mysql_tables() instead of close_thread_tables().
     @ sql/sys_vars.cc
        Remove code added in scope of WL#4284 which would
        break when we perform set @@session.autocommit along
        with setting other variables and using tables or functions.
        A test case added to variables.test.
     @ sql/transaction.cc
        Add asserts.
     @ sql/tztime.cc
        Use close_mysql_tables() rather than close_thread_tables().

    modified:
      mysql-test/r/variables.result
      mysql-test/r/view.result
      mysql-test/suite/rpl/r/rpl_row_implicit_commit_binlog.result
      mysql-test/t/variables.test
      sql/event_data_objects.cc
      sql/event_db_repository.cc
      sql/event_db_repository.h
      sql/events.cc
      sql/ha_ndbcluster.cc
      sql/ha_ndbcluster_binlog.cc
      sql/handler.cc
      sql/handler.h
      sql/log.cc
      sql/log_event.cc
      sql/rpl_rli.cc
      sql/set_var.cc
      sql/slave.cc
      sql/sp.cc
      sql/sp_head.cc
      sql/sp_head.h
      sql/sql_acl.cc
      sql/sql_base.cc
      sql/sql_base.h
      sql/sql_class.cc
      sql/sql_cursor.cc
      sql/sql_do.cc
      sql/sql_handler.cc
      sql/sql_insert.cc
      sql/sql_lex.cc
      sql/sql_parse.cc
      sql/sql_parse.h
      sql/sql_partition.cc
      sql/sql_plugin.cc
      sql/sql_prepare.cc
      sql/sql_priv.h
      sql/sql_servers.cc
      sql/sql_table.cc
      sql/sql_trigger.cc
      sql/sql_udf.cc
      sql/sys_vars.cc
      sql/transaction.cc
      sql/tztime.cc
 3085 Dmitry Lenev	2010-07-26
      Test for bug #53820 "ALTER a MEDIUMINT column table causes full 
      table copy".
      
      This patch only adds test case as the bug itself was addressed 
      by Ramil's fix for bug 50946 "fast index creation still seems
      to copy the table".

    modified:
      mysql-test/r/alter_table.result
      mysql-test/t/alter_table.test
=== modified file 'mysql-test/r/variables.result'
--- a/mysql-test/r/variables.result	2010-06-04 16:09:50 +0000
+++ b/mysql-test/r/variables.result	2010-07-27 10:25:53 +0000
@@ -1677,3 +1677,25 @@ SET @@sql_quote_show_create = @sql_quote
 
 # End of Bug#34828.
 
+# Make sure we can manipulate with autocommit in the
+# along with other variables.
+drop table if exists t1;
+drop function if exists t1_max;
+drop function if exists t1_min;
+create table t1 (a int) engine=innodb;
+insert into t1(a) values (0), (1);
+create function t1_max() returns int return (select max(a) from t1);
+create function t1_min() returns int return (select min(a) from t1);
+select t1_min();
+t1_min()
+0
+select t1_max();
+t1_max()
+1
+set @@session.autocommit=t1_min(), @@session.autocommit=t1_max(),
+@@session.autocommit=t1_min(), @@session.autocommit=t1_max(),
+@@session.autocommit=t1_min(), @@session.autocommit=t1_max();
+# Cleanup.
+drop table t1;
+drop function t1_min;
+drop function t1_max;

=== modified file 'mysql-test/r/view.result'
--- a/mysql-test/r/view.result	2010-06-06 11:19:29 +0000
+++ b/mysql-test/r/view.result	2010-07-27 10:25:53 +0000
@@ -1955,15 +1955,15 @@ CHECK TABLE v1, v2, v3, v4, v5, v6;
 Table	Op	Msg_type	Msg_text
 test.v1	check	Error	FUNCTION test.f1 does not exist
 test.v1	check	Error	View 'test.v1' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them
-test.v1	check	status	Operation failed
+test.v1	check	error	Corrupt
 test.v2	check	status	OK
 test.v3	check	Error	FUNCTION test.f1 does not exist
 test.v3	check	Error	View 'test.v3' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them
-test.v3	check	status	Operation failed
+test.v3	check	error	Corrupt
 test.v4	check	status	OK
 test.v5	check	Error	FUNCTION test.f1 does not exist
 test.v5	check	Error	View 'test.v5' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them
-test.v5	check	status	Operation failed
+test.v5	check	error	Corrupt
 test.v6	check	status	OK
 create function f1 () returns int return (select max(col1) from t1);
 DROP TABLE t1;

=== modified file 'mysql-test/suite/rpl/r/rpl_row_implicit_commit_binlog.result'
--- a/mysql-test/suite/rpl/r/rpl_row_implicit_commit_binlog.result	2010-04-20 09:10:43 +0000
+++ b/mysql-test/suite/rpl/r/rpl_row_implicit_commit_binlog.result	2010-07-27 10:25:53 +0000
@@ -165,10 +165,6 @@ master-bin.000001	#	Table_map	#	#	table_
 master-bin.000001	#	Write_rows	#	#	table_id: # flags: STMT_END_F
 master-bin.000001	#	Xid	#	#	COMMIT /* XID */
 master-bin.000001	#	Query	#	#	use `test`; SET PASSWORD FOR 'user'@'localhost'='*D8DECEC305209EEFEC43008E1D420E1AA06B19E0'
-master-bin.000001	#	Query	#	#	BEGIN
-master-bin.000001	#	Table_map	#	#	table_id: # (mysql.user)
-master-bin.000001	#	Update_rows	#	#	table_id: # flags: STMT_END_F
-master-bin.000001	#	Query	#	#	COMMIT
 -e-e-e-e-e-e-e-e-e-e-e- >> << -e-e-e-e-e-e-e-e-e-e-e-
 
 -b-b-b-b-b-b-b-b-b-b-b- >> << -b-b-b-b-b-b-b-b-b-b-b-

=== modified file 'mysql-test/t/variables.test'
--- a/mysql-test/t/variables.test	2010-06-04 16:09:50 +0000
+++ b/mysql-test/t/variables.test	2010-07-27 10:25:53 +0000
@@ -1405,4 +1405,30 @@ SET @@sql_quote_show_create = @sql_quote
 --echo # End of Bug#34828.
 --echo
 
+--echo # Make sure we can manipulate with autocommit in the
+--echo # along with other variables.
+
+
+--disable_warnings
+drop table if exists t1;
+drop function if exists t1_max;
+drop function if exists t1_min;
+--enable_warnings
+
+create table t1 (a int) engine=innodb;
+insert into t1(a) values (0), (1);
+create function t1_max() returns int return (select max(a) from t1);
+create function t1_min() returns int return (select min(a) from t1);
+select t1_min();
+select t1_max();
+set @@session.autocommit=t1_min(), @@session.autocommit=t1_max(),
+    @@session.autocommit=t1_min(), @@session.autocommit=t1_max(),
+    @@session.autocommit=t1_min(), @@session.autocommit=t1_max();
+
+--echo # Cleanup.
+drop table t1;
+drop function t1_min;
+drop function t1_max;
+
+
 ###########################################################################

=== modified file 'sql/event_data_objects.cc'
--- a/sql/event_data_objects.cc	2010-07-08 21:20:08 +0000
+++ b/sql/event_data_objects.cc	2010-07-27 10:25:53 +0000
@@ -1402,6 +1402,8 @@ Event_job_data::execute(THD *thd, bool d
   */
   thd->set_db(dbname.str, dbname.length);
 
+  lex_start(thd);
+
 #ifndef NO_EMBEDDED_ACCESS_CHECKS
   if (event_sctx.change_security_context(thd,
                                          &definer_user, &definer_host,
@@ -1411,7 +1413,7 @@ Event_job_data::execute(THD *thd, bool d
                     "[%s].[%s.%s] execution failed, "
                     "failed to authenticate the user.",
                     definer.str, dbname.str, name.str);
-    goto end_no_lex_start;
+    goto end;
   }
 #endif
 
@@ -1427,11 +1429,11 @@ Event_job_data::execute(THD *thd, bool d
                     "[%s].[%s.%s] execution failed, "
                     "user no longer has EVENT privilege.",
                     definer.str, dbname.str, name.str);
-    goto end_no_lex_start;
+    goto end;
   }
 
   if (construct_sp_sql(thd, &sp_sql))
-    goto end_no_lex_start;
+    goto end;
 
   /*
     Set up global thread attributes to reflect the properties of
@@ -1451,8 +1453,6 @@ Event_job_data::execute(THD *thd, bool d
     if (parser_state.init(thd, thd->query(), thd->query_length()))
       goto end;
 
-    lex_start(thd);
-
     if (parse_sql(thd, & parser_state, creation_ctx))
     {
       sql_print_error("Event Scheduler: "
@@ -1484,13 +1484,6 @@ Event_job_data::execute(THD *thd, bool d
   }
 
 end:
-  if (thd->lex->sphead)                        /* NULL only if a parse error */
-  {
-    delete thd->lex->sphead;
-    thd->lex->sphead= NULL;
-  }
-
-end_no_lex_start:
   if (drop && !thd->is_fatal_error)
   {
     /*
@@ -1529,7 +1522,6 @@ end_no_lex_start:
   if (save_sctx)
     event_sctx.restore_security_context(thd, save_sctx);
 #endif
-  lex_end(thd->lex);
   thd->lex->unit.cleanup();
   thd->end_statement();
   thd->cleanup_after_query();

=== modified file 'sql/event_db_repository.cc'
--- a/sql/event_db_repository.cc	2010-03-31 14:05:33 +0000
+++ b/sql/event_db_repository.cc	2010-07-27 10:25:53 +0000
@@ -518,17 +518,20 @@ Event_db_repository::table_scan_all_for_
 */
 
 bool
-Event_db_repository::fill_schema_events(THD *thd, TABLE_LIST *tables,
+Event_db_repository::fill_schema_events(THD *thd, TABLE_LIST *i_s_table,
                                         const char *db)
 {
-  TABLE *schema_table= tables->table;
-  TABLE *event_table= NULL;
+  TABLE *schema_table= i_s_table->table;
+  Open_tables_backup open_tables_backup;
+  TABLE_LIST event_table;
   int ret= 0;
 
   DBUG_ENTER("Event_db_repository::fill_schema_events");
   DBUG_PRINT("info",("db=%s", db? db:"(null)"));
 
-  if (open_event_table(thd, TL_READ, &event_table))
+  event_table.init_one_table("mysql", 5, "event", 5, "event", TL_READ);
+
+  if (open_system_tables_for_read(thd, &event_table, &open_tables_backup))
     DBUG_RETURN(TRUE);
 
   /*
@@ -541,11 +544,11 @@ Event_db_repository::fill_schema_events(
                   every single row's `db` with the schema which we show.
   */
   if (db)
-    ret= index_read_for_db_for_i_s(thd, schema_table, event_table, db);
+    ret= index_read_for_db_for_i_s(thd, schema_table, event_table.table, db);
   else
-    ret= table_scan_all_for_i_s(thd, schema_table, event_table);
+    ret= table_scan_all_for_i_s(thd, schema_table, event_table.table);
 
-  close_thread_tables(thd);
+  close_system_tables(thd, &open_tables_backup);
 
   DBUG_PRINT("info", ("Return code=%d", ret));
   DBUG_RETURN(ret);
@@ -584,10 +587,7 @@ Event_db_repository::open_event_table(TH
   tables.init_one_table("mysql", 5, "event", 5, "event", lock_type);
 
   if (open_and_lock_tables(thd, &tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
-  {
-    close_thread_tables(thd);
     DBUG_RETURN(TRUE);
-  }
 
   *table= tables.table;
   tables.table->use_all_columns();
@@ -700,7 +700,8 @@ Event_db_repository::create_event(THD *t
 
 end:
   if (table)
-    close_thread_tables(thd);
+    close_mysql_tables(thd);
+
   thd->variables.sql_mode= saved_mode;
   DBUG_RETURN(test(ret));
 }
@@ -811,7 +812,8 @@ Event_db_repository::update_event(THD *t
 
 end:
   if (table)
-    close_thread_tables(thd);
+    close_mysql_tables(thd);
+
   thd->variables.sql_mode= saved_mode;
   DBUG_RETURN(test(ret));
 }
@@ -865,7 +867,7 @@ Event_db_repository::drop_event(THD *thd
 
 end:
   if (table)
-    close_thread_tables(thd);
+    close_mysql_tables(thd);
 
   DBUG_RETURN(test(ret));
 }
@@ -934,33 +936,13 @@ Event_db_repository::find_named_event(LE
 void
 Event_db_repository::drop_schema_events(THD *thd, LEX_STRING schema)
 {
-  DBUG_ENTER("Event_db_repository::drop_schema_events");
-  drop_events_by_field(thd, ET_FIELD_DB, schema);
-  DBUG_VOID_RETURN;
-}
-
-
-/**
-  Drops all events which have a specific value of a field.
-
-  @pre The thread handle has no open tables.
-
-  @param[in,out] thd         Thread
-  @param[in,out] table       mysql.event TABLE
-  @param[in]     field       Which field of the row to use for matching
-  @param[in]     field_value The value that should match
-*/
-
-void
-Event_db_repository::drop_events_by_field(THD *thd,
-                                          enum enum_events_table_field field,
-                                          LEX_STRING field_value)
-{
   int ret= 0;
   TABLE *table= NULL;
   READ_RECORD read_record_info;
-  DBUG_ENTER("Event_db_repository::drop_events_by_field");
-  DBUG_PRINT("enter", ("field=%d field_value=%s", field, field_value.str));
+  enum enum_events_table_field field= ET_FIELD_DB;
+  MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint();
+  DBUG_ENTER("Event_db_repository::drop_schema_events");
+  DBUG_PRINT("enter", ("field=%d schema=%s", field, schema.str));
 
   if (open_event_table(thd, TL_WRITE, &table))
     DBUG_VOID_RETURN;
@@ -979,7 +961,7 @@ Event_db_repository::drop_events_by_fiel
                           get_field(thd->mem_root,
                                     table->field[ET_FIELD_NAME])));
 
-      if (!sortcmp_lex_string(et_field_lex, field_value, system_charset_info))
+      if (!sortcmp_lex_string(et_field_lex, schema, system_charset_info))
       {
         DBUG_PRINT("info", ("Dropping"));
         if ((ret= table->file->ha_delete_row(table->record[0])))
@@ -989,6 +971,11 @@ Event_db_repository::drop_events_by_fiel
   }
   end_read_record(&read_record_info);
   close_thread_tables(thd);
+  /*
+    Make sure to only release the MDL lock on mysql.event, not other
+    metadata locks DROP DATABASE might have acquired.
+  */
+  thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
 
   DBUG_VOID_RETURN;
 }
@@ -1026,7 +1013,7 @@ Event_db_repository::load_named_event(TH
     else if ((ret= etn->load_from_row(thd, table)))
       my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0), "event");
 
-    close_thread_tables(thd);
+    close_mysql_tables(thd);
   }
 
   thd->variables.sql_mode= saved_mode;
@@ -1104,7 +1091,8 @@ update_timing_fields_for_event(THD *thd,
 
 end:
   if (table)
-    close_thread_tables(thd);
+    close_mysql_tables(thd);
+
   /* Restore the state of binlog format */
   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
   if (save_binlog_row_based)
@@ -1151,7 +1139,7 @@ Event_db_repository::check_system_tables
     if (table_intact.check(tables.table, &mysql_db_table_def))
       ret= 1;
 
-    close_thread_tables(thd);
+    close_mysql_tables(thd);
   }
   /* Check mysql.user */
   tables.init_one_table("mysql", 5, "user", 4, "user", TL_READ);
@@ -1171,7 +1159,7 @@ Event_db_repository::check_system_tables
                       event_priv_column_position);
       ret= 1;
     }
-    close_thread_tables(thd);
+    close_mysql_tables(thd);
   }
   /* Check mysql.event */
   tables.init_one_table("mysql", 5, "event", 5, "event", TL_READ);
@@ -1185,7 +1173,7 @@ Event_db_repository::check_system_tables
   {
     if (table_intact.check(tables.table, &event_table_def))
       ret= 1;
-    close_thread_tables(thd);
+    close_mysql_tables(thd);
   }
 
   DBUG_RETURN(test(ret));

=== modified file 'sql/event_db_repository.h'
--- a/sql/event_db_repository.h	2007-08-15 15:08:44 +0000
+++ b/sql/event_db_repository.h	2010-07-27 10:25:53 +0000
@@ -91,7 +91,7 @@ public:
   bool
   load_named_event(THD *thd, LEX_STRING dbname, LEX_STRING name, Event_basic *et);
 
-  bool
+  static bool
   open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE **table);
 
   bool
@@ -109,9 +109,6 @@ public:
   static bool
   check_system_tables(THD *thd);
 private:
-  void
-  drop_events_by_field(THD *thd, enum enum_events_table_field field,
-                       LEX_STRING field_value);
   bool
   index_read_for_db_for_i_s(THD *thd, TABLE *schema_table, TABLE *event_table,
                             const char *db);

=== modified file 'sql/events.cc'
--- a/sql/events.cc	2010-04-19 12:09:44 +0000
+++ b/sql/events.cc	2010-07-27 10:25:53 +0000
@@ -16,7 +16,7 @@
 #include "sql_priv.h"
 #include "unireg.h"
 #include "sql_parse.h"                          // check_access
-#include "sql_base.h"                           // close_thread_tables
+#include "sql_base.h"                           // close_mysql_tables
 #include "sql_show.h"                           // append_definer
 #include "events.h"
 #include "sql_db.h"                          // check_db_dir_existence
@@ -754,7 +754,6 @@ Events::fill_schema_events(THD *thd, TAB
 {
   char *db= NULL;
   int ret;
-  Open_tables_backup open_tables_backup;
   DBUG_ENTER("Events::fill_schema_events");
 
   if (check_if_system_tables_error())
@@ -773,15 +772,7 @@ Events::fill_schema_events(THD *thd, TAB
       DBUG_RETURN(1);
     db= thd->lex->select_lex.db;
   }
-  /*
-    Reset and backup of the currently open tables in this thread
-    is a way to allow SELECTs from INFORMATION_SCHEMA.events under
-    LOCK TABLES and in pre-locked mode. See also
-    Events::show_create_event for additional comments.
-  */
-  thd->reset_n_backup_open_tables_state(&open_tables_backup);
   ret= db_repository->fill_schema_events(thd, tables, db);
-  thd->restore_backup_open_tables_state(&open_tables_backup);
 
   DBUG_RETURN(ret);
 }
@@ -1161,8 +1152,7 @@ Events::load_events_from_db(THD *thd)
 end:
   end_read_record(&read_record_info);
 
-  close_thread_tables(thd);
-
+  close_mysql_tables(thd);
   DBUG_RETURN(ret);
 }
 

=== modified file 'sql/ha_ndbcluster.cc'
--- a/sql/ha_ndbcluster.cc	2010-07-08 21:20:08 +0000
+++ b/sql/ha_ndbcluster.cc	2010-07-27 10:25:53 +0000
@@ -7417,7 +7417,8 @@ int ndbcluster_find_files(handlerton *ht
                                  FALSE,   /* drop_temporary */ 
                                  FALSE,   /* drop_view */
                                  TRUE     /* dont_log_query*/);
-
+      trans_commit_implicit(thd); /* Safety, should be unnecessary. */
+      thd->mdl_context.release_transactional_locks();
       /* Clear error message that is returned when table is deleted */
       thd->clear_error();
     }

=== modified file 'sql/ha_ndbcluster_binlog.cc'
--- a/sql/ha_ndbcluster_binlog.cc	2010-07-08 21:20:08 +0000
+++ b/sql/ha_ndbcluster_binlog.cc	2010-07-27 10:25:53 +0000
@@ -298,13 +298,6 @@ 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
@@ -2422,7 +2415,11 @@ int ndb_add_ndb_binlog_index(THD *thd, v
   }
 
 add_ndb_binlog_index_err:
+  thd->stmt_da->can_overwrite_status= TRUE;
+  thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
+  thd->stmt_da->can_overwrite_status= FALSE;
   close_thread_tables(thd);
+  thd->mdl_context.release_transactional_locks();
   ndb_binlog_index= 0;
   thd->variables.option_bits= saved_options;
   return error;
@@ -3969,7 +3966,9 @@ restart:
     {
       if (ndb_binlog_index->s->needs_reopen())
       {
+        trans_commit_stmt(thd);
         close_thread_tables(thd);
+        thd->mdl_context.release_transactional_locks();
         ndb_binlog_index= 0;
       }
     }
@@ -4280,7 +4279,9 @@ restart:
   if (do_ndbcluster_binlog_close_connection == BCCC_restart)
   {
     ndb_binlog_tables_inited= FALSE;
+    trans_commit_stmt(thd);
     close_thread_tables(thd);
+    thd->mdl_context.release_transactional_locks();
     ndb_binlog_index= 0;
     goto restart;
   }
@@ -4288,7 +4289,11 @@ err:
   sql_print_information("Stopping Cluster Binlog");
   DBUG_PRINT("info",("Shutting down cluster binlog thread"));
   thd->proc_info= "Shutting down";
+  thd->stmt_da->can_overwrite_status= TRUE;
+  thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
+  thd->stmt_da->can_overwrite_status= FALSE;
   close_thread_tables(thd);
+  thd->mdl_context.release_transactional_locks();
   mysql_mutex_lock(&injector_mutex);
   /* don't mess with the injector_ndb anymore from other threads */
   injector_thd= 0;

=== modified file 'sql/handler.cc'
--- a/sql/handler.cc	2010-07-08 21:20:08 +0000
+++ b/sql/handler.cc	2010-07-27 10:25:53 +0000
@@ -1145,6 +1145,7 @@ int ha_commit_trans(THD *thd, bool all)
 
   if (thd->in_sub_stmt)
   {
+    DBUG_ASSERT(0);
     /*
       Since we don't support nested statement transactions in 5.0,
       we can't commit or rollback stmt transactions while we are inside
@@ -1159,7 +1160,6 @@ int ha_commit_trans(THD *thd, bool all)
       bail out with error even before ha_commit_trans() call. To be 100% safe
       let us throw error in non-debug builds.
     */
-    DBUG_ASSERT(0);
     my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
     DBUG_RETURN(2);
   }
@@ -1342,6 +1342,7 @@ int ha_rollback_trans(THD *thd, bool all
 
   if (thd->in_sub_stmt)
   {
+    DBUG_ASSERT(0);
     /*
       If we are inside stored function or trigger we should not commit or
       rollback current statement transaction. See comment in ha_commit_trans()
@@ -1349,7 +1350,6 @@ int ha_rollback_trans(THD *thd, bool all
     */
     if (!all)
       DBUG_RETURN(0);
-    DBUG_ASSERT(0);
     my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
     DBUG_RETURN(1);
   }

=== modified file 'sql/handler.h'
--- a/sql/handler.h	2010-07-15 13:47:50 +0000
+++ b/sql/handler.h	2010-07-27 10:25:53 +0000
@@ -846,6 +846,7 @@ struct THD_TRANS
   bool modified_non_trans_table;
 
   void reset() { no_2pc= FALSE; modified_non_trans_table= FALSE; }
+  bool is_empty() const { return ha_list == NULL; }
 };
 
 

=== modified file 'sql/log.cc'
--- a/sql/log.cc	2010-07-15 13:47:50 +0000
+++ b/sql/log.cc	2010-07-27 10:25:53 +0000
@@ -27,7 +27,7 @@
 #include "my_global.h"                          /* NO_EMBEDDED_ACCESS_CHECKS */
 #include "sql_priv.h"
 #include "log.h"
-#include "sql_base.h"                           // close_thread_tables
+#include "sql_base.h"                           // open_log_table
 #include "sql_repl.h"
 #include "sql_delete.h"                         // mysql_truncate
 #include "sql_parse.h"                          // command_name

=== modified file 'sql/log_event.cc'
--- a/sql/log_event.cc	2010-07-15 13:47:50 +0000
+++ b/sql/log_event.cc	2010-07-27 10:25:53 +0000
@@ -3332,6 +3332,19 @@ int Query_log_event::do_apply_event(Rela
       
       thd->table_map_for_update= (table_map)table_map_for_update;
       thd->set_invoker(&user, &host);
+      /*
+        Flag if we need to rollback the statement transaction on
+        slave if it by chance succeeds.
+        If we expected a non-zero error code and get nothing and,
+        it is a concurrency issue or ignorable issue, effects
+        of the statement should be rolled back.
+      */
+      if (expected_error &&
+          (ignored_error_code(expected_error) ||
+           concurrency_error_code(expected_error)))
+      {
+        thd->variables.option_bits|= OPTION_MASTER_SQL_ERROR;
+      }
       /* Execute the query (note that we bypass dispatch_command()) */
       Parser_state parser_state;
       if (!parser_state.init(thd, thd->query(), thd->query_length()))
@@ -3340,6 +3353,8 @@ int Query_log_event::do_apply_event(Rela
         log_slow_statement(thd);
       }
 
+      thd->variables.option_bits&= ~OPTION_MASTER_SQL_ERROR;
+
       /*
         Resetting the enable_slow_log thd variable.
 
@@ -3382,7 +3397,6 @@ START SLAVE; . Query: '%s'", expected_er
       general_log_write(thd, COM_QUERY, thd->query(), thd->query_length());
 
 compare_errors:
-
     /*
       In the slave thread, we may sometimes execute some DROP / * 40005
       TEMPORARY * / TABLE that come from parts of binlogs (likely if we
@@ -3430,26 +3444,8 @@ Default database: '%s'. Query: '%s'",
       DBUG_PRINT("info",("error ignored"));
       clear_all_errors(thd, const_cast<Relay_log_info*>(rli));
       thd->killed= THD::NOT_KILLED;
-      /*
-        When an error is expected and matches the actual error the
-        slave does not report any error and by consequence changes
-        on transactional tables are not rolled back in the function
-        close_thread_tables(). For that reason, we explicitly roll
-        them back here.
-      */
-      if (expected_error && expected_error == actual_error)
-        trans_rollback_stmt(thd);
     }
     /*
-      If we expected a non-zero error code and get nothing and, it is a concurrency
-      issue or should be ignored.
-    */
-    else if (expected_error && !actual_error &&
-             (concurrency_error_code(expected_error) ||
-              ignored_error_code(expected_error)))
-      trans_rollback_stmt(thd);
-
-    /*
       Other cases: mostly we expected no error and get one.
     */
     else if (thd->is_slave_error || thd->is_fatal_error)
@@ -3516,7 +3512,6 @@ end:
   thd->set_db(NULL, 0);                 /* will free the current database */
   thd->set_query(NULL, 0);
   DBUG_PRINT("info", ("end: query= 0"));
-  close_thread_tables(thd);
   /*
     As a disk space optimization, future masters will not log an event for
     LAST_INSERT_ID() if that function returned 0 (and thus they will be able
@@ -4946,7 +4941,22 @@ error:
   thd->catalog= 0;
   thd->set_db(NULL, 0);                   /* will free the current database */
   thd->set_query(NULL, 0);
+  thd->stmt_da->can_overwrite_status= TRUE;
+  thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
+  thd->stmt_da->can_overwrite_status= FALSE;
   close_thread_tables(thd);
+  /*
+    - If inside a multi-statement transaction,
+    defer the release of metadata locks until the current
+    transaction is either committed or rolled back. This prevents
+    other statements from modifying the table for the entire
+    duration of this transaction.  This provides commit ordering
+    and guarantees serializability across multiple transactions.
+    - If in autocommit mode, or outside a transactional context,
+    automatically release metadata locks of the current statement.
+  */
+  if (! thd->in_multi_stmt_transaction_mode())
+    thd->mdl_context.release_transactional_locks();
 
   DBUG_EXECUTE_IF("LOAD_DATA_INFILE_has_fatal_error",
                   thd->is_slave_error= 0; thd->is_fatal_error= 1;);
@@ -5531,11 +5541,9 @@ int Xid_log_event::do_apply_event(Relay_
   /* For a slave Xid_log_event is COMMIT */
   general_log_print(thd, COM_QUERY,
                     "COMMIT /* implicit, from Xid_log_event */");
-  if (!(res= trans_commit(thd)))
-  {
-    close_thread_tables(thd);
-    thd->mdl_context.release_transactional_locks();
-  }
+  res= trans_commit(thd); /* Automatically rolls back on error. */
+  thd->mdl_context.release_transactional_locks();
+
   return res;
 }
 
@@ -7610,8 +7618,6 @@ int Rows_log_event::do_apply_event(Relay
             We should not honour --slave-skip-errors at this point as we are
             having severe errors which should not be skiped.
           */
-          mysql_unlock_tables(thd, thd->lock);
-          thd->lock= 0;
           thd->is_slave_error= 1;
           const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
           DBUG_RETURN(ERR_BAD_TABLE_DEF);

=== modified file 'sql/rpl_rli.cc'
--- a/sql/rpl_rli.cc	2010-07-15 13:47:50 +0000
+++ b/sql/rpl_rli.cc	2010-07-27 10:25:53 +0000
@@ -1257,7 +1257,23 @@ void Relay_log_info::clear_tables_to_loc
 
 void Relay_log_info::slave_close_thread_tables(THD *thd)
 {
+  thd->stmt_da->can_overwrite_status= TRUE;
+  thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
+  thd->stmt_da->can_overwrite_status= FALSE;
+
   close_thread_tables(thd);
+  /*
+    - If inside a multi-statement transaction,
+    defer the release of metadata locks until the current
+    transaction is either committed or rolled back. This prevents
+    other statements from modifying the table for the entire
+    duration of this transaction.  This provides commit ordering
+    and guarantees serializability across multiple transactions.
+    - If in autocommit mode, or outside a transactional context,
+    automatically release metadata locks of the current statement.
+  */
+  if (! thd->in_multi_stmt_transaction_mode())
+    thd->mdl_context.release_transactional_locks();
   clear_tables_to_lock();
 }
 #endif

=== modified file 'sql/set_var.cc'
--- a/sql/set_var.cc	2010-07-02 19:38:04 +0000
+++ b/sql/set_var.cc	2010-07-27 10:25:53 +0000
@@ -27,7 +27,6 @@
 #include "mysqld.h"                             // lc_messages_dir
 #include "sys_vars_shared.h"
 #include "transaction.h"
-#include "sql_base.h"                           // close_thread_tables
 #include "sql_locale.h"                         // my_locale_by_number,
                                                 // my_locale_by_name
 #include "strfunc.h"      // find_set_from_flags, find_set

=== modified file 'sql/slave.cc'
--- a/sql/slave.cc	2010-07-15 13:47:50 +0000
+++ b/sql/slave.cc	2010-07-27 10:25:53 +0000
@@ -3044,7 +3044,6 @@ err:
   change_rpl_status(RPL_ACTIVE_SLAVE,RPL_IDLE_SLAVE);
   DBUG_ASSERT(thd->net.buff != 0);
   net_end(&thd->net); // destructor will not free it, because net.vio is 0
-  close_thread_tables(thd);
   mysql_mutex_lock(&LOCK_thread_count);
   THD_CHECK_SENTRY(thd);
   delete thd;

=== modified file 'sql/sp.cc'
--- a/sql/sp.cc	2010-06-11 13:54:39 +0000
+++ b/sql/sp.cc	2010-07-27 10:25:53 +0000
@@ -450,10 +450,7 @@ static TABLE *open_proc_table_for_update
   if (!proc_table_intact.check(table, &proc_table_def))
     DBUG_RETURN(table);
 
-  close_thread_tables(thd);
-
   DBUG_RETURN(NULL);
-
 }
 
 
@@ -856,6 +853,7 @@ db_load_routine(THD *thd, int type, sp_n
   }
 
 end:
+  thd->lex->sphead= NULL;
   lex_end(thd->lex);
   thd->lex= old_lex;
   return ret;
@@ -1159,8 +1157,6 @@ sp_create_routine(THD *thd, int type, sp
 done:
   thd->count_cuted_fields= saved_count_cuted_fields;
   thd->variables.sql_mode= saved_mode;
-
-  close_thread_tables(thd);
   /* Restore the state of binlog format */
   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
   if (save_binlog_row_based)
@@ -1239,8 +1235,6 @@ sp_drop_routine(THD *thd, int type, sp_n
         sp_cache_flush_obsolete(spc, &sp);
     }
   }
-
-  close_thread_tables(thd);
   /* Restore the state of binlog format */
   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
   if (save_binlog_row_based)
@@ -1348,7 +1342,6 @@ sp_update_routine(THD *thd, int type, sp
     sp_cache_invalidate();
   }
 err:
-  close_thread_tables(thd);
   /* Restore the state of binlog format */
   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
   if (save_binlog_row_based)
@@ -1370,6 +1363,7 @@ sp_drop_db_routines(THD *thd, char *db)
   TABLE *table;
   int ret;
   uint key_len;
+  MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint();
   DBUG_ENTER("sp_drop_db_routines");
   DBUG_PRINT("enter", ("db: %s", db));
 
@@ -1410,6 +1404,11 @@ sp_drop_db_routines(THD *thd, char *db)
   table->file->ha_index_end();
 
   close_thread_tables(thd);
+  /*
+    Make sure to only release the MDL lock on mysql.proc, not other
+    metadata locks DROP DATABASE might have acquired.
+  */
+  thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
 
 err:
   DBUG_RETURN(ret);
@@ -2142,6 +2141,7 @@ sp_load_for_information_schema(THD *thd,
   newlex.current_select= NULL; 
   sp= sp_compile(thd, &defstr, sql_mode, creation_ctx);
   *free_sp_head= 1;
+  thd->lex->sphead= NULL;
   lex_end(thd->lex);
   thd->lex= old_lex;
   return sp;

=== modified file 'sql/sp_head.cc'
--- a/sql/sp_head.cc	2010-06-17 13:31:51 +0000
+++ b/sql/sp_head.cc	2010-07-27 10:25:53 +0000
@@ -38,6 +38,7 @@
 #include "set_var.h"
 #include "sql_parse.h"                          // cleanup_items
 #include "sql_base.h"                           // close_thread_tables
+#include "transaction.h"       // trans_commit_stmt
 
 /*
   Sufficient max length of printed destinations and frame offsets (all uints).
@@ -795,6 +796,7 @@ sp_head::~sp_head()
   while ((lex= (LEX *)m_lex.pop()))
   {
     THD *thd= lex->thd;
+    thd->lex->sphead= NULL;
     lex_end(thd->lex);
     delete thd->lex;
     thd->lex= lex;
@@ -1995,16 +1997,23 @@ sp_head::execute_procedure(THD *thd, Lis
       arguments evaluation. If arguments evaluation required prelocking mode,
       we'll leave it here.
     */
+    thd->lex->unit.cleanup();
+
     if (!thd->in_sub_stmt)
     {
-      thd->lex->unit.cleanup();
+      thd->stmt_da->can_overwrite_status= TRUE;
+      thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
+      thd->stmt_da->can_overwrite_status= FALSE;
+    }
 
-      thd_proc_info(thd, "closing tables");
-      close_thread_tables(thd);
-      thd_proc_info(thd, 0);
+    thd_proc_info(thd, "closing tables");
+    close_thread_tables(thd);
+    thd_proc_info(thd, 0);
 
-      thd->rollback_item_tree_changes();
-    }
+    if (! thd->in_sub_stmt && ! thd->in_multi_stmt_transaction_mode())
+      thd->mdl_context.release_transactional_locks();
+
+    thd->rollback_item_tree_changes();
 
     DBUG_PRINT("info",(" %.*s: eval args done", (int) m_name.length, 
                        m_name.str));
@@ -2197,6 +2206,7 @@ sp_head::restore_lex(THD *thd)
   merge_table_list(thd, sublex->query_tables, sublex);
   if (! sublex->sp_lex_in_use)
   {
+    sublex->sphead= NULL;
     lex_end(sublex);
     delete sublex;
   }
@@ -2806,12 +2816,27 @@ sp_lex_keeper::reset_lex_and_exec_core(T
     DBUG_PRINT("info",("exec_core returned: %d", res));
   }
 
-  m_lex->unit.cleanup();
+  /*
+    Call after unit->cleanup() to close open table
+    key read.
+  */
+  if (open_tables)
+  {
+    m_lex->unit.cleanup();
+    /* Here we also commit or rollback the current statement. */
+    if (! thd->in_sub_stmt)
+    {
+      thd->stmt_da->can_overwrite_status= TRUE;
+      thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
+      thd->stmt_da->can_overwrite_status= FALSE;
+    }
+    thd_proc_info(thd, "closing tables");
+    close_thread_tables(thd);
+    thd_proc_info(thd, 0);
 
-  thd_proc_info(thd, "closing tables");
-  /* Here we also commit or rollback the current statement. */
-  close_thread_tables(thd);
-  thd_proc_info(thd, 0);
+    if (! thd->in_sub_stmt && ! thd->in_multi_stmt_transaction_mode())
+      thd->mdl_context.release_transactional_locks();
+  }
 
   if (m_lex->query_tables_own_last)
   {

=== modified file 'sql/sp_head.h'
--- a/sql/sp_head.h	2010-06-06 11:19:29 +0000
+++ b/sql/sp_head.h	2010-07-27 10:25:53 +0000
@@ -682,6 +682,8 @@ public:
   {
     if (m_lex_resp)
     {
+      /* Prevent endless recursion. */
+      m_lex->sphead= NULL;
       lex_end(m_lex);
       delete m_lex;
     }

=== modified file 'sql/sql_acl.cc'
--- a/sql/sql_acl.cc	2010-07-19 08:27:53 +0000
+++ b/sql/sql_acl.cc	2010-07-27 10:25:53 +0000
@@ -27,7 +27,7 @@
 #include "my_global.h"                          /* NO_EMBEDDED_ACCESS_CHECKS */
 #include "sql_priv.h"
 #include "sql_acl.h"         // MYSQL_DB_FIELD_COUNT, ACL_ACCESS
-#include "sql_base.h"                           // close_thread_tables
+#include "sql_base.h"                           // close_mysql_tables
 #include "key.h"             // key_copy, key_cmp_if_same, key_restore
 #include "sql_show.h"        // append_identifier
 #include "sql_table.h"                         // build_table_filename
@@ -730,9 +730,7 @@ my_bool acl_reload(THD *thd)
   if (old_initialized)
     mysql_mutex_unlock(&acl_cache->lock);
 end:
-  trans_commit_implicit(thd);
-  close_thread_tables(thd);
-  thd->mdl_context.release_transactional_locks();
+  close_mysql_tables(thd);
   DBUG_RETURN(return_val);
 }
 
@@ -1585,6 +1583,7 @@ bool change_password(THD *thd, const cha
   /* Buffer should be extended when password length is extended. */
   char buff[512];
   ulong query_length;
+  bool save_binlog_row_based;
   uint new_password_len= (uint) strlen(new_password);
   bool result= 1;
   DBUG_ENTER("change_password");
@@ -1614,10 +1613,17 @@ bool change_password(THD *thd, const cha
       DBUG_RETURN(0);
   }
 #endif
-
   if (!(table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
     DBUG_RETURN(1);
 
+  /*
+    This statement will be replicated as a statement, even when using
+    row-based replication.  The flag will be reset at the end of the
+    statement.
+  */
+  if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
+    thd->clear_current_stmt_binlog_format_row();
+
   mysql_mutex_lock(&acl_cache->lock);
   ACL_USER *acl_user;
   if (!(acl_user= find_acl_user(host, user, TRUE)))
@@ -1652,7 +1658,13 @@ bool change_password(THD *thd, const cha
                               FALSE, FALSE, FALSE, 0);
   }
 end:
-  close_thread_tables(thd);
+  close_mysql_tables(thd);
+
+  /* Restore the state of binlog format */
+  DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
+  if (save_binlog_row_based)
+    thd->set_current_stmt_binlog_format_row();
+
   DBUG_RETURN(result);
 }
 
@@ -3082,7 +3094,7 @@ int mysql_table_grant(THD *thd, TABLE_LI
           DBUG_RETURN(TRUE);
         column_priv|= column->rights;
       }
-      close_thread_tables(thd);
+      close_mysql_tables(thd);
     }
     else
     {
@@ -3172,7 +3184,6 @@ int mysql_table_grant(THD *thd, TABLE_LI
   thd->lex->sql_command= backup.sql_command;
   if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
   {						// Should never happen
-    close_thread_tables(thd);			/* purecov: deadcode */
     /* Restore the state of binlog format */
     DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
     if (save_binlog_row_based)
@@ -3398,7 +3409,6 @@ bool mysql_routine_grant(THD *thd, TABLE
 
   if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
   {						// Should never happen
-    close_thread_tables(thd);
     /* Restore the state of binlog format */
     DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
     if (save_binlog_row_based)
@@ -3553,7 +3563,6 @@ bool mysql_grant(THD *thd, const char *d
 
   if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
   {						// This should never happen
-    close_thread_tables(thd);			/* purecov: deadcode */
     /* Restore the state of binlog format */
     DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
     if (save_binlog_row_based)
@@ -3613,7 +3622,6 @@ bool mysql_grant(THD *thd, const char *d
   }
 
   mysql_rwlock_unlock(&LOCK_grant);
-  close_thread_tables(thd);
 
   if (!result)
     my_ok(thd);
@@ -3874,10 +3882,7 @@ static my_bool grant_reload_procs_priv(T
   table.open_type= OT_BASE_ONLY;
 
   if (open_and_lock_tables(thd, &table, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
-  {
-    close_thread_tables(thd);
     DBUG_RETURN(TRUE);
-  }
 
   mysql_rwlock_wrlock(&LOCK_grant);
   /* Save a copy of the current hash if we need to undo the grant load */
@@ -3899,7 +3904,7 @@ static my_bool grant_reload_procs_priv(T
   }
   mysql_rwlock_unlock(&LOCK_grant);
 
-  close_thread_tables(thd);
+  close_mysql_tables(thd);
   DBUG_RETURN(return_val);
 }
 
@@ -3970,9 +3975,7 @@ my_bool grant_reload(THD *thd)
     free_root(&old_mem,MYF(0));
   }
   mysql_rwlock_unlock(&LOCK_grant);
-  trans_commit_implicit(thd);
-  close_thread_tables(thd);
-  thd->mdl_context.release_transactional_locks();
+  close_mysql_tables(thd);
 
   /*
     It is OK failing to load procs_priv table because we may be
@@ -5250,7 +5253,6 @@ int open_grant_tables(THD *thd, TABLE_LI
 
   if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
   {						// This should never happen
-    close_thread_tables(thd);
     DBUG_RETURN(-1);
   }
 
@@ -5890,7 +5892,6 @@ bool mysql_create_user(THD *thd, List <L
     result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length());
 
   mysql_rwlock_unlock(&LOCK_grant);
-  close_thread_tables(thd);
   /* Restore the state of binlog format */
   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
   if (save_binlog_row_based)
@@ -5975,7 +5976,6 @@ bool mysql_drop_user(THD *thd, List <LEX
     result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length());
 
   mysql_rwlock_unlock(&LOCK_grant);
-  close_thread_tables(thd);
   thd->variables.sql_mode= old_sql_mode;
   /* Restore the state of binlog format */
   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
@@ -6072,7 +6072,6 @@ bool mysql_rename_user(THD *thd, List <L
     result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length());
 
   mysql_rwlock_unlock(&LOCK_grant);
-  close_thread_tables(thd);
   /* Restore the state of binlog format */
   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
   if (save_binlog_row_based)
@@ -6270,8 +6269,6 @@ bool mysql_revoke_all(THD *thd,  List <L
     write_bin_log(thd, FALSE, thd->query(), thd->query_length());
 
   mysql_rwlock_unlock(&LOCK_grant);
-  close_thread_tables(thd);
-
   /* Restore the state of binlog format */
   DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row());
   if (save_binlog_row_based)
@@ -6418,7 +6415,6 @@ bool sp_revoke_privileges(THD *thd, cons
 
   mysql_mutex_unlock(&acl_cache->lock);
   mysql_rwlock_unlock(&LOCK_grant);
-  close_thread_tables(thd);
 
   thd->pop_internal_handler();
   /* Restore the state of binlog format */

=== modified file 'sql/sql_base.cc'
--- a/sql/sql_base.cc	2010-07-19 08:27:53 +0000
+++ b/sql/sql_base.cc	2010-07-27 10:25:53 +0000
@@ -1402,6 +1402,9 @@ void close_thread_tables(THD *thd)
     DEBUG_SYNC(thd, "before_close_thread_tables");
 #endif
 
+  DBUG_ASSERT(thd->transaction.stmt.is_empty() || thd->in_sub_stmt ||
+              (thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
+
   /* Detach MERGE children after every statement. Even under LOCK TABLES. */
   for (table= thd->open_tables; table; table= table->next)
   {
@@ -1446,28 +1449,6 @@ void close_thread_tables(THD *thd)
     Mark all temporary tables used by this statement as free for reuse.
   */
   mark_temp_tables_as_free_for_reuse(thd);
-  /*
-    Let us commit transaction for statement. Since in 5.0 we only have
-    one statement transaction and don't allow several nested statement
-    transactions this call will do nothing if we are inside of stored
-    function or trigger (i.e. statement transaction is already active and
-    does not belong to statement for which we do close_thread_tables()).
-    TODO: This should be fixed in later releases.
-   */
-  if (!(thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
-  {
-    thd->stmt_da->can_overwrite_status= TRUE;
-    thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
-    thd->stmt_da->can_overwrite_status= FALSE;
-
-    /*
-      Reset transaction state, but only if we're not inside a
-      sub-statement of a prelocked statement.
-    */
-    if (thd->locked_tables_mode <= LTM_LOCK_TABLES ||
-        thd->lex->requires_prelocking())
-      thd->transaction.stmt.reset();
-  }
 
   if (thd->locked_tables_mode)
   {
@@ -1528,26 +1509,6 @@ void close_thread_tables(THD *thd)
   if (thd->open_tables)
     close_open_tables(thd);
 
-  /*
-    - If inside a multi-statement transaction,
-    defer the release of metadata locks until the current
-    transaction is either committed or rolled back. This prevents
-    other statements from modifying the table for the entire
-    duration of this transaction.  This provides commit ordering
-    and guarantees serializability across multiple transactions.
-    - If closing a system table, defer the release of metadata locks
-    to the caller. We have no sentinel in MDL subsystem to guard
-    transactional locks from system tables locks, so don't know
-    which locks are which here.
-    - If in autocommit mode, or outside a transactional context,
-    automatically release metadata locks of the current statement.
-  */
-  if (! thd->in_multi_stmt_transaction_mode() &&
-      ! (thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
-  {
-    thd->mdl_context.release_transactional_locks();
-  }
-
   DBUG_VOID_RETURN;
 }
 
@@ -1562,7 +1523,14 @@ bool close_thread_table(THD *thd, TABLE 
   DBUG_ASSERT(table->key_read == 0);
   DBUG_ASSERT(!table->file || table->file->inited == handler::NONE);
   mysql_mutex_assert_not_owner(&LOCK_open);
-
+  /*
+    The metadata lock must be released after giving back
+    the table to the table cache.
+  */
+  DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE,
+                                             table->s->db.str,
+                                             table->s->table_name.str,
+                                             MDL_SHARED));
   table->mdl_ticket= NULL;
 
   mysql_mutex_lock(&thd->LOCK_thd_data);
@@ -3188,6 +3156,7 @@ Locked_tables_list::init_locked_tables(T
   return FALSE;
 }
 
+
 /**
   Leave LTM_LOCK_TABLES mode if it's been entered.
 
@@ -3224,7 +3193,12 @@ Locked_tables_list::unlock_locked_tables
     }
     thd->leave_locked_tables_mode();
 
+    DBUG_ASSERT(thd->transaction.stmt.is_empty());
     close_thread_tables(thd);
+    /*
+      We rely on the caller to implicitly commit the
+      transaction and release transactional locks.
+    */
   }
   /*
     After closing tables we can free memory used for storing lock
@@ -3810,9 +3784,7 @@ Open_table_context::Open_table_context(T
              LONG_TIMEOUT : thd->variables.lock_wait_timeout),
    m_flags(flags),
    m_action(OT_NO_ACTION),
-   m_has_locks((thd->in_multi_stmt_transaction_mode() &&
-                thd->mdl_context.has_locks()) ||
-                thd->mdl_context.trans_sentinel())
+   m_has_locks(thd->mdl_context.has_locks())
 {}
 
 
@@ -5264,6 +5236,8 @@ TABLE *open_ltable(THD *thd, TABLE_LIST 
     table= 0;
 
 end:
+  if (table == NULL)
+    close_thread_tables(thd);
   thd_proc_info(thd, 0);
   DBUG_RETURN(table);
 }
@@ -5282,7 +5256,8 @@ end:
                               should work for this statement.
 
   @note
-    The lock will automaticaly be freed by close_thread_tables()
+    The thr_lock locks will automatically be freed by
+    close_thread_tables().
 
   @retval FALSE  OK.
   @retval TRUE   Error
@@ -5293,11 +5268,12 @@ bool open_and_lock_tables(THD *thd, TABL
                           Prelocking_strategy *prelocking_strategy)
 {
   uint counter;
+  MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint();
   DBUG_ENTER("open_and_lock_tables");
   DBUG_PRINT("enter", ("derived handling: %d", derived));
 
   if (open_tables(thd, &tables, &counter, flags, prelocking_strategy))
-    DBUG_RETURN(TRUE);
+    goto err;
 
   DBUG_EXECUTE_IF("sleep_open_and_lock_after_open", {
                   const char *old_proc_info= thd->proc_info;
@@ -5306,15 +5282,22 @@ bool open_and_lock_tables(THD *thd, TABL
                   thd->proc_info= old_proc_info;});
 
   if (lock_tables(thd, tables, counter, flags))
-    DBUG_RETURN(TRUE);
+    goto err;
 
   if (derived &&
       (mysql_handle_derived(thd->lex, &mysql_derived_prepare) ||
        (thd->fill_derived_tables() &&
         mysql_handle_derived(thd->lex, &mysql_derived_filling))))
-    DBUG_RETURN(TRUE); /* purecov: inspected */
+    goto err;
 
   DBUG_RETURN(FALSE);
+err:
+  if (! thd->in_sub_stmt)
+    trans_rollback_stmt(thd);  /* Necessary if derived handling failed. */
+  close_thread_tables(thd);
+  /* Don't keep locks for a failed statement. */
+  thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+  DBUG_RETURN(TRUE);
 }
 
 
@@ -5340,13 +5323,24 @@ bool open_and_lock_tables(THD *thd, TABL
 
 bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags)
 {
+  DML_prelocking_strategy prelocking_strategy;
   uint counter;
+  MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint();
   DBUG_ENTER("open_normal_and_derived_tables");
   DBUG_ASSERT(!thd->fill_derived_tables());
-  if (open_tables(thd, &tables, &counter, flags) ||
+  if (open_tables(thd, &tables, &counter, flags, &prelocking_strategy) ||
       mysql_handle_derived(thd->lex, &mysql_derived_prepare))
-    DBUG_RETURN(TRUE); /* purecov: inspected */
+    goto end;
+
   DBUG_RETURN(0);
+end:
+  /* No need to rollback statement transaction, it's not started. */
+  DBUG_ASSERT(thd->transaction.stmt.is_empty());
+  close_thread_tables(thd);
+  /* Don't keep locks for a failed statement. */
+  thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+
+  DBUG_RETURN(TRUE); /* purecov: inspected */
 }
 
 
@@ -5607,6 +5601,14 @@ void close_tables_for_reopen(THD *thd, T
     /* We have to cleanup translation tables of views. */
     tmp->cleanup_items();
   }
+  /*
+    No need to commit/rollback the statement transaction: it's
+    either not started or we're filling in an INFORMATION_SCHEMA
+    table on the fly, and thus mustn't manipulate with the
+    transaction of the enclosing statement.
+  */
+  DBUG_ASSERT(thd->transaction.stmt.is_empty() ||
+              (thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
   close_thread_tables(thd);
   thd->mdl_context.rollback_to_savepoint(start_of_statement_svp);
 }
@@ -9034,7 +9036,8 @@ open_system_tables_for_read(THD *thd, TA
                            MYSQL_LOCK_IGNORE_TIMEOUT))
   {
     lex->restore_backup_query_tables_list(&query_tables_list_backup);
-    goto error;
+    thd->restore_backup_open_tables_state(backup);
+    DBUG_RETURN(TRUE);
   }
 
   for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global)
@@ -9045,11 +9048,6 @@ open_system_tables_for_read(THD *thd, TA
   lex->restore_backup_query_tables_list(&query_tables_list_backup);
 
   DBUG_RETURN(FALSE);
-
-error:
-  close_system_tables(thd, backup);
-
-  DBUG_RETURN(TRUE);
 }
 
 
@@ -9072,6 +9070,38 @@ close_system_tables(THD *thd, Open_table
 }
 
 
+/**
+  A helper function to close a mysql.* table opened
+  in an auxiliary THD during bootstrap or in the main
+  connection, when we know that there are no locks
+  held by the connection due to a preceding implicit
+  commit.
+
+  This function assumes that there is no
+  statement transaction started for the operation
+  itself, since mysql.* tables are not transactional
+  and when they are used the binlog is off (DDL
+  binlogging is always statement-based.
+
+  We need this function since we'd like to not
+  just close the system table, but also release
+  the metadata lock on it.
+
+  Note, that in LOCK TABLES mode this function
+  does not release the metadata lock. But in this
+  mode the table can be opened only if it is locked
+  explicitly with LOCK TABLES.
+*/
+
+void
+close_mysql_tables(THD *thd)
+{
+  /* No need to commit/rollback statement transaction, it's not started. */
+  DBUG_ASSERT(thd->transaction.stmt.is_empty());
+  close_thread_tables(thd);
+  thd->mdl_context.release_transactional_locks();
+}
+
 /*
   Open and lock one system table for update.
 
@@ -9143,16 +9173,7 @@ open_log_table(THD *thd, TABLE_LIST *one
     table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET;
   }
   else
-  {
-    /*
-      If error in mysql_lock_tables(), open_ltable doesn't close the
-      table. Thread kill during mysql_lock_tables() is such error. But
-      open tables cannot be accepted when restoring the open tables
-      state.
-    */
-    close_thread_tables(thd);
     thd->restore_backup_open_tables_state(backup);
-  }
 
   thd->utime_after_lock= save_utime_after_lock;
   DBUG_RETURN(table);

=== modified file 'sql/sql_base.h'
--- a/sql/sql_base.h	2010-07-01 14:58:47 +0000
+++ b/sql/sql_base.h	2010-07-27 10:25:53 +0000
@@ -240,6 +240,7 @@ bool is_equal(const LEX_STRING *a, const
 bool open_system_tables_for_read(THD *thd, TABLE_LIST *table_list,
                                  Open_tables_backup *backup);
 void close_system_tables(THD *thd, Open_tables_backup *backup);
+void close_mysql_tables(THD *thd);
 TABLE *open_system_table_for_update(THD *thd, TABLE_LIST *one_table);
 TABLE *open_log_table(THD *thd, TABLE_LIST *one_table, Open_tables_backup *backup);
 void close_log_table(THD *thd, Open_tables_backup *backup);

=== modified file 'sql/sql_class.cc'
--- a/sql/sql_class.cc	2010-07-15 13:47:50 +0000
+++ b/sql/sql_class.cc	2010-07-27 10:25:53 +0000
@@ -29,7 +29,6 @@
 #include "sql_priv.h"
 #include "unireg.h"                    // REQUIRED: for other includes
 #include "sql_class.h"
-#include "lock.h"      // unlock_global_read_lock, mysql_unlock_tables
 #include "sql_cache.h"                          // query_cache_abort
 #include "sql_base.h"                           // close_thread_tables
 #include "sql_time.h"                         // date_time_format_copy
@@ -1817,12 +1816,6 @@ bool select_send::send_eof()
   */
   ha_release_temporary_latches(thd);
 
-  /* Unlock tables before sending packet to gain some speed */
-  if (thd->lock && ! thd->locked_tables_mode)
-  {
-    mysql_unlock_tables(thd, thd->lock);
-    thd->lock=0;
-  }
   /* 
     Don't send EOF if we're in error condition (which implies we've already
     sent or are sending an error)

=== modified file 'sql/sql_cursor.cc'
--- a/sql/sql_cursor.cc	2010-06-08 08:08:46 +0000
+++ b/sql/sql_cursor.cc	2010-07-27 10:25:53 +0000
@@ -22,6 +22,7 @@
 #include "sql_select.h"
 #include "probes_mysql.h"
 #include "sql_parse.h"                        // mysql_execute_command
+#include "sql_base.h"
 
 /****************************************************************************
   Declarations.
@@ -523,6 +524,7 @@ Sensitive_cursor::close()
     thd->derived_tables= derived_tables;
     thd->lock= lock;
 
+    close_thread_tables(thd);
     /* Is expected to at least close tables and empty thd->change_list */
     stmt_arena->cleanup_stmt();
 

=== modified file 'sql/sql_do.cc'
--- a/sql/sql_do.cc	2010-03-31 14:05:33 +0000
+++ b/sql/sql_do.cc	2010-07-27 10:25:53 +0000
@@ -39,9 +39,10 @@ bool mysql_do(THD *thd, List<Item> &valu
     /*
       Rollback the effect of the statement, since next instruction
       will clear the error and the rollback in the end of
-      dispatch_command() won't work.
+      mysql_execute_command() won't work.
     */
-    trans_rollback_stmt(thd);
+    if (! thd->in_sub_stmt)
+      trans_rollback_stmt(thd);
     thd->clear_error(); // DO always is OK
   }
   my_ok(thd);

=== modified file 'sql/sql_handler.cc'
--- a/sql/sql_handler.cc	2010-07-13 08:39:24 +0000
+++ b/sql/sql_handler.cc	2010-07-27 10:25:53 +0000
@@ -59,7 +59,7 @@
 #include "key.h"                                // key_copy
 #include "sql_base.h"                           // insert_fields
 #include "sql_select.h"
-#include <assert.h>
+#include "transaction.h"
 
 #define HANDLER_TABLES_HASH_SIZE 120
 
@@ -309,9 +309,15 @@ bool mysql_ha_open(THD *thd, TABLE_LIST 
   }
   if (error)
   {
+    /*
+      No need to rollback statement transaction, it's not started.
+      If called with reopen flag, no need to rollback either,
+      it will be done at statement end.
+    */
+    DBUG_ASSERT(thd->transaction.stmt.is_empty());
     close_thread_tables(thd);
-    thd->set_open_tables(backup_open_tables);
     thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+    thd->set_open_tables(backup_open_tables);
     if (!reopen)
       my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables);
     else
@@ -578,6 +584,11 @@ retry:
   if (sql_handler_lock_error.need_reopen())
   {
     DBUG_ASSERT(!lock && !thd->is_error());
+    /*
+      Always close statement transaction explicitly,
+      so that the engine doesn't have to count locks.
+    */
+    trans_rollback_stmt(thd);
     mysql_ha_close_table(thd, hash_tables);
     goto retry;
   }
@@ -764,12 +775,18 @@ retry:
     num_rows++;
   }
 ok:
+  /*
+    Always close statement transaction explicitly,
+    so that the engine doesn't have to count locks.
+  */
+  trans_commit_stmt(thd);
   mysql_unlock_tables(thd,lock);
   my_eof(thd);
   DBUG_PRINT("exit",("OK"));
   DBUG_RETURN(FALSE);
 
 err:
+  trans_rollback_stmt(thd);
   mysql_unlock_tables(thd,lock);
 err0:
   DBUG_PRINT("exit",("ERROR"));

=== modified file 'sql/sql_insert.cc'
--- a/sql/sql_insert.cc	2010-07-08 21:20:08 +0000
+++ b/sql/sql_insert.cc	2010-07-27 10:25:53 +0000
@@ -1862,7 +1862,10 @@ public:
     while ((row=rows.get()))
       delete row;
     if (table)
+    {
       close_thread_tables(&thd);
+      thd.mdl_context.release_transactional_locks();
+    }
     mysql_mutex_lock(&LOCK_thread_count);
     mysql_mutex_destroy(&mutex);
     mysql_cond_destroy(&cond);
@@ -2414,6 +2417,8 @@ bool Delayed_insert::open_and_lock_table
   }
   if (!(table->file->ha_table_flags() & HA_CAN_INSERT_DELAYED))
   {
+    /* To rollback InnoDB statement transaction. */
+    trans_rollback_stmt(&thd);
     my_error(ER_DELAYED_NOT_SUPPORTED, MYF(ME_FATALERROR),
              table_list.table_name);
     return TRUE;
@@ -2480,12 +2485,6 @@ pthread_handler_t handle_delayed_insert(
       goto err;
     }
 
-    /*
-      Open table requires an initialized lex in case the table is
-      partitioned. The .frm file contains a partial SQL string which is
-      parsed using a lex, that depends on initialized thd->lex.
-    */
-    lex_start(thd);
     thd->lex->sql_command= SQLCOM_INSERT;        // For innodb::store_lock()
     /*
       Statement-based replication of INSERT DELAYED has problems with RAND()
@@ -2619,28 +2618,11 @@ pthread_handler_t handle_delayed_insert(
     }
 
   err:
-    /*
-      mysql_lock_tables() can potentially start a transaction and write
-      a table map. In the event of an error, that transaction has to be
-      rolled back.  We only need to roll back a potential statement
-      transaction, since real transactions are rolled back in
-      close_thread_tables().
-
-      TODO: This is not true any more, table maps are generated on the
-      first call to ha_*_row() instead. Remove code that are used to
-      cover for the case outlined above.
-     */
-    trans_rollback_stmt(thd);
-
     DBUG_LEAVE;
   }
 
-  /*
-    di should be unlinked from the thread handler list and have no active
-    clients
-  */
-
   close_thread_tables(thd);			// Free the table
+  thd->mdl_context.release_transactional_locks();
   di->table=0;
   thd->killed= THD::KILL_CONNECTION;	        // If error
   mysql_cond_broadcast(&di->cond_client);       // Safety
@@ -2648,6 +2630,10 @@ pthread_handler_t handle_delayed_insert(
 
   mysql_mutex_lock(&LOCK_delayed_create);       // Because of delayed_get_table
   mysql_mutex_lock(&LOCK_delayed_insert);
+  /*
+    di should be unlinked from the thread handler list and have no active
+    clients
+  */
   delete di;
   mysql_mutex_unlock(&LOCK_delayed_insert);
   mysql_mutex_unlock(&LOCK_delayed_create);

=== modified file 'sql/sql_lex.cc'
--- a/sql/sql_lex.cc	2010-07-08 21:20:08 +0000
+++ b/sql/sql_lex.cc	2010-07-27 10:25:53 +0000
@@ -450,6 +450,9 @@ void lex_end(LEX *lex)
   }
   reset_dynamic(&lex->plugins);
 
+  delete lex->sphead;
+  lex->sphead= NULL;
+
   DBUG_VOID_RETURN;
 }
 

=== modified file 'sql/sql_parse.cc'
--- a/sql/sql_parse.cc	2010-07-19 08:27:53 +0000
+++ b/sql/sql_parse.cc	2010-07-27 10:25:53 +0000
@@ -115,6 +115,7 @@
    "FUNCTION" : "PROCEDURE")
 
 static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables);
+static void sql_kill(THD *thd, ulong id, bool only_kill_query);
 
 const char *any_db="*any*";	// Special symbol for check_access
 
@@ -413,6 +414,9 @@ void init_update_queries(void)
   sql_command_flags[SQLCOM_FLUSH]=              CF_AUTO_COMMIT_TRANS;
   sql_command_flags[SQLCOM_RESET]=              CF_AUTO_COMMIT_TRANS;
   sql_command_flags[SQLCOM_CHECK]=              CF_AUTO_COMMIT_TRANS;
+  sql_command_flags[SQLCOM_CREATE_SERVER]=      CF_AUTO_COMMIT_TRANS;
+  sql_command_flags[SQLCOM_ALTER_SERVER]=       CF_AUTO_COMMIT_TRANS;
+  sql_command_flags[SQLCOM_DROP_SERVER]=        CF_AUTO_COMMIT_TRANS;
 }
 
 bool sqlcom_can_generate_row_events(const THD *thd)
@@ -568,7 +572,6 @@ static void handle_bootstrap_impl(THD *t
     }
 
     mysql_parse(thd, thd->query(), length, &parser_state);
-    close_thread_tables(thd);			// Free tables
 
     bootstrap_error= thd->is_error();
     thd->protocol->end_statement();
@@ -1139,13 +1142,11 @@ bool dispatch_command(enum enum_server_c
     {
       char *beginning_of_next_stmt= (char*)
         parser_state.m_lip.found_semicolon;
-
-      thd->protocol->end_statement();
-      query_cache_end_of_result(thd);
       /*
         Multiple queries exits, execute them individually
       */
-      close_thread_tables(thd);
+      thd->protocol->end_statement();
+      query_cache_end_of_result(thd);
       ulong length= (ulong)(packet_end - beginning_of_next_stmt);
 
       log_slow_statement(thd);
@@ -1197,38 +1198,54 @@ bool dispatch_command(enum enum_server_c
     char *fields, *packet_end= packet + packet_length, *arg_end;
     /* Locked closure of all tables */
     TABLE_LIST table_list;
-    LEX_STRING conv_name;
-
-    /* used as fields initializator */
-    lex_start(thd);
+    LEX_STRING table_name;
+    LEX_STRING db;
+    /*
+      SHOW statements should not add the used tables to the list of tables
+      used in a transaction.
+    */
+    MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint();
 
     status_var_increment(thd->status_var.com_stat[SQLCOM_SHOW_FIELDS]);
-    bzero((char*) &table_list,sizeof(table_list));
-    if (thd->copy_db_to(&table_list.db, &table_list.db_length))
+    if (thd->copy_db_to(&db.str, &db.length))
       break;
     /*
       We have name + wildcard in packet, separated by endzero
     */
     arg_end= strend(packet);
     uint arg_length= arg_end - packet;
-    
+
     /* Check given table name length. */
     if (arg_length >= packet_length || arg_length > NAME_LEN)
     {
       my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0));
       break;
     }
-    thd->convert_string(&conv_name, system_charset_info,
+    thd->convert_string(&table_name, system_charset_info,
 			packet, arg_length, thd->charset());
-    if (check_table_name(conv_name.str, conv_name.length, FALSE))
+    if (check_table_name(table_name.str, table_name.length, FALSE))
     {
       /* this is OK due to convert_string() null-terminating the string */
-      my_error(ER_WRONG_TABLE_NAME, MYF(0), conv_name.str);
+      my_error(ER_WRONG_TABLE_NAME, MYF(0), table_name.str);
       break;
     }
-
-    table_list.alias= table_list.table_name= conv_name.str;
     packet= arg_end + 1;
+    mysql_reset_thd_for_next_command(thd);
+    lex_start(thd);
+    /* Must be before we init the table list. */
+    if (lower_case_table_names)
+      table_name.length= my_casedn_str(files_charset_info, table_name.str);
+    table_list.init_one_table(db.str, db.length, table_name.str,
+                              table_name.length, table_name.str, TL_READ);
+    /*
+      Init TABLE_LIST members necessary when the undelrying
+      table is view.
+    */
+    table_list.select_lex= &(thd->lex->select_lex);
+    thd->lex->
+      select_lex.table_list.link_in_list(&table_list,
+                                         &table_list.next_local);
+    thd->lex->add_to_query_tables(&table_list);
 
     if (is_infoschema_db(table_list.db, table_list.db_length))
     {
@@ -1242,32 +1259,23 @@ bool dispatch_command(enum enum_server_c
       break;
     thd->set_query(fields, query_length);
     general_log_print(thd, command, "%s %s", table_list.table_name, fields);
-    if (lower_case_table_names)
-      my_casedn_str(files_charset_info, table_list.table_name);
 
-    if (check_access(thd, SELECT_ACL, table_list.db,
-                     &table_list.grant.privilege,
-                     &table_list.grant.m_internal,
-                     0, 0))
-      break;
-    if (check_grant(thd, SELECT_ACL, &table_list, TRUE, UINT_MAX, FALSE))
+    if (check_table_access(thd, SELECT_ACL, &table_list,
+                           TRUE, UINT_MAX, FALSE))
       break;
-    /* init structures for VIEW processing */
-    table_list.select_lex= &(thd->lex->select_lex);
-
-    lex_start(thd);
-    mysql_reset_thd_for_next_command(thd);
-
-    thd->lex->
-      select_lex.table_list.link_in_list(&table_list,
-                                         &table_list.next_local);
-    thd->lex->add_to_query_tables(&table_list);
-    init_mdl_requests(&table_list);
-
-    /* switch on VIEW optimisation: do not fill temporary tables */
+    /*
+      Turn on an optimization relevant if the underlying table
+      is a view: do not fill derived tables.
+    */
     thd->lex->sql_command= SQLCOM_SHOW_FIELDS;
+
     mysqld_list_fields(thd,&table_list,fields);
     thd->lex->unit.cleanup();
+    /* No need to rollback statement transaction, it's not started. */
+    DBUG_ASSERT(thd->transaction.stmt.is_empty());
+    close_thread_tables(thd);
+    thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
+
     thd->cleanup_after_query();
     break;
   }
@@ -1315,7 +1323,6 @@ bool dispatch_command(enum enum_server_c
     ulong options= (ulong) (uchar) packet[0];
     if (trans_commit_implicit(thd))
       break;
-    close_thread_tables(thd);
     thd->mdl_context.release_transactional_locks();
     if (check_global_access(thd,RELOAD_ACL))
       break;
@@ -1377,7 +1384,6 @@ bool dispatch_command(enum enum_server_c
     DBUG_PRINT("quit",("Got shutdown command for level %u", level));
     general_log_print(thd, command, NullS);
     my_eof(thd);
-    close_thread_tables(thd);			// Free before kill
     kill_mysql();
     error=TRUE;
     break;
@@ -1480,29 +1486,9 @@ bool dispatch_command(enum enum_server_c
     my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0));
     break;
   }
-
-  /* report error issued during command execution */
-  if (thd->killed_errno())
-  {
-    if (! thd->stmt_da->is_set())
-      thd->send_kill_message();
-  }
-  if (thd->killed == THD::KILL_QUERY || thd->killed == THD::KILL_BAD_DATA)
-  {
-    thd->killed= THD::NOT_KILLED;
-    thd->mysys_var->abort= 0;
-  }
-
-  /* If commit fails, we should be able to reset the OK status. */
-  thd->stmt_da->can_overwrite_status= TRUE;
-  thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
-  thd->stmt_da->can_overwrite_status= FALSE;
-
-  thd->transaction.stmt.reset();
-
-  thd->proc_info= "closing tables";
-  /* Free tables */
-  close_thread_tables(thd);
+  DBUG_ASSERT(thd->derived_tables == NULL &&
+              (thd->open_tables == NULL ||
+               (thd->locked_tables_mode == LTM_LOCK_TABLES)));
 
   thd->protocol->end_statement();
   query_cache_end_of_result(thd);
@@ -1715,6 +1701,9 @@ int prepare_schema_table(THD *thd, LEX *
   In brief: take exclusive locks, expel tables from the table
   cache, reopen the tables, enter the 'LOCKED TABLES' mode,
   downgrade the locks.
+  Note: the function is written to be called from
+  mysql_execute_command(), it is not reusable in arbitrary
+  execution context.
 
   Required privileges
   -------------------
@@ -1816,9 +1805,9 @@ static bool flush_tables_with_read_lock(
                             &lock_tables_prelocking_strategy) ||
        thd->locked_tables_list.init_locked_tables(thd))
   {
-    close_thread_tables(thd);
     goto error;
   }
+  thd->variables.option_bits|= OPTION_TABLE_LOCK;
 
   /*
     Downgrade the exclusive locks.
@@ -2041,6 +2030,7 @@ mysql_execute_command(THD *thd)
   thd->work_part_info= 0;
 #endif
 
+  DBUG_ASSERT(thd->transaction.stmt.is_empty() || thd->in_sub_stmt);
   /*
     In many cases first table of main SELECT_LEX have special meaning =>
     check that it is first table in global list and relink it first in 
@@ -2222,8 +2212,7 @@ mysql_execute_command(THD *thd)
     /* Commit the normal transaction if one is active. */
     if (trans_commit_implicit(thd))
       goto error;
-    /* Close tables and release metadata locks. */
-    close_thread_tables(thd);
+    /* Release metadata locks acquired in this transaction. */
     thd->mdl_context.release_transactional_locks();
   }
 
@@ -3536,24 +3525,27 @@ end_with_restore_list:
       done FLUSH TABLES WITH READ LOCK + BEGIN. If this assumption becomes
       false, mysqldump will not work.
     */
-    thd->locked_tables_list.unlock_locked_tables(thd);
     if (thd->variables.option_bits & OPTION_TABLE_LOCK)
     {
-      trans_commit_implicit(thd);
+      res= trans_commit_implicit(thd);
+      thd->locked_tables_list.unlock_locked_tables(thd);
       thd->mdl_context.release_transactional_locks();
       thd->variables.option_bits&= ~(OPTION_TABLE_LOCK);
     }
     if (thd->global_read_lock.is_acquired())
       thd->global_read_lock.unlock_global_read_lock(thd);
+    if (res)
+      goto error;
     my_ok(thd);
     break;
   case SQLCOM_LOCK_TABLES:
+    /* We must end the transaction first, regardless of anything */
+    res= trans_commit_implicit(thd);
     thd->locked_tables_list.unlock_locked_tables(thd);
-    /* we must end the trasaction first, regardless of anything */
-    if (trans_commit_implicit(thd))
-      goto error;
-    /* release transactional metadata locks. */
+    /* Release transactional metadata locks. */
     thd->mdl_context.release_transactional_locks();
+    if (res)
+      goto error;
     if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables,
                            FALSE, UINT_MAX, FALSE))
       goto error;
@@ -3576,17 +3568,14 @@ end_with_restore_list:
 
     if (res)
     {
+      trans_rollback_stmt(thd);
       /*
         Need to end the current transaction, so the storage engine (InnoDB)
         can free its locks if LOCK TABLES locked some tables before finding
         that it can't lock a table in its list
       */
-      trans_rollback_stmt(thd);
       trans_commit_implicit(thd);
-      /*
-        Close tables and release metadata locks otherwise a later call to
-        close_thread_tables might not release the locks if autocommit is off.
-      */
+      /* Close tables and release metadata locks. */
       close_thread_tables(thd);
       DBUG_ASSERT(!thd->locked_tables_mode);
       thd->mdl_context.release_transactional_locks();
@@ -4205,9 +4194,7 @@ end_with_restore_list:
         locks in the MDL context, so there is no risk to
         deadlock.
       */
-      trans_commit_implicit(thd);
-      close_thread_tables(thd);
-      thd->mdl_context.release_transactional_locks();
+      close_mysql_tables(thd);
       /*
         Check if the definer exists on slave, 
         then use definer privilege to insert routine privileges to mysql.procs_priv.
@@ -4484,9 +4471,7 @@ create_sp_error:
           locks in the MDL context, so there is no risk to
           deadlock.
         */
-        trans_commit_implicit(thd);
-        close_thread_tables(thd);
-        thd->mdl_context.release_transactional_locks();
+        close_mysql_tables(thd);
 
         if (sp_automatic_privileges && !opt_noacl &&
             sp_revoke_privileges(thd, db, name,
@@ -4778,17 +4763,60 @@ finish:
   DBUG_ASSERT(!thd->in_active_multi_stmt_transaction() ||
                thd->in_multi_stmt_transaction_mode());
 
+
+  if (! thd->in_sub_stmt)
+  {
+    /* report error issued during command execution */
+    if (thd->killed_errno())
+    {
+      if (! thd->stmt_da->is_set())
+        thd->send_kill_message();
+    }
+    if (thd->killed == THD::KILL_QUERY || thd->killed == THD::KILL_BAD_DATA)
+    {
+      thd->killed= THD::NOT_KILLED;
+      thd->mysys_var->abort= 0;
+    }
+    if (thd->is_error() || (thd->variables.option_bits & OPTION_MASTER_SQL_ERROR))
+      trans_rollback_stmt(thd);
+    else
+    {
+      /* If commit fails, we should be able to reset the OK status. */
+      thd->stmt_da->can_overwrite_status= TRUE;
+      trans_commit_stmt(thd);
+      thd->stmt_da->can_overwrite_status= FALSE;
+    }
+  }
+
+  lex->unit.cleanup();
+  /* Free tables */
+  thd_proc_info(thd, "closing tables");
+  close_thread_tables(thd);
+  thd_proc_info(thd, 0);
+
   if (stmt_causes_implicit_commit(thd, CF_IMPLICIT_COMMIT_END))
   {
+    /* No transaction control allowed in sub-statements. */
+    DBUG_ASSERT(! thd->in_sub_stmt);
     /* If commit fails, we should be able to reset the OK status. */
     thd->stmt_da->can_overwrite_status= TRUE;
-    /* Commit or rollback the statement transaction. */
-    thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd);
     /* Commit the normal transaction if one is active. */
     trans_commit_implicit(thd);
     thd->stmt_da->can_overwrite_status= FALSE;
-    /* Close tables and release metadata locks. */
-    close_thread_tables(thd);
+    thd->mdl_context.release_transactional_locks();
+  }
+  else if (! thd->in_sub_stmt && ! thd->in_multi_stmt_transaction_mode())
+  {
+    /*
+      - If inside a multi-statement transaction,
+      defer the release of metadata locks until the current
+      transaction is either committed or rolled back. This prevents
+      other statements from modifying the table for the entire
+      duration of this transaction.  This provides commit ordering
+      and guarantees serializability across multiple transactions.
+      - If in autocommit mode, or outside a transactional context,
+      automatically release metadata locks of the current statement.
+    */
     thd->mdl_context.release_transactional_locks();
   }
 
@@ -5886,12 +5914,6 @@ void mysql_parse(THD *thd, const char *i
 
       query_cache_abort(&thd->query_cache_tls);
     }
-    if (thd->lex->sphead)
-    {
-      delete thd->lex->sphead;
-      thd->lex->sphead= 0;
-    }
-    lex->unit.cleanup();
     thd_proc_info(thd, "freeing items");
     thd->end_statement();
     thd->cleanup_after_query();
@@ -6997,11 +7019,15 @@ uint kill_one_thread(THD *thd, ulong id,
     only_kill_query     Should it kill the query or the connection
 */
 
+static
 void sql_kill(THD *thd, ulong id, bool only_kill_query)
 {
   uint error;
   if (!(error= kill_one_thread(thd, id, only_kill_query)))
-    my_ok(thd);
+  {
+    if (! thd->killed)
+      my_ok(thd);
+  }
   else
     my_error(error, MYF(0), id);
 }

=== modified file 'sql/sql_parse.h'
--- a/sql/sql_parse.h	2010-07-15 17:45:08 +0000
+++ b/sql/sql_parse.h	2010-07-27 10:25:53 +0000
@@ -51,7 +51,6 @@ bool parse_sql(THD *thd,
                Object_creation_ctx *creation_ctx);
 
 uint kill_one_thread(THD *thd, ulong id, bool only_kill_query);
-void sql_kill(THD *thd, ulong id, bool only_kill_query);
 
 void free_items(Item *item);
 void cleanup_items(Item *item);

=== modified file 'sql/sql_partition.cc'
--- a/sql/sql_partition.cc	2010-07-08 21:20:08 +0000
+++ b/sql/sql_partition.cc	2010-07-27 10:25:53 +0000
@@ -59,7 +59,7 @@
 #include "my_md5.h"
 #include "transaction.h"
 
-#include "sql_base.h"                           // close_thread_tables
+#include "sql_base.h"                   // close_all_tables_for_name
 #include "sql_table.h"                  // build_table_filename,
                                         // build_table_shadow_filename,
                                         // table_to_filename
@@ -6758,7 +6758,6 @@ uint fast_alter_partition_table(THD *thd
                                  table_list, FALSE, NULL,
                                  written_bin_log));
 err:
-  close_thread_tables(thd);
   DBUG_RETURN(TRUE);
 }
 #endif

=== modified file 'sql/sql_plugin.cc'
--- a/sql/sql_plugin.cc	2010-07-08 21:20:08 +0000
+++ b/sql/sql_plugin.cc	2010-07-27 10:25:53 +0000
@@ -21,7 +21,7 @@
 #include "sql_locale.h"
 #include "sql_plugin.h"
 #include "sql_parse.h"          // check_table_access
-#include "sql_base.h"                           // close_thread_tables
+#include "sql_base.h"                           // close_mysql_tables
 #include "key.h"                                // key_copy
 #include "sql_show.h"           // remove_status_vars, add_status_vars
 #include "strfunc.h"            // find_set
@@ -1511,8 +1511,8 @@ static void plugin_load(MEM_ROOT *tmp_ro
     sql_print_error(ER(ER_GET_ERRNO), my_errno);
   end_read_record(&read_record_info);
   table->m_needs_reopen= TRUE;                  // Force close to free memory
+  close_mysql_tables(new_thd);
 end:
-  close_thread_tables(new_thd);
   /* Remember that we don't have a THD */
   my_pthread_setspecific_ptr(THR_THD, 0);
   DBUG_VOID_RETURN;

=== modified file 'sql/sql_prepare.cc'
--- a/sql/sql_prepare.cc	2010-06-28 20:32:09 +0000
+++ b/sql/sql_prepare.cc	2010-07-27 10:25:53 +0000
@@ -90,7 +90,7 @@ When one supplies long data for a placeh
 #include "set_var.h"
 #include "sql_prepare.h"
 #include "sql_parse.h" // insert_precheck, update_precheck, delete_precheck
-#include "sql_base.h"  // close_thread_tables
+#include "sql_base.h"  // open_normal_and_derived_tables
 #include "sql_cache.h"                          // query_cache_*
 #include "sql_view.h"                          // create_view_precheck
 #include "sql_delete.h"                        // mysql_prepare_delete
@@ -2989,12 +2989,6 @@ Execute_sql_statement::execute_server_co
 
   error= mysql_execute_command(thd);
 
-  if (thd->killed_errno())
-  {
-    if (! thd->stmt_da->is_set())
-      thd->send_kill_message();
-  }
-
   /* report error issued during command execution */
   if (error == 0 && thd->spcont == NULL)
     general_log_write(thd, COM_STMT_EXECUTE,
@@ -3102,13 +3096,8 @@ void Prepared_statement::cleanup_stmt()
   DBUG_ENTER("Prepared_statement::cleanup_stmt");
   DBUG_PRINT("enter",("stmt: 0x%lx", (long) this));
 
-  delete lex->sphead;
-  lex->sphead= 0;
-  /* The order is important */
-  lex->unit.cleanup();
   cleanup_items(free_list);
   thd->cleanup_after_query();
-  close_thread_tables(thd);
   thd->rollback_item_tree_changes();
 
   DBUG_VOID_RETURN;
@@ -3272,21 +3261,16 @@ bool Prepared_statement::prepare(const c
     to PREPARE stmt FROM "CREATE PROCEDURE ..."
   */
   DBUG_ASSERT(lex->sphead == NULL || error != 0);
-  if (lex->sphead)
-  {
-    delete lex->sphead;
-    lex->sphead= NULL;
-  }
+  /* The order is important */
+  lex->unit.cleanup();
+
+  /* No need to commit statement transaction, it's not started. */
+  DBUG_ASSERT(thd->transaction.stmt.is_empty());
 
+  close_thread_tables(thd);
+  thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
   lex_end(lex);
   cleanup_stmt();
-  /*
-    If not inside a multi-statement transaction, the metadata
-    locks have already been released and our savepoint points
-    to ticket which has been released as well.
-  */
-  if (thd->in_multi_stmt_transaction_mode())
-    thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
   thd->restore_backup_statement(this, &stmt_backup);
   thd->stmt_arena= old_stmt_arena;
 
@@ -3393,11 +3377,6 @@ Prepared_statement::set_parameters(Strin
   and execute of a new statement. If this happens repeatedly
   more than MAX_REPREPARE_ATTEMPTS times, we give up.
 
-  In future we need to be able to keep the metadata locks between
-  prepare and execute, but right now open_and_lock_tables(), as
-  well as close_thread_tables() are buried deep inside
-  execution code (mysql_execute_command()).
-
   @return TRUE if an error, FALSE if success
   @retval  TRUE    either MAX_REPREPARE_ATTEMPTS has been reached,
                    or some general error
@@ -3484,11 +3463,6 @@ Prepared_statement::execute_server_runna
 
   error= server_runnable->execute_server_code(thd);
 
-  delete lex->sphead;
-  lex->sphead= 0;
-  /* The order is important */
-  lex->unit.cleanup();
-  close_thread_tables(thd);
   thd->cleanup_after_query();
 
   thd->restore_active_arena(this, &stmt_backup);

=== modified file 'sql/sql_priv.h'
--- a/sql/sql_priv.h	2010-06-17 13:31:51 +0000
+++ b/sql/sql_priv.h	2010-07-27 10:25:53 +0000
@@ -135,6 +135,16 @@ extern char err_shared_dir[];
   Type of locks to be acquired is specified directly.
 */
 #define SELECT_HIGH_PRIORITY            (1ULL << 34)     // SELECT, user
+/**
+  Is set in slave SQL thread when there was an
+  error on master, which, when is not reproducible
+  on slave (i.e. the query succeeds on slave),
+  is not terminal to the state of repliation,
+  and should be ignored. The slave SQL thread,
+  however, needs to rollback the effects of the
+  succeeded statement to keep replication consistent.
+*/
+#define OPTION_MASTER_SQL_ERROR (1ULL << 35)
 
 
 /* The rest of the file is included in the server only */

=== modified file 'sql/sql_servers.cc'
--- a/sql/sql_servers.cc	2010-03-31 14:05:33 +0000
+++ b/sql/sql_servers.cc	2010-07-27 10:25:53 +0000
@@ -36,7 +36,7 @@
 #include "sql_priv.h"
 #include "sql_servers.h"
 #include "unireg.h"
-#include "sql_base.h"                           // close_thread_tables
+#include "sql_base.h"                           // close_mysql_tables
 #include "records.h"          // init_read_record, end_read_record
 #include "hash_filo.h"
 #include <m_ctype.h>
@@ -280,9 +280,7 @@ bool servers_reload(THD *thd)
   }
 
 end:
-  trans_commit_implicit(thd);
-  close_thread_tables(thd);
-  thd->mdl_context.release_transactional_locks();
+  close_mysql_tables(thd);
   DBUG_PRINT("info", ("unlocking servers_cache"));
   mysql_rwlock_unlock(&THR_LOCK_servers);
   DBUG_RETURN(return_val);
@@ -535,6 +533,7 @@ int insert_server_record(TABLE *table, F
 {
   int error;
   DBUG_ENTER("insert_server_record");
+  tmp_disable_binlog(table->in_use);
   table->use_all_columns();
 
   empty_record(table);
@@ -571,6 +570,8 @@ int insert_server_record(TABLE *table, F
   }
   else
     error= ER_FOREIGN_SERVER_EXISTS;
+
+  reenable_binlog(table->in_use);
   DBUG_RETURN(error);
 }
 
@@ -625,7 +626,7 @@ int drop_server(THD *thd, LEX_SERVER_OPT
   error= delete_server_record(table, name.str, name.length);
 
   /* close the servers table before we call closed_cached_connection_tables */
-  close_thread_tables(thd);
+  close_mysql_tables(thd);
 
   if (close_cached_connection_tables(thd, TRUE, &name))
   {
@@ -880,6 +881,7 @@ update_server_record(TABLE *table, FOREI
 {
   int error=0;
   DBUG_ENTER("update_server_record");
+  tmp_disable_binlog(table->in_use);
   table->use_all_columns();
   /* set the field that's the PK to the value we're looking for */
   table->field[0]->store(server->server_name,
@@ -913,6 +915,7 @@ update_server_record(TABLE *table, FOREI
   }
 
 end:
+  reenable_binlog(table->in_use);
   DBUG_RETURN(error);
 }
 
@@ -938,6 +941,7 @@ delete_server_record(TABLE *table,
 {
   int error;
   DBUG_ENTER("delete_server_record");
+  tmp_disable_binlog(table->in_use);
   table->use_all_columns();
 
   /* set the field that's the PK to the value we're looking for */
@@ -959,6 +963,7 @@ delete_server_record(TABLE *table,
       table->file->print_error(error, MYF(0));
   }
 
+  reenable_binlog(table->in_use);
   DBUG_RETURN(error);
 }
 
@@ -1050,7 +1055,7 @@ int alter_server(THD *thd, LEX_SERVER_OP
   error= update_server(thd, existing, altered);
 
   /* close the servers table before we call closed_cached_connection_tables */
-  close_thread_tables(thd);
+  close_mysql_tables(thd);
 
   if (close_cached_connection_tables(thd, FALSE, &name))
   {

=== modified file 'sql/sql_table.cc'
--- a/sql/sql_table.cc	2010-07-13 08:39:24 +0000
+++ b/sql/sql_table.cc	2010-07-27 10:25:53 +0000
@@ -2285,18 +2285,13 @@ err:
   {
     /*
       Under LOCK TABLES we should release meta-data locks on the tables
-      which were dropped. Otherwise we can rely on close_thread_tables()
-      doing this. Unfortunately in this case we are likely to get more
-      false positives in try_acquire_lock() function. So
-      it makes sense to remove exclusive meta-data locks in all cases.
+      which were dropped.
 
       Leave LOCK TABLES mode if we managed to drop all tables which were
       locked. Additional check for 'non_temp_tables_count' is to avoid
       leaving LOCK TABLES mode if we have dropped only temporary tables.
     */
-    if (! thd->locked_tables_mode)
-      thd->mdl_context.release_transactional_locks();
-    else
+    if (thd->locked_tables_mode)
     {
       if (thd->lock && thd->lock->table_count == 0 && non_temp_tables_count > 0)
       {
@@ -2305,7 +2300,8 @@ err:
       }
       for (table= tables; table; table= table->next_local)
       {
-        if (table->mdl_request.ticket)
+        /* Drop locks for all successfully dropped tables. */
+        if (table->table == NULL && table->mdl_request.ticket)
         {
           /*
             Under LOCK TABLES we may have several instances of table open
@@ -2316,6 +2312,10 @@ err:
         }
       }
     }
+    /*
+      Rely on the caller to implicitly commit the transaction
+      and release metadata locks.
+    */
   }
 
 end:
@@ -4214,8 +4214,14 @@ warn:
 }
 
 
-/*
-  Database and name-locking aware wrapper for mysql_create_table_no_lock(),
+/**
+  Implementation of SQLCOM_CREATE_TABLE.
+
+  Take the metadata locks (including a shared lock on the affected
+  schema) and create the table. Is written to be called from
+  mysql_execute_command(), to which it delegates the common parts
+  with other commands (i.e. implicit commit before and after,
+  close of thread tables.
 */
 
 bool mysql_create_table(THD *thd, TABLE_LIST *create_table,
@@ -4231,7 +4237,7 @@ bool mysql_create_table(THD *thd, TABLE_
   if (open_and_lock_tables(thd, thd->lex->query_tables, FALSE, 0))
   {
     result= TRUE;
-    goto unlock;
+    goto end;
   }
 
   /* Got lock. */
@@ -4253,16 +4259,7 @@ bool mysql_create_table(THD *thd, TABLE_
         !(create_info->options & HA_LEX_CREATE_TMP_TABLE))))
     result= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
 
-  if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
-  {
-    /*
-      close_thread_tables() takes care about both closing open tables (which
-      might be still around in case of error) and releasing metadata locks.
-    */
-    close_thread_tables(thd);
-  }
-
-unlock:
+end:
   DBUG_RETURN(result);
 }
 
@@ -4752,6 +4749,7 @@ static bool mysql_admin_table(THD* thd, 
         trans_rollback_stmt(thd);
         trans_rollback(thd);
         close_thread_tables(thd);
+        thd->mdl_context.release_transactional_locks();
         DBUG_PRINT("admin", ("simple error, admin next table"));
         continue;
       case -1:           // error, message could be written to net
@@ -5038,11 +5036,11 @@ send_result_message:
       trans_commit_stmt(thd);
       trans_commit(thd);
       close_thread_tables(thd);
-      table->table= NULL;
       thd->mdl_context.release_transactional_locks();
+      table->table= NULL;
       if (!result_code) // recreation went ok
       {
-        /* Clear the ticket released in close_thread_tables(). */
+        /* Clear the ticket released above. */
         table->mdl_request.ticket= NULL;
         DEBUG_SYNC(thd, "ha_admin_open_ltable");
         table->mdl_request.set_type(MDL_SHARED_WRITE);
@@ -6729,13 +6727,11 @@ bool mysql_alter_table(THD *thd,char *ne
         goto err;
       DBUG_EXECUTE_IF("sleep_alter_enable_indexes", my_sleep(6000000););
       error= table->file->ha_enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
-      /* COND_refresh will be signaled in close_thread_tables() */
       break;
     case DISABLE:
       if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
         goto err;
       error=table->file->ha_disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
-      /* COND_refresh will be signaled in close_thread_tables() */
       break;
     default:
       DBUG_ASSERT(FALSE);
@@ -6821,8 +6817,8 @@ bool mysql_alter_table(THD *thd,char *ne
     {
       /*
         Under LOCK TABLES we should adjust meta-data locks before finishing
-        statement. Otherwise we can rely on close_thread_tables() releasing
-        them.
+        statement. Otherwise we can rely on them being released
+        along with the implicit commit.
       */
       if (new_name != table_name || new_db != db)
       {
@@ -7360,8 +7356,8 @@ bool mysql_alter_table(THD *thd,char *ne
     5) Write statement to the binary log.
     6) If we are under LOCK TABLES and do ALTER TABLE ... RENAME we
        remove placeholders and release metadata locks.
-    7) If we are not not under LOCK TABLES we rely on close_thread_tables()
-       call to remove placeholders and releasing metadata locks.
+    7) If we are not not under LOCK TABLES we rely on the caller
+      (mysql_execute_command()) to release metadata locks.
   */
 
   thd_proc_info(thd, "rename result table");
@@ -7990,7 +7986,13 @@ bool mysql_checksum_table(THD *thd, TABL
 	}
       }
       thd->clear_error();
+      if (! thd->in_sub_stmt)
+        trans_rollback_stmt(thd);
       close_thread_tables(thd);
+      /*
+        Don't release metadata locks, this will be done at
+        statement end.
+      */
       table->table=0;				// For query cache
     }
     if (protocol->write())
@@ -8000,10 +8002,7 @@ bool mysql_checksum_table(THD *thd, TABL
   my_eof(thd);
   DBUG_RETURN(FALSE);
 
- err:
-  close_thread_tables(thd);			// Shouldn't be needed
-  if (table)
-    table->table=0;
+err:
   DBUG_RETURN(TRUE);
 }
 

=== modified file 'sql/sql_trigger.cc'
--- a/sql/sql_trigger.cc	2010-06-17 13:31:51 +0000
+++ b/sql/sql_trigger.cc	2010-07-27 10:25:53 +0000
@@ -540,9 +540,9 @@ end:
   }
 
   /*
-    If we are under LOCK TABLES we should restore original state of meta-data
-    locks. Otherwise call to close_thread_tables() will take care about both
-    TABLE instance created by open_n_lock_single_table() and metadata lock.
+    If we are under LOCK TABLES we should restore original state of
+    meta-data locks. Otherwise all locks will be released along
+    with the implicit commit.
   */
   if (thd->locked_tables_mode && tables && lock_upgrade_done)
     mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE);
@@ -1321,6 +1321,7 @@ bool Table_triggers_list::check_n_load(T
       thd->reset_db((char*) db, strlen(db));
       while ((trg_create_str= it++))
       {
+        sp_head *sp;
         trg_sql_mode= itm++;
         LEX_STRING *trg_definer= it_definer++;
 
@@ -1357,13 +1358,14 @@ bool Table_triggers_list::check_n_load(T
         */
         lex.set_trg_event_type_for_tables();
 
-        lex.sphead->set_info(0, 0, &lex.sp_chistics, (ulong) *trg_sql_mode);
-
         int event= lex.trg_chistics.event;
         int action_time= lex.trg_chistics.action_time;
 
-        lex.sphead->set_creation_ctx(creation_ctx);
-        triggers->bodies[event][action_time]= lex.sphead;
+        sp= triggers->bodies[event][action_time]= lex.sphead;
+        lex.sphead= NULL; /* Prevent double cleanup. */
+
+        sp->set_info(0, 0, &lex.sp_chistics, (ulong) *trg_sql_mode);
+        sp->set_creation_ctx(creation_ctx);
 
         if (!trg_definer->length)
         {
@@ -1376,27 +1378,26 @@ bool Table_triggers_list::check_n_load(T
             push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
                                 ER_TRG_NO_DEFINER, ER(ER_TRG_NO_DEFINER),
                                 (const char*) db,
-                                (const char*) lex.sphead->m_name.str);
+                                (const char*) sp->m_name.str);
           
           /*
             Set definer to the '' to correct displaying in the information
             schema.
           */
 
-          lex.sphead->set_definer((char*) "", 0);
+          sp->set_definer((char*) "", 0);
 
           /*
             Triggers without definer information are executed under the
             authorization of the invoker.
           */
 
-          lex.sphead->m_chistics->suid= SP_IS_NOT_SUID;
+          sp->m_chistics->suid= SP_IS_NOT_SUID;
         }
         else
-          lex.sphead->set_definer(trg_definer->str, trg_definer->length);
+          sp->set_definer(trg_definer->str, trg_definer->length);
 
-        if (triggers->names_list.push_back(&lex.sphead->m_name,
-                                           &table->mem_root))
+        if (triggers->names_list.push_back(&sp->m_name, &table->mem_root))
             goto err_with_lex_cleanup;
 
         if (!(on_table_name= alloc_lex_string(&table->mem_root)))

=== modified file 'sql/sql_udf.cc'
--- a/sql/sql_udf.cc	2010-06-11 15:28:18 +0000
+++ b/sql/sql_udf.cc	2010-07-27 10:25:53 +0000
@@ -33,7 +33,7 @@
 
 #include "sql_priv.h"
 #include "unireg.h"
-#include "sql_base.h"                           // close_thread_tables
+#include "sql_base.h"                           // close_mysql_tables
 #include "sql_parse.h"                        // check_identifier_name
 #include "sql_table.h"                        // write_bin_log
 #include "records.h"          // init_read_record, end_read_record
@@ -251,7 +251,7 @@ void udf_init()
   table->m_needs_reopen= TRUE;                  // Force close to free memory
 
 end:
-  close_thread_tables(new_thd);
+  close_mysql_tables(new_thd);
   delete new_thd;
   /* Remember that we don't have a THD */
   my_pthread_setspecific_ptr(THR_THD,  0);

=== modified file 'sql/sys_vars.cc'
--- a/sql/sys_vars.cc	2010-07-15 17:45:08 +0000
+++ b/sql/sys_vars.cc	2010-07-27 10:25:53 +0000
@@ -2203,14 +2203,21 @@ static bool fix_autocommit(sys_var *self
       thd->variables.option_bits & OPTION_NOT_AUTOCOMMIT)
   { // activating autocommit
 
-    if (trans_commit(thd))
+    if (trans_commit_stmt(thd) || trans_commit(thd))
     {
       thd->variables.option_bits&= ~OPTION_AUTOCOMMIT;
       return true;
     }
-    close_thread_tables(thd);
-    thd->mdl_context.release_transactional_locks();
-
+    /*
+      Don't close thread tables or release metadata locks: if we do so, we
+      risk releasing locks/closing tables of expressions used to assign
+      other variables, as in:
+      set @var=my_stored_function1(), @@autocommit=1, @var2=(select max(a)
+      from my_table), ...
+      The locks will be released at statement end anyway, as SET
+      statement that assigns autocommit is marked to commit
+      transaction implicitly at the end (@sa stmt_causes_implicitcommit()).
+    */
     thd->variables.option_bits&=
                  ~(OPTION_BEGIN | OPTION_KEEP_LOG | OPTION_NOT_AUTOCOMMIT);
     thd->transaction.all.modified_non_trans_table= false;

=== modified file 'sql/transaction.cc'
--- a/sql/transaction.cc	2010-06-25 07:32:24 +0000
+++ b/sql/transaction.cc	2010-07-27 10:25:53 +0000
@@ -28,6 +28,12 @@ static bool trans_check(THD *thd)
   enum xa_states xa_state= thd->transaction.xid_state.xa_state;
   DBUG_ENTER("trans_check");
 
+  /*
+    Always commit statement transaction before manipulating with
+    the normal one.
+  */
+  DBUG_ASSERT(thd->transaction.stmt.is_empty());
+
   if (unlikely(thd->in_sub_stmt))
     my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
   if (xa_state != XA_NOTR)
@@ -252,6 +258,14 @@ bool trans_commit_stmt(THD *thd)
 {
   DBUG_ENTER("trans_commit_stmt");
   int res= FALSE;
+  /*
+    We currently don't invoke commit/rollback at end of
+    a sub-statement.  In future, we perhaps should take
+    a savepoint for each nested statement, and release the
+    savepoint when statement has succeeded.
+  */
+  DBUG_ASSERT(! thd->in_sub_stmt);
+
   if (thd->transaction.stmt.ha_list)
   {
     res= ha_commit_trans(thd, FALSE);
@@ -267,6 +281,9 @@ bool trans_commit_stmt(THD *thd)
     RUN_HOOK(transaction, after_rollback, (thd, FALSE));
   else
     RUN_HOOK(transaction, after_commit, (thd, FALSE));
+
+  thd->transaction.stmt.reset();
+
   DBUG_RETURN(test(res));
 }
 
@@ -283,6 +300,14 @@ bool trans_rollback_stmt(THD *thd)
 {
   DBUG_ENTER("trans_rollback_stmt");
 
+  /*
+    We currently don't invoke commit/rollback at end of
+    a sub-statement.  In future, we perhaps should take
+    a savepoint for each nested statement, and release the
+    savepoint when statement has succeeded.
+  */
+  DBUG_ASSERT(! thd->in_sub_stmt);
+
   if (thd->transaction.stmt.ha_list)
   {
     ha_rollback_trans(thd, FALSE);
@@ -294,6 +319,8 @@ bool trans_rollback_stmt(THD *thd)
 
   RUN_HOOK(transaction, after_rollback, (thd, FALSE));
 
+  thd->transaction.stmt.reset();
+
   DBUG_RETURN(FALSE);
 }
 

=== modified file 'sql/tztime.cc'
--- a/sql/tztime.cc	2010-07-15 11:13:30 +0000
+++ b/sql/tztime.cc	2010-07-27 10:25:53 +0000
@@ -50,13 +50,6 @@
                                                 // MYSQL_LOCK_IGNORE_TIMEOUT
 
 /*
-  This forward declaration is needed because including sql_base.h
-  causes further includes.  [TODO] Eliminate this forward declaration
-  and include a file with the prototype instead.
-*/
-extern void close_thread_tables(THD *thd);
-
-/*
   Now we don't use abbreviations in server but we will do this in future.
 */
 #if defined(TZINFO2SQL) || defined(TESTTIME)
@@ -1784,10 +1777,7 @@ end_with_setting_default_tz:
 
 end_with_close:
   if (time_zone_tables_exist)
-  {
-    close_thread_tables(thd);
-    thd->mdl_context.release_transactional_locks();
-  }
+    close_mysql_tables(thd);
 
 end_with_cleanup:
 


Attachment: [text/bzr-bundle] bzr/kostja@sun.com-20100727102553-b4n2ojcyfj79l2x7.bundle
Thread
bzr push into mysql-trunk-runtime branch (kostja:3085 to 3086)Bug#52044 Bug#55452Konstantin Osipov27 Jul