diff options
240 files changed, 28027 insertions, 8062 deletions
diff --git a/.bzrignore b/.bzrignore index 668a8824576..61177d5ed62 100644 --- a/.bzrignore +++ b/.bzrignore @@ -3070,5 +3070,8 @@ libmysqld/rpl_handler.cc libmysqld/debug_sync.cc libmysqld/rpl_handler.cc dbug/tests +libmysqld/mdl.cc +client/transaction.h +libmysqld/transaction.cc libmysqld/sys_vars.cc libmysqld/keycaches.cc diff --git a/client/Makefile.am b/client/Makefile.am index e94d638a8be..feb71a491e4 100644 --- a/client/Makefile.am +++ b/client/Makefile.am @@ -107,7 +107,8 @@ sql_src=log_event.h mysql_priv.h rpl_constants.h \ rpl_utility.h rpl_tblmap.h rpl_tblmap.cc \ log_event.cc my_decimal.h my_decimal.cc \ log_event_old.h log_event_old.cc \ - rpl_record_old.h rpl_record_old.cc + rpl_record_old.h rpl_record_old.cc \ + transaction.h strings_src=decimal.c dtoa.c link_sources: diff --git a/include/my_base.h b/include/my_base.h index 226a6c44733..b9d0bb48480 100644 --- a/include/my_base.h +++ b/include/my_base.h @@ -191,10 +191,11 @@ enum ha_extra_function { /* Inform handler that we will do a rename */ HA_EXTRA_PREPARE_FOR_RENAME, /* - Orders MERGE handler to attach or detach its child tables. Used at - begin and end of a statement. + Special actions for MERGE tables. */ + HA_EXTRA_ADD_CHILDREN_LIST, HA_EXTRA_ATTACH_CHILDREN, + HA_EXTRA_IS_ATTACHED_CHILDREN, HA_EXTRA_DETACH_CHILDREN }; diff --git a/include/my_global.h b/include/my_global.h index 27fac096c19..3fe2a4d61c9 100644 --- a/include/my_global.h +++ b/include/my_global.h @@ -1598,7 +1598,7 @@ inline void operator delete[](void*, void*) { /* Do nothing */ } #if !defined(max) #define max(a, b) ((a) > (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b)) -#endif +#endif /* Only Linux is known to need an explicit sync of the directory to make sure a file creation/deletion/renaming in(from,to) this directory durable. @@ -1611,6 +1611,25 @@ inline void operator delete[](void*, void*) { /* Do nothing */ } #define bool In_C_you_should_use_my_bool_instead() #endif +/* Provide __func__ macro definition for platforms that miss it. */ +#if __STDC_VERSION__ < 199901L +# if __GNUC__ >= 2 +# define __func__ __FUNCTION__ +# else +# define __func__ "<unknown>" +# endif +#elif defined(_MSC_VER) +# if _MSC_VER < 1300 +# define __func__ "<unknown>" +# else +# define __func__ __FUNCTION__ +# endif +#elif defined(__BORLANDC__) +# define __func__ __FUNC__ +#else +# define __func__ "<unknown>" +#endif + #ifndef HAVE_RINT /** All integers up to this number can be represented exactly as double precision diff --git a/include/my_sys.h b/include/my_sys.h index 57f326321fd..bb6f0188071 100644 --- a/include/my_sys.h +++ b/include/my_sys.h @@ -235,6 +235,9 @@ extern void (*fatal_error_handler_hook)(uint my_err, const char *str, extern uint my_file_limit; extern ulong my_thread_stack_size; +extern const char *(*proc_info_hook)(void *, const char *, const char *, + const char *, const unsigned int); + #ifdef HAVE_LARGE_PAGES extern my_bool my_use_large_pages; extern uint my_large_page_size; diff --git a/include/probes_mysql_nodtrace.h b/include/probes_mysql_nodtrace.h deleted file mode 100644 index bc3b65a00e5..00000000000 --- a/include/probes_mysql_nodtrace.h +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Generated by dheadgen(1). - */ - -#ifndef _PROBES_MYSQL_D -#define _PROBES_MYSQL_D - -#ifdef __cplusplus -extern "C" { -#endif - -#define MYSQL_CONNECTION_START(arg0, arg1, arg2) -#define MYSQL_CONNECTION_START_ENABLED() (0) -#define MYSQL_CONNECTION_DONE(arg0, arg1) -#define MYSQL_CONNECTION_DONE_ENABLED() (0) -#define MYSQL_COMMAND_START(arg0, arg1, arg2, arg3) -#define MYSQL_COMMAND_START_ENABLED() (0) -#define MYSQL_COMMAND_DONE(arg0) -#define MYSQL_COMMAND_DONE_ENABLED() (0) -#define MYSQL_QUERY_START(arg0, arg1, arg2, arg3, arg4) -#define MYSQL_QUERY_START_ENABLED() (0) -#define MYSQL_QUERY_DONE(arg0) -#define MYSQL_QUERY_DONE_ENABLED() (0) -#define MYSQL_QUERY_PARSE_START(arg0) -#define MYSQL_QUERY_PARSE_START_ENABLED() (0) -#define MYSQL_QUERY_PARSE_DONE(arg0) -#define MYSQL_QUERY_PARSE_DONE_ENABLED() (0) -#define MYSQL_QUERY_CACHE_HIT(arg0, arg1) -#define MYSQL_QUERY_CACHE_HIT_ENABLED() (0) -#define MYSQL_QUERY_CACHE_MISS(arg0) -#define MYSQL_QUERY_CACHE_MISS_ENABLED() (0) -#define MYSQL_QUERY_EXEC_START(arg0, arg1, arg2, arg3, arg4, arg5) -#define MYSQL_QUERY_EXEC_START_ENABLED() (0) -#define MYSQL_QUERY_EXEC_DONE(arg0) -#define MYSQL_QUERY_EXEC_DONE_ENABLED() (0) -#define MYSQL_INSERT_ROW_START(arg0, arg1) -#define MYSQL_INSERT_ROW_START_ENABLED() (0) -#define MYSQL_INSERT_ROW_DONE(arg0) -#define MYSQL_INSERT_ROW_DONE_ENABLED() (0) -#define MYSQL_UPDATE_ROW_START(arg0, arg1) -#define MYSQL_UPDATE_ROW_START_ENABLED() (0) -#define MYSQL_UPDATE_ROW_DONE(arg0) -#define MYSQL_UPDATE_ROW_DONE_ENABLED() (0) -#define MYSQL_DELETE_ROW_START(arg0, arg1) -#define MYSQL_DELETE_ROW_START_ENABLED() (0) -#define MYSQL_DELETE_ROW_DONE(arg0) -#define MYSQL_DELETE_ROW_DONE_ENABLED() (0) -#define MYSQL_READ_ROW_START(arg0, arg1, arg2) -#define MYSQL_READ_ROW_START_ENABLED() (0) -#define MYSQL_READ_ROW_DONE(arg0) -#define MYSQL_READ_ROW_DONE_ENABLED() (0) -#define MYSQL_INDEX_READ_ROW_START(arg0, arg1) -#define MYSQL_INDEX_READ_ROW_START_ENABLED() (0) -#define MYSQL_INDEX_READ_ROW_DONE(arg0) -#define MYSQL_INDEX_READ_ROW_DONE_ENABLED() (0) -#define MYSQL_HANDLER_RDLOCK_START(arg0, arg1) -#define MYSQL_HANDLER_RDLOCK_START_ENABLED() (0) -#define MYSQL_HANDLER_WRLOCK_START(arg0, arg1) -#define MYSQL_HANDLER_WRLOCK_START_ENABLED() (0) -#define MYSQL_HANDLER_UNLOCK_START(arg0, arg1) -#define MYSQL_HANDLER_UNLOCK_START_ENABLED() (0) -#define MYSQL_HANDLER_RDLOCK_DONE(arg0) -#define MYSQL_HANDLER_RDLOCK_DONE_ENABLED() (0) -#define MYSQL_HANDLER_WRLOCK_DONE(arg0) -#define MYSQL_HANDLER_WRLOCK_DONE_ENABLED() (0) -#define MYSQL_HANDLER_UNLOCK_DONE(arg0) -#define MYSQL_HANDLER_UNLOCK_DONE_ENABLED() (0) -#define MYSQL_FILESORT_START(arg0, arg1) -#define MYSQL_FILESORT_START_ENABLED() (0) -#define MYSQL_FILESORT_DONE(arg0, arg1) -#define MYSQL_FILESORT_DONE_ENABLED() (0) -#define MYSQL_SELECT_START(arg0) -#define MYSQL_SELECT_START_ENABLED() (0) -#define MYSQL_SELECT_DONE(arg0, arg1) -#define MYSQL_SELECT_DONE_ENABLED() (0) -#define MYSQL_INSERT_START(arg0) -#define MYSQL_INSERT_START_ENABLED() (0) -#define MYSQL_INSERT_DONE(arg0, arg1) -#define MYSQL_INSERT_DONE_ENABLED() (0) -#define MYSQL_INSERT_SELECT_START(arg0) -#define MYSQL_INSERT_SELECT_START_ENABLED() (0) -#define MYSQL_INSERT_SELECT_DONE(arg0, arg1) -#define MYSQL_INSERT_SELECT_DONE_ENABLED() (0) -#define MYSQL_UPDATE_START(arg0) -#define MYSQL_UPDATE_START_ENABLED() (0) -#define MYSQL_UPDATE_DONE(arg0, arg1, arg2) -#define MYSQL_UPDATE_DONE_ENABLED() (0) -#define MYSQL_MULTI_UPDATE_START(arg0) -#define MYSQL_MULTI_UPDATE_START_ENABLED() (0) -#define MYSQL_MULTI_UPDATE_DONE(arg0, arg1, arg2) -#define MYSQL_MULTI_UPDATE_DONE_ENABLED() (0) -#define MYSQL_DELETE_START(arg0) -#define MYSQL_DELETE_START_ENABLED() (0) -#define MYSQL_DELETE_DONE(arg0, arg1) -#define MYSQL_DELETE_DONE_ENABLED() (0) -#define MYSQL_MULTI_DELETE_START(arg0) -#define MYSQL_MULTI_DELETE_START_ENABLED() (0) -#define MYSQL_MULTI_DELETE_DONE(arg0, arg1) -#define MYSQL_MULTI_DELETE_DONE_ENABLED() (0) -#define MYSQL_NET_READ_START() -#define MYSQL_NET_READ_START_ENABLED() (0) -#define MYSQL_NET_READ_DONE(arg0, arg1) -#define MYSQL_NET_READ_DONE_ENABLED() (0) -#define MYSQL_NET_WRITE_START(arg0) -#define MYSQL_NET_WRITE_START_ENABLED() (0) -#define MYSQL_NET_WRITE_DONE(arg0) -#define MYSQL_NET_WRITE_DONE_ENABLED() (0) -#define MYSQL_KEYCACHE_READ_START(arg0, arg1, arg2, arg3) -#define MYSQL_KEYCACHE_READ_START_ENABLED() (0) -#define MYSQL_KEYCACHE_READ_BLOCK(arg0) -#define MYSQL_KEYCACHE_READ_BLOCK_ENABLED() (0) -#define MYSQL_KEYCACHE_READ_HIT() -#define MYSQL_KEYCACHE_READ_HIT_ENABLED() (0) -#define MYSQL_KEYCACHE_READ_MISS() -#define MYSQL_KEYCACHE_READ_MISS_ENABLED() (0) -#define MYSQL_KEYCACHE_READ_DONE(arg0, arg1) -#define MYSQL_KEYCACHE_READ_DONE_ENABLED() (0) -#define MYSQL_KEYCACHE_WRITE_START(arg0, arg1, arg2, arg3) -#define MYSQL_KEYCACHE_WRITE_START_ENABLED() (0) -#define MYSQL_KEYCACHE_WRITE_BLOCK(arg0) -#define MYSQL_KEYCACHE_WRITE_BLOCK_ENABLED() (0) -#define MYSQL_KEYCACHE_WRITE_DONE(arg0, arg1) -#define MYSQL_KEYCACHE_WRITE_DONE_ENABLED() (0) - -#ifdef __cplusplus -} -#endif - -#endif /* _PROBES_MYSQL_D */ diff --git a/include/thr_lock.h b/include/thr_lock.h index d669d2dd2ab..3dc8857dcd0 100644 --- a/include/thr_lock.h +++ b/include/thr_lock.h @@ -161,6 +161,8 @@ void thr_unlock(THR_LOCK_DATA *data); enum enum_thr_lock_result thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_OWNER *owner); void thr_multi_unlock(THR_LOCK_DATA **data,uint count); +void +thr_lock_merge_status(THR_LOCK_DATA **data, uint count); void thr_abort_locks(THR_LOCK *lock, my_bool upgrade_lock); my_bool thr_abort_locks_for_thread(THR_LOCK *lock, my_thread_id thread); void thr_print_locks(void); /* For debugging */ diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index 2eeb092b759..dbe2584a8a9 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -134,6 +134,7 @@ SET(LIBMYSQLD_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/partition_info.cc ../sql/sql_connect.cc ../sql/scheduler.cc ../sql/event_parse_data.cc ../sql/sql_signal.cc ../sql/rpl_handler.cc + ../sql/mdl.cc ../sql/transaction.cc ${GEN_SOURCES} ${LIB_SOURCES}) diff --git a/libmysqld/Makefile.am b/libmysqld/Makefile.am index c3e3086b446..20df55ef418 100644 --- a/libmysqld/Makefile.am +++ b/libmysqld/Makefile.am @@ -75,11 +75,10 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \ sp_head.cc sp_pcontext.cc sp.cc sp_cache.cc sp_rcontext.cc \ parse_file.cc sql_view.cc sql_trigger.cc my_decimal.cc \ rpl_filter.cc sql_partition.cc sql_builtin.cc sql_plugin.cc \ - debug_sync.cc \ - sql_tablespace.cc \ + debug_sync.cc sql_tablespace.cc transaction.cc \ rpl_injector.cc my_user.c partition_info.cc \ sql_servers.cc event_parse_data.cc sql_signal.cc \ - rpl_handler.cc keycaches.cc + rpl_handler.cc mdl.cc keycaches.cc libmysqld_int_a_SOURCES= $(libmysqld_sources) nodist_libmysqld_int_a_SOURCES= $(libmysqlsources) $(sqlsources) diff --git a/mysql-test/extra/binlog_tests/drop_table.test b/mysql-test/extra/binlog_tests/drop_table.test new file mode 100644 index 00000000000..c55cbb67560 --- /dev/null +++ b/mysql-test/extra/binlog_tests/drop_table.test @@ -0,0 +1,34 @@ +# +# Bug#989: If DROP TABLE while there's an active transaction, wrong binlog order +# + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +connect (con1,localhost,root,,); +connect (con2,localhost,root,,); + +connection con1; +RESET MASTER; +CREATE TABLE t1 (a INT); +SET AUTOCOMMIT=OFF; +BEGIN; +INSERT INTO t1 VALUES(1); + +connection con2; +--send DROP TABLE t1; + +connection con1; +COMMIT; + +connection con2; +--reap + +connection default; + +--disconnect con1 +--disconnect con2 + +let $VERSION=`select version()`; +source include/show_binlog_events.inc; diff --git a/mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test b/mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test index da0b77fbc23..7c928b565dd 100644 --- a/mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test +++ b/mysql-test/extra/binlog_tests/mix_innodb_myisam_binlog.test @@ -204,6 +204,10 @@ select (@after:=unix_timestamp())*0; # always give repeatable output # the bug, the reap would return immediately after the insert into t2. select (@after-@before) >= 2; +connection con3; +commit; + +connection con2; drop table t1,t2; commit; diff --git a/mysql-test/include/binlog_inject_error.inc b/mysql-test/include/binlog_inject_error.inc deleted file mode 100644 index 6465f7943a4..00000000000 --- a/mysql-test/include/binlog_inject_error.inc +++ /dev/null @@ -1,22 +0,0 @@ -# -# === Name -# -# binlog_inject_error.inc -# -# === Description -# -# Inject binlog write error when running the query, verifies that the -# query is ended with the proper error (ER_ERROR_ON_WRITE). -# -# === Usage -# -# let query= 'CREATE TABLE t1 (a INT)'; -# source include/binlog_inject_error.inc; -# - -SET GLOBAL debug='d,injecting_fault_writing'; ---echo $query; ---replace_regex /(errno: .*)/(errno: #)/ ---error ER_ERROR_ON_WRITE ---eval $query -SET GLOBAL debug=''; diff --git a/mysql-test/include/commit.inc b/mysql-test/include/commit.inc index d91ba8291fd..d5f10a6d78e 100644 --- a/mysql-test/include/commit.inc +++ b/mysql-test/include/commit.inc @@ -725,15 +725,15 @@ call p_verify_status_increment(4, 4, 4, 4); alter table t3 add column (b int); call p_verify_status_increment(2, 0, 2, 0); alter table t3 rename t4; -call p_verify_status_increment(2, 2, 2, 2); +call p_verify_status_increment(4, 4, 4, 4); rename table t4 to t3; -call p_verify_status_increment(2, 2, 2, 2); +call p_verify_status_increment(0, 0, 0, 0); truncate table t3; call p_verify_status_increment(4, 4, 4, 4); create view v1 as select * from t2; -call p_verify_status_increment(1, 0, 1, 0); +call p_verify_status_increment(2, 0, 2, 0); check table t1; -call p_verify_status_increment(3, 0, 3, 0); +call p_verify_status_increment(2, 0, 2, 0); --echo # Sic: after this bug is fixed, CHECK leaves no pending transaction commit; call p_verify_status_increment(0, 0, 0, 0); diff --git a/mysql-test/include/handler.inc b/mysql-test/include/handler.inc index 6e7f53ba9b2..715b2c74bf2 100644 --- a/mysql-test/include/handler.inc +++ b/mysql-test/include/handler.inc @@ -518,12 +518,15 @@ connect (flush,localhost,root,,); connection flush; --echo connection: flush --send flush tables; -connection default; ---echo connection: default +connect (waiter,localhost,root,,); +connection waiter; +--echo connection: waiter let $wait_condition= select count(*) = 1 from information_schema.processlist where state = "Flushing tables"; --source include/wait_condition.inc +connection default; +--echo connection: default handler t2 open; handler t2 read first; handler t1 read next; @@ -540,7 +543,7 @@ disconnect flush; # --disable_warnings -drop table if exists t1,t2; +drop table if exists t1, t0; --enable_warnings create table t1 (c1 int); --echo connection: default @@ -549,24 +552,47 @@ handler t1 read first; connect (flush,localhost,root,,); connection flush; --echo connection: flush ---send rename table t1 to t2; -connection default; ---echo connection: default +--send rename table t1 to t0; +connection waiter; +--echo connection: waiter let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Waiting for table" and info = "rename table t1 to t2"; + where state = "Waiting for table" and info = "rename table t1 to t0"; --source include/wait_condition.inc -handler t2 open; -handler t2 read first; ---error ER_NO_SUCH_TABLE -handler t1 read next; -handler t1 close; -handler t2 close; +connection default; +--echo connection: default +--echo # +--echo # RENAME placed two pending locks and waits. +--echo # When HANDLER t0 OPEN does open_tables(), it calls +--echo # mysql_ha_flush(), which in turn closes the open HANDLER for t1. +--echo # RENAME TABLE gets unblocked. If it gets scheduled quickly +--echo # and manages to complete before open_tables() +--echo # of HANDLER t0 OPEN, open_tables() and therefore the whole +--echo # HANDLER t0 OPEN succeeds. Otherwise open_tables() +--echo # notices a pending or active exclusive metadata lock on t2 +--echo # and the whole HANDLER t0 OPEN fails with ER_LOCK_DEADLOCK +--echo # error. +--echo # +--error 0, ER_LOCK_DEADLOCK +handler t0 open; +--error 0, ER_UNKNOWN_TABLE +handler t0 close; +--echo connection: flush connection flush; reap; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--error ER_UNKNOWN_TABLE +handler t1 close; connection default; -drop table t2; +drop table t0; +connection flush; disconnect flush; +--source include/wait_until_disconnected.inc +connection waiter; +disconnect waiter; +--source include/wait_until_disconnected.inc +connection default; # # Bug#30882 Dropping a temporary table inside a stored function may cause a server crash @@ -699,27 +725,61 @@ handler t1 read a next; # Bug#41112: crash in mysql_ha_close_table/get_lock_data with alter table # +connect(con1,localhost,root,,); +connect(con2,localhost,root,,); + +connection default; --disable_warnings drop table if exists t1; --enable_warnings +--echo # First test case which is supposed trigger the execution +--echo # path on which problem was discovered. create table t1 (a int); insert into t1 values (1); handler t1 open; -connect(con1,localhost,root,,); +connection con1; +lock table t1 write; send alter table t1 engine=memory; -connection default; +connection con2; let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "rename result table" and info = "alter table t1 engine=memory"; + where state = "Waiting for table" and info = "alter table t1 engine=memory"; --source include/wait_condition.inc +connection default; --error ER_ILLEGAL_HA handler t1 read a next; handler t1 close; connection con1; --reap +unlock tables; +drop table t1; +--echo # Now test case which was reported originally but which no longer +--echo # triggers execution path which has caused the problem. +connection default; +create table t1 (a int, key(a)); +insert into t1 values (1); +handler t1 open; +connection con1; +send alter table t1 engine=memory; +connection con2; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 engine=memory"; +--source include/wait_condition.inc +connection default; +--echo # Since S metadata lock was already acquired at HANDLER OPEN time +--echo # and TL_READ lock requested by HANDLER READ is compatible with +--echo # ALTER's TL_WRITE_ALLOW_READ the below statement should succeed +--echo # without waiting. The old version of table should be used in it. +handler t1 read a next; +handler t1 close; +connection con1; drop table t1; disconnect con1; --source include/wait_until_disconnected.inc +connection con2; +disconnect con2; +--source include/wait_until_disconnected.inc connection default; # @@ -729,3 +789,714 @@ USE information_schema; --error ER_WRONG_USAGE HANDLER COLUMNS OPEN; USE test; + +--echo # +--echo # Add test coverage for HANDLER and LOCK TABLES, HANDLER and DDL. +--echo # +--disable_warnings +drop table if exists t1, t2, t3; +--enable_warnings +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 (a int, key a (a)) select * from t1; +create temporary table t3 (a int, key a (a)) select * from t2; +handler t1 open; +handler t2 open; +handler t3 open; +--echo # +--echo # LOCK TABLES implicitly closes all handlers. +--echo # +lock table t3 read; +--echo # +--echo # No HANDLER sql is available under lock tables anyway. +--echo # +--error ER_LOCK_OR_ACTIVE_TRANSACTION +handler t1 open; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +handler t1 read next; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +handler t2 close; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +handler t3 open; +--echo # After UNLOCK TABLES no handlers are around, they were +--echo # implicitly closed. +unlock tables; +drop temporary table t3; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--error ER_UNKNOWN_TABLE +handler t2 close; +--error ER_UNKNOWN_TABLE +handler t3 read next; +--echo # +--echo # Other operations also implicitly close handler: +--echo # +--echo # TRUNCATE +--echo # +handler t1 open; +truncate table t1; +--error ER_UNKNOWN_TABLE +handler t1 read next; +handler t1 open; +--echo # +--echo # CREATE TRIGGER +--echo # +create trigger t1_ai after insert on t1 for each row set @a=1; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--echo # +--echo # DROP TRIGGER +--echo # +handler t1 open; +drop trigger t1_ai; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--echo # +--echo # ALTER TABLE +--echo # +handler t1 open; +alter table t1 add column b int; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--echo # +--echo # ANALYZE TABLE +--echo # +handler t1 open; +analyze table t1; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--echo # +--echo # OPTIMIZE TABLE +--echo # +handler t1 open; +optimize table t1; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--echo # +--echo # REPAIR TABLE +--echo # +handler t1 open; +repair table t1; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--echo # +--echo # DROP TABLE, naturally. +--echo # +handler t1 open; +drop table t1; +--error ER_UNKNOWN_TABLE +handler t1 read next; +create table t1 (a int, b int, key a (a)) select a from t2; +--echo # +--echo # RENAME TABLE, naturally +--echo # +handler t1 open; +rename table t1 to t3; +--error ER_UNKNOWN_TABLE +handler t1 read next; +--echo # +--echo # CREATE TABLE (even with IF NOT EXISTS clause, +--echo # and the table exists). +--echo # +handler t2 open; +create table if not exists t2 (a int); +--error ER_UNKNOWN_TABLE +handler t2 read next; +rename table t3 to t1; +drop table t2; +--echo # +--echo # FLUSH TABLE doesn't close the table but loses the position +--echo # +handler t1 open; +handler t1 read a prev; +flush table t1; +handler t1 read a prev; +handler t1 close; +--echo # +--echo # FLUSH TABLES WITH READ LOCK behaves like FLUSH TABLE. +--echo # +handler t1 open; +handler t1 read a prev; +flush tables with read lock; +handler t1 read a prev; +handler t1 close; +unlock tables; +--echo # +--echo # Explore the effect of HANDLER locks on concurrent DDL +--echo # +handler t1 open; +--echo # Establishing auxiliary connections con1, con2, con3 +connect(con1, localhost, root,,); +connect(con2, localhost, root,,); +connect(con3, localhost, root,,); +--echo # --> connection con1; +connection con1; +--echo # Sending: +--send drop table t1 +--echo # We can't use connection 'default' as wait_condition will +--echo # autoclose handlers. +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop table t1' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t1'; +--source include/wait_condition.inc +--echo # --> connection default +connection default; +handler t1 read a prev; +handler t1 read a prev; +handler t1 close; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop table t1'... +--reap +--echo # --> connection default +connection default; +--echo # +--echo # Explore the effect of HANDLER locks in parallel with SELECT +--echo # +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +begin; +select * from t1; +handler t1 open; +handler t1 read a prev; +handler t1 read a prev; +handler t1 close; +--echo # --> connection con1; +connection con1; +--echo # Sending: +--send drop table t1 +--echo # --> connection con2 +connection con2; +--echo # Waiting for 'drop table t1' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t1'; +--source include/wait_condition.inc +--echo # --> connection default +connection default; +--echo # We can still use the table, it's part of the transaction +select * from t1; +--echo # Such are the circumstances that t1 is a part of transaction, +--echo # thus we can reopen it in the handler +handler t1 open; +--echo # We can commit the transaction, it doesn't close the handler +--echo # and doesn't let DROP to proceed. +commit; +handler t1 read a prev; +handler t1 read a prev; +handler t1 read a prev; +handler t1 close; +--echo # --> connection con1 +connection con1; +--echo # Now drop can proceed +--echo # Reaping 'drop table t1'... +--reap +--echo # --> connection default +connection default; +--echo # +--echo # Demonstrate that HANDLER locks and transaction locks +--echo # reside in the same context, and we don't back-off +--echo # when have transaction or handler locks. +--echo # +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t0 (a int, key a (a)); +insert into t0 (a) values (1), (2), (3), (4), (5); +begin; +select * from t1; +--echo # --> connection con2 +connection con2; +--echo # Sending: +send rename table t0 to t3, t1 to t0, t3 to t1; +--echo # --> connection con1 +connection con1; +--echo # Waiting for 'rename table ...' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='rename table t0 to t3, t1 to t0, t3 to t1'; +--source include/wait_condition.inc +--echo # --> connection default +connection default; +--error ER_LOCK_DEADLOCK +handler t0 open; +--error ER_LOCK_DEADLOCK +select * from t0; +handler t1 open; +commit; +handler t1 close; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'rename table ...'... +--reap +--echo # --> connection default +connection default; +handler t1 open; +handler t1 read a prev; +handler t1 close; +drop table t0; +--echo # +--echo # Originally there was a deadlock error in this test. +--echo # With implementation of deadlock detector +--echo # we no longer deadlock, but block and wait on a lock. +--echo # The HANDLER is auto-closed as soon as the connection +--echo # sees a pending conflicting lock against it. +--echo # +create table t2 (a int, key a (a)); +handler t1 open; +--echo # --> connection con1 +connection con1; +lock tables t2 read; +--echo # --> connection con2 +connection con2; +--echo # Sending 'drop table t2'... +--send drop table t2 +--echo # --> connection con1 +connection con1; +--echo # Waiting for 'drop table t2' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t2'; +--source include/wait_condition.inc +--echo # --> connection default +connection default; +--echo # Sending 'select * from t2' +send select * from t2; +--echo # --> connection con1 +connection con1; +--echo # Waiting for 'select * from t2' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='select * from t2'; +unlock tables; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'drop table t2'... +--reap +--echo # --> connection default +connection default; +--echo # Reaping 'select * from t2' +--error ER_NO_SUCH_TABLE +reap; +handler t1 close; + +--echo # +--echo # ROLLBACK TO SAVEPOINT releases transactional locks, +--echo # but has no effect on open HANDLERs +--echo # +create table t2 like t1; +create table t3 like t1; +begin; +--echo # Have something before the savepoint +select * from t3; +savepoint sv; +handler t1 open; +handler t1 read a first; +handler t1 read a next; +select * from t2; +--echo # --> connection con1 +connection con1; +--echo # Sending: +--send drop table t1 +--echo # --> connection con2 +connection con2; +--echo # Sending: +--send drop table t2 +--echo # --> connection default +connection default; +--echo # Let DROP TABLE statements sync in. We must use +--echo # a separate connection for that, because otherwise SELECT +--echo # will auto-close the HANDLERs, becaues there are pending +--echo # exclusive locks against them. +--echo # --> connection con3 +connection con3; +--echo # Waiting for 'drop table t1' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t1'; +--source include/wait_condition.inc +--echo # Waiting for 'drop table t2' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t2'; +--source include/wait_condition.inc +--echo # Demonstrate that t2 lock was released and t2 was dropped +--echo # after ROLLBACK TO SAVEPOINT +--echo # --> connection default +connection default; +rollback to savepoint sv; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'drop table t2'... +--reap +--echo # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +--echo # lock. +--echo # --> connection default +connection default; +handler t1 read a next; +handler t1 read a next; +--echo # Demonstrate that the drop will go through as soon as we close the +--echo # HANDLER +handler t1 close; +--echo # connection con1 +connection con1; +--echo # Reaping 'drop table t1'... +--reap +--echo # --> connection default +connection default; +commit; +drop table t3; +--echo # +--echo # A few special cases when using SAVEPOINT/ROLLBACK TO +--echo # SAVEPOINT and HANDLER. +--echo # +--echo # Show that rollback to the savepoint taken in the beginning +--echo # of the transaction doesn't release mdl lock on +--echo # the HANDLER that was opened later. +--echo # +create table t1 (a int, key a(a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 like t1; +begin; +savepoint sv; +handler t1 open; +handler t1 read a first; +handler t1 read a next; +select * from t2; +--echo # --> connection con1 +connection con1; +--echo # Sending: +--send drop table t1 +--echo # --> connection con2 +connection con2; +--echo # Sending: +--send drop table t2 +--echo # --> connection default +connection default; +--echo # Let DROP TABLE statements sync in. We must use +--echo # a separate connection for that, because otherwise SELECT +--echo # will auto-close the HANDLERs, becaues there are pending +--echo # exclusive locks against them. +--echo # --> connection con3 +connection con3; +--echo # Waiting for 'drop table t1' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t1'; +--source include/wait_condition.inc +--echo # Waiting for 'drop table t2' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t2'; +--source include/wait_condition.inc +--echo # Demonstrate that t2 lock was released and t2 was dropped +--echo # after ROLLBACK TO SAVEPOINT +--echo # --> connection default +connection default; +rollback to savepoint sv; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'drop table t2'... +--reap +--echo # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +--echo # lock. +--echo # --> connection default +connection default; +handler t1 read a next; +handler t1 read a next; +--echo # Demonstrate that the drop will go through as soon as we close the +--echo # HANDLER +handler t1 close; +--echo # connection con1 +connection con1; +--echo # Reaping 'drop table t1'... +--reap +--echo # --> connection default +connection default; +commit; +--echo # +--echo # Show that rollback to the savepoint taken in the beginning +--echo # of the transaction works properly (no valgrind warnins, etc), +--echo # even though it's done after the HANDLER mdl lock that was there +--echo # at the beginning is released and added again. +--echo # +create table t1 (a int, key a(a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 like t1; +create table t3 like t1; +insert into t3 (a) select a from t1; +begin; +handler t1 open; +savepoint sv; +handler t1 read a first; +select * from t2; +handler t1 close; +handler t3 open; +handler t3 read a first; +rollback to savepoint sv; +--echo # --> connection con1 +connection con1; +drop table t1, t2; +--echo # Sending: +--send drop table t3 +--echo # Let DROP TABLE statement sync in. +--echo # --> connection con2 +connection con2; +--echo # Waiting for 'drop table t3' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t3'; +--source include/wait_condition.inc +--echo # Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +--echo # lock. +--echo # --> connection default +connection default; +handler t3 read a next; +--echo # Demonstrate that the drop will go through as soon as we close the +--echo # HANDLER +handler t3 close; +--echo # connection con1 +connection con1; +--echo # Reaping 'drop table t3'... +--reap +--echo # --> connection default +connection default; +commit; + +--echo # +--echo # If we have to wait on an exclusive locks while having +--echo # an open HANDLER, ER_LOCK_DEADLOCK is reported. +--echo # +create table t1 (a int, key a(a)); +create table t2 like t1; +handler t1 open; +--echo # --> connection con1 +connection con1; +lock table t1 write, t2 write; +--echo # --> connection default +connection default; +send drop table t2; +--echo # --> connection con2 +connection con2; +--echo # Waiting for 'drop table t2' to get blocked... +let $wait_condition=select count(*)=1 from information_schema.processlist where state='Waiting for table' and info='drop table t2'; +--source include/wait_condition.inc +--echo # --> connection con1 +connection con1; +--error ER_LOCK_DEADLOCK +drop table t1; +unlock tables; +--echo # --> connection default +connection default; +reap; + +--echo # Demonstrate that there is no deadlock with FLUSH TABLE, +--echo # even though it is waiting for the other table to go away +create table t2 like t1; +--echo # Sending: +--send flush table t2 +--echo # --> connection con2 +connection con2; +drop table t1; +--echo # --> connection con1 +connection con1; +unlock tables; +--echo # --> connection default +connection default; +--echo # Reaping 'flush table t2'... +--reap +drop table t2; + +--echo # +--echo # Bug #46224 HANDLER statements within a transaction might +--echo # lead to deadlocks +--echo # +create table t1 (a int, key a(a)); +insert into t1 values (1), (2); + +--echo # --> connection default +connection default; +begin; +select * from t1; +handler t1 open; + +--echo # --> connection con1 +connection con1; +--echo # Sending: +--send lock tables t1 write + +--echo # --> connection con2 +connection con2; +--echo # Check that 'lock tables t1 write' waits until transaction which +--echo # has read from the table commits. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "lock tables t1 write"; +--source include/wait_condition.inc + +--echo # --> connection default +connection default; +--echo # The below 'handler t1 read ...' should not be blocked as +--echo # 'lock tables t1 write' has not succeeded yet. +handler t1 read a next; + +--echo # Unblock 'lock tables t1 write'. +commit; + +--echo # --> connection con1 +connection con1; +--echo # Reap 'lock tables t1 write'. +--reap + +--echo # --> connection default +connection default; +--echo # Sending: +--send handler t1 read a next + +--echo # --> connection con1 +connection con1; +--echo # Waiting for 'handler t1 read a next' to get blocked... +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Table lock" and info = "handler t1 read a next"; +--source include/wait_condition.inc + +--echo # The below 'drop table t1' should be able to proceed without +--echo # waiting as it will force HANDLER to be closed. +drop table t1; +unlock tables; + +--echo # --> connection default +connection default; +--echo # Reaping 'handler t1 read a next'... +--error ER_NO_SUCH_TABLE +--reap +handler t1 close; + +--echo # --> connection con1 +connection con1; +disconnect con1; +--source include/wait_until_disconnected.inc +--echo # --> connection con2 +connection con2; +disconnect con2; +--source include/wait_until_disconnected.inc +--echo # --> connection con3 +connection con3; +disconnect con3; +--source include/wait_until_disconnected.inc +connection default; + +--echo # +--echo # A temporary table test. +--echo # Check that we don't loose positions of HANDLER opened +--echo # against a temporary table. +--echo # +create table t1 (a int, b int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create temporary table t2 (a int, b int, key a (a)); +insert into t2 (a) select a from t1; +handler t1 open; +handler t1 read a next; +handler t2 open; +handler t2 read a next; +flush table t1; +handler t2 read a next; +--echo # Sic: the position is lost +handler t1 read a next; +select * from t1; +--echo # Sic: the position is not lost +handler t2 read a next; +--error ER_CANT_REOPEN_TABLE +select * from t2; +handler t2 read a next; +drop table t1; +drop temporary table t2; + +--echo # +--echo # A test for lock_table_names()/unlock_table_names() function. +--echo # It should work properly in presence of open HANDLER. +--echo # +create table t1 (a int, b int, key a (a)); +create table t2 like t1; +create table t3 like t1; +create table t4 like t1; +handler t1 open; +handler t2 open; +rename table t4 to t5, t3 to t4, t5 to t3; +handler t1 read first; +handler t2 read first; +drop table t1, t2, t3, t4; + +--echo # +--echo # A test for FLUSH TABLES WITH READ LOCK and HANDLER statements. +--echo # +set autocommit=0; +create table t1 (a int, b int, key a (a)); +insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5); +create table t2 like t1; +insert into t2 (a, b) select a, b from t1; +create table t3 like t1; +insert into t3 (a, b) select a, b from t1; +commit; +flush tables with read lock; +handler t1 open; +lock table t1 read; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +handler t1 read next; +--echo # This implicitly leaves LOCK TABLES but doesn't drop the GLR +--error ER_NO_SUCH_TABLE +lock table not_exists_write read; +--echo # We still have the read lock. +--error ER_CANT_UPDATE_WITH_READLOCK +drop table t1; +handler t1 open; +select a from t2; +handler t1 read next; +flush tables with read lock; +handler t2 open; +flush tables with read lock; +handler t1 read next; +select a from t3; +handler t2 read next; +handler t1 close; +rollback; +handler t2 close; +--error ER_CANT_UPDATE_WITH_READLOCK +drop table t1; +commit; +flush tables; +--error ER_CANT_UPDATE_WITH_READLOCK +drop table t1; +unlock tables; +drop table t1; +set autocommit=default; +drop table t2, t3; + +--echo # +--echo # HANDLER statement and operation-type aware metadata locks. +--echo # Check that when we clone a ticket for HANDLER we downrade +--echo # the lock. +--echo # +--echo # Establish an auxiliary connection con1. +connect (con1,localhost,root,,); +--echo # -> connection default +connection default; +create table t1 (a int, b int, key a (a)); +insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5); +begin; +insert into t1 (a, b) values (6, 6); +handler t1 open; +handler t1 read a last; +insert into t1 (a, b) values (7, 7); +handler t1 read a last; +commit; +--echo # -> connection con1 +connection con1; +--echo # Demonstrate that the HANDLER doesn't hold MDL_SHARED_WRITE. +lock table t1 write; +unlock tables; +--echo # -> connection default +connection default; +handler t1 read a prev; +handler t1 close; +--echo # Cleanup. +drop table t1; +--echo # -> connection con1 +connection con1; +disconnect con1; +--source include/wait_until_disconnected.inc +--echo # -> connection default +connection default; + +--echo # +--echo # A test for Bug#50555 "handler commands crash server in +--echo # my_hash_first()". +--echo # +--error ER_UNKNOWN_TABLE +handler no_such_table read no_such_index first; +--error ER_UNKNOWN_TABLE +handler no_such_table close; diff --git a/mysql-test/include/implicit_commit_helper.inc b/mysql-test/include/implicit_commit_helper.inc new file mode 100644 index 00000000000..5e87b2db079 --- /dev/null +++ b/mysql-test/include/implicit_commit_helper.inc @@ -0,0 +1,5 @@ +INSERT INTO db1.trans (a) VALUES (1); +--disable_result_log +eval $statement; +--enable_result_log +CALL db1.test_if_commit(); diff --git a/mysql-test/include/mix1.inc b/mysql-test/include/mix1.inc index 194d9e41108..66648aaf1bf 100644 --- a/mysql-test/include/mix1.inc +++ b/mysql-test/include/mix1.inc @@ -899,6 +899,8 @@ CREATE PROCEDURE p1 () BEGIN DECLARE i INT DEFAULT 50; DECLARE cnt INT; + # Continue even in the presence of ER_LOCK_DEADLOCK. + DECLARE CONTINUE HANDLER FOR 1213 BEGIN END; START TRANSACTION; ALTER TABLE t1 ENGINE=InnoDB; COMMIT; @@ -1392,6 +1394,7 @@ SELECT * FROM t1; connection con2; --reap SELECT * FROM t1; +COMMIT; --echo # Switch to connection con1 connection con1; diff --git a/mysql-test/include/mix2.inc b/mysql-test/include/mix2.inc index b4c4a9b8836..001d4cf44d4 100644 --- a/mysql-test/include/mix2.inc +++ b/mysql-test/include/mix2.inc @@ -1994,6 +1994,7 @@ commit; connection b; set autocommit = 0; update t1 set b = 5 where a = 2; +commit; connection a; delimiter |; create trigger t1t before insert on t1 for each row begin set NEW.b = NEW.a * 10 + 5, NEW.c = NEW.a / 10; end | @@ -2056,6 +2057,7 @@ update t2 set b = b + 5 where a = 1; update t3 set b = b + 5 where a = 1; update t4 set b = b + 5 where a = 1; insert into t5(a) values(20); +commit; connection b; set autocommit = 0; insert into t1(a) values(7); diff --git a/mysql-test/include/mtr_warnings.sql b/mysql-test/include/mtr_warnings.sql index 00e8c4e6c95..e62dba8ae82 100644 --- a/mysql-test/include/mtr_warnings.sql +++ b/mysql-test/include/mtr_warnings.sql @@ -194,6 +194,14 @@ INSERT INTO global_suppressions VALUES ("Slave I/O: Get master COLLATION_SERVER failed with error:.*"), ("Slave I/O: Get master TIME_ZONE failed with error:.*"), + /* + BUG#42147 - Concurrent DML and LOCK TABLE ... READ for InnoDB + table cause warnings in errlog + Note: This is a temporary suppression until Bug#42147 can be + fixed properly. See bug page for more information. + */ + ("Found lock of type 6 that is write and read locked"), + ("THE_LAST_SUPPRESSION")|| diff --git a/mysql-test/lib/v1/mtr_report.pl b/mysql-test/lib/v1/mtr_report.pl index 3c78c3ca064..36aba983c34 100644 --- a/mysql-test/lib/v1/mtr_report.pl +++ b/mysql-test/lib/v1/mtr_report.pl @@ -376,6 +376,9 @@ sub mtr_report_stats ($) { /Slave: Can't DROP 'c7'.* 1091/ or /Slave: Key column 'c6'.* 1072/ or + # Warnings generated until bug#42147 is properly resolved + /Found lock of type 6 that is write and read locked/ or + # rpl_idempotency.test produces warnings for the slave. ($testname eq 'rpl.rpl_idempotency' and (/Slave: Can\'t find record in \'t1\' Error_code: 1032/ or diff --git a/mysql-test/r/commit_1innodb.result b/mysql-test/r/commit_1innodb.result index 51c4ac3002c..bbff677ab3f 100644 --- a/mysql-test/r/commit_1innodb.result +++ b/mysql-test/r/commit_1innodb.result @@ -841,11 +841,11 @@ call p_verify_status_increment(2, 0, 2, 0); SUCCESS alter table t3 rename t4; -call p_verify_status_increment(2, 2, 2, 2); +call p_verify_status_increment(4, 4, 4, 4); SUCCESS rename table t4 to t3; -call p_verify_status_increment(2, 2, 2, 2); +call p_verify_status_increment(0, 0, 0, 0); SUCCESS truncate table t3; @@ -853,13 +853,13 @@ call p_verify_status_increment(4, 4, 4, 4); SUCCESS create view v1 as select * from t2; -call p_verify_status_increment(1, 0, 1, 0); +call p_verify_status_increment(2, 0, 2, 0); SUCCESS check table t1; Table Op Msg_type Msg_text test.t1 check status OK -call p_verify_status_increment(3, 0, 3, 0); +call p_verify_status_increment(2, 0, 2, 0); SUCCESS # Sic: after this bug is fixed, CHECK leaves no pending transaction diff --git a/mysql-test/r/create.result b/mysql-test/r/create.result index 019aeba27b6..09d14188d0e 100644 --- a/mysql-test/r/create.result +++ b/mysql-test/r/create.result @@ -785,7 +785,7 @@ drop table t1; create table t1 select * from t2; ERROR 42S02: Table 'test.t2' doesn't exist create table t1 select * from t1; -ERROR HY000: You can't specify target table 't1' for update in FROM clause +ERROR 42S02: Table 'test.t1' doesn't exist create table t1 select coalesce('a' collate latin1_swedish_ci,'b' collate latin1_bin); ERROR HY000: Illegal mix of collations (latin1_swedish_ci,EXPLICIT) and (latin1_bin,EXPLICIT) for operation 'coalesce' create table t1 (primary key(a)) select "b" as b; @@ -805,6 +805,11 @@ Note 1050 Table 't1' already exists select * from t1; i 1 +create table if not exists t1 select * from t1; +ERROR HY000: You can't specify target table 't1' for update in FROM clause +select * from t1; +i +1 create table t1 select coalesce('a' collate latin1_swedish_ci,'b' collate latin1_bin); ERROR HY000: Illegal mix of collations (latin1_swedish_ci,EXPLICIT) and (latin1_bin,EXPLICIT) for operation 'coalesce' select * from t1; @@ -1956,3 +1961,22 @@ END ;| ERROR 42000: This version of MySQL doesn't yet support 'multiple triggers with the same action time and event for one table' DROP TABLE t1; DROP TABLE B; +# +# Bug #47107 assert in notify_shared_lock on incorrect +# CREATE TABLE , HANDLER +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1(f1 integer); +# The following CREATE TABLEs before gave an assert. +HANDLER t1 OPEN AS A; +CREATE TABLE t1 SELECT 1 AS f2; +ERROR 42S01: Table 't1' already exists +HANDLER t1 OPEN AS A; +CREATE TABLE t1(f1 integer); +ERROR 42S01: Table 't1' already exists +CREATE TABLE t2(f1 integer); +HANDLER t1 OPEN AS A; +CREATE TABLE t1 LIKE t2; +ERROR 42S01: Table 't1' already exists +DROP TABLE t2; +DROP TABLE t1; diff --git a/mysql-test/r/debug_sync.result b/mysql-test/r/debug_sync.result index 8b46334204c..25fdf523200 100644 --- a/mysql-test/r/debug_sync.result +++ b/mysql-test/r/debug_sync.result @@ -263,7 +263,7 @@ DROP TABLE t1; SET DEBUG_SYNC= 'RESET'; DROP TABLE IF EXISTS t1; CREATE TABLE t1 (c1 INT); -LOCK TABLE t1 WRITE; +LOCK TABLE t1 READ; connection con1 SET DEBUG_SYNC= 'wait_for_lock SIGNAL locked EXECUTE 2'; INSERT INTO t1 VALUES (1); diff --git a/mysql-test/r/drop.result b/mysql-test/r/drop.result index 54bd05e526f..8c6bd582232 100644 --- a/mysql-test/r/drop.result +++ b/mysql-test/r/drop.result @@ -77,8 +77,8 @@ drop table t1; drop database if exists mysqltest; drop table if exists t1; create table t1 (i int); -lock tables t1 read; create database mysqltest; +lock tables t1 read; drop table t1; show open tables; drop database mysqltest; diff --git a/mysql-test/r/drop_debug.result b/mysql-test/r/drop_debug.result index 75346b88bc6..f2c89034451 100644 --- a/mysql-test/r/drop_debug.result +++ b/mysql-test/r/drop_debug.result @@ -7,12 +7,16 @@ DROP DATABASE IF EXISTS mysql_test; CREATE DATABASE mysql_test; CREATE TABLE mysql_test.t1(a INT); +CREATE TABLE mysql_test.t2(b INT); +CREATE TABLE mysql_test.t3(c INT); SET SESSION DEBUG = "+d,bug43138"; DROP DATABASE mysql_test; Warnings: Error 1051 Unknown table 't1' +Error 1051 Unknown table 't2' +Error 1051 Unknown table 't3' SET SESSION DEBUG = "-d,bug43138"; diff --git a/mysql-test/r/flush.result b/mysql-test/r/flush.result index b978304f59d..2be426d3a4a 100644 --- a/mysql-test/r/flush.result +++ b/mysql-test/r/flush.result @@ -33,6 +33,9 @@ flush tables with read lock; ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction lock table t1 read; flush tables with read lock; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +unlock tables; +flush tables with read lock; lock table t1 write; ERROR HY000: Can't execute the query because you have a conflicting read lock lock table t1 read; @@ -46,6 +49,7 @@ flush tables with read lock; ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction lock table t1 read, t2 read, t3 read; flush tables with read lock; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction unlock tables; drop table t1, t2, t3; create table t1 (c1 int); @@ -69,6 +73,7 @@ ERROR HY000: Can't execute the given command because you have active locked tabl unlock tables; lock tables t1 read; flush tables with read lock; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction unlock tables; drop table t1, t2; set session low_priority_updates=default; diff --git a/mysql-test/r/flush_block_commit.result b/mysql-test/r/flush_block_commit.result index d2197beaaab..7062d05c2d7 100644 --- a/mysql-test/r/flush_block_commit.result +++ b/mysql-test/r/flush_block_commit.result @@ -1,3 +1,4 @@ +# Save the initial number of concurrent sessions # Establish connection con1 (user=root) # Establish connection con2 (user=root) # Establish connection con3 (user=root) @@ -8,15 +9,17 @@ BEGIN; INSERT INTO t1 VALUES(1); # Switch to connection con2 FLUSH TABLES WITH READ LOCK; -SELECT * FROM t1; -a # Switch to connection con1 +# Sending: COMMIT; # Switch to connection con2 +# Wait until COMMIT gets blocked. +# Verify that 'con1' was blocked and data did not move. SELECT * FROM t1; a UNLOCK TABLES; # Switch to connection con1 +# Reaping COMMIT # Switch to connection con1 BEGIN; SELECT * FROM t1 FOR UPDATE; @@ -32,6 +35,7 @@ COMMIT; # Switch to connection con2 a 1 +COMMIT; # Switch to connection con3 UNLOCK TABLES; # Switch to connection con2 @@ -40,8 +44,6 @@ COMMIT; BEGIN; INSERT INTO t1 VALUES(10); FLUSH TABLES WITH READ LOCK; -COMMIT; -UNLOCK TABLES; # Switch to connection con2 FLUSH TABLES WITH READ LOCK; UNLOCK TABLES; @@ -53,5 +55,11 @@ a SHOW CREATE DATABASE test; Database Create Database test CREATE DATABASE `test` /*!40100 DEFAULT CHARACTER SET latin1 */ -DROP TABLE t1; +COMMIT; +# Cleanup # Switch to connection default and close connections con1, con2, con3 +# We commit open transactions when we disconnect: only then we can +# drop the table. +DROP TABLE t1; +# End of 4.1 tests +# Wait till all disconnects are completed diff --git a/mysql-test/r/flush_block_commit_notembedded.result b/mysql-test/r/flush_block_commit_notembedded.result index 4348dbd67e5..6d8af3f5864 100644 --- a/mysql-test/r/flush_block_commit_notembedded.result +++ b/mysql-test/r/flush_block_commit_notembedded.result @@ -1,17 +1,20 @@ +# Save the initial number of concurrent sessions # Establish connection con1 (user=root) # Establish connection con2 (user=root) # Switch to connection con1 CREATE TABLE t1 (a INT) ENGINE=innodb; RESET MASTER; SET AUTOCOMMIT=0; -INSERT t1 VALUES (1); +SELECT 1; +1 +1 # Switch to connection con2 FLUSH TABLES WITH READ LOCK; SHOW MASTER STATUS; File Position Binlog_Do_DB Binlog_Ignore_DB master-bin.000001 107 # Switch to connection con1 -COMMIT; +INSERT INTO t1 VALUES (1); # Switch to connection con2 SHOW MASTER STATUS; File Position Binlog_Do_DB Binlog_Ignore_DB @@ -20,4 +23,12 @@ UNLOCK TABLES; # Switch to connection con1 DROP TABLE t1; SET AUTOCOMMIT=1; +create table t1 (a int) engine=innodb; +flush tables with read lock; +begin; +insert into t1 values (1);; +unlock tables; +commit; +drop table t1; # Switch to connection default and close connections con1 and con2 +# Wait till all disconnects are completed diff --git a/mysql-test/r/flush_table.result b/mysql-test/r/flush_table.result index 8821bade6b4..2b0ee1cb205 100644 --- a/mysql-test/r/flush_table.result +++ b/mysql-test/r/flush_table.result @@ -3,21 +3,14 @@ create table t1 (a int not null auto_increment primary key); insert into t1 values(0); lock table t1 read; flush table t1; +ERROR HY000: Table 't1' was locked with a READ lock and can't be updated +unlock tables; +lock table t1 write; +flush table t1; check table t1; Table Op Msg_type Msg_text test.t1 check status OK unlock tables; -lock table t1 read; -lock table t1 read; -flush table t1; -select * from t1; -a -1 -unlock tables; -select * from t1; -a -1 -unlock tables; lock table t1 write; lock table t1 read; flush table t1; @@ -26,7 +19,7 @@ a 1 unlock tables; unlock tables; -lock table t1 read; +lock table t1 write; lock table t1 write; flush table t1; select * from t1; diff --git a/mysql-test/r/handler_innodb.result b/mysql-test/r/handler_innodb.result index 957fc30acef..4def5c82df8 100644 --- a/mysql-test/r/handler_innodb.result +++ b/mysql-test/r/handler_innodb.result @@ -548,6 +548,7 @@ c1 1 connection: flush flush tables;; +connection: waiter connection: default handler t2 open; handler t2 read first; @@ -559,23 +560,36 @@ c1 handler t1 close; handler t2 close; drop table t1,t2; -drop table if exists t1,t2; +drop table if exists t1, t0; create table t1 (c1 int); connection: default handler t1 open; handler t1 read first; c1 connection: flush -rename table t1 to t2;; +rename table t1 to t0;; +connection: waiter connection: default -handler t2 open; -handler t2 read first; -c1 +# +# RENAME placed two pending locks and waits. +# When HANDLER t0 OPEN does open_tables(), it calls +# mysql_ha_flush(), which in turn closes the open HANDLER for t1. +# RENAME TABLE gets unblocked. If it gets scheduled quickly +# and manages to complete before open_tables() +# of HANDLER t0 OPEN, open_tables() and therefore the whole +# HANDLER t0 OPEN succeeds. Otherwise open_tables() +# notices a pending or active exclusive metadata lock on t2 +# and the whole HANDLER t0 OPEN fails with ER_LOCK_DEADLOCK +# error. +# +handler t0 open; +handler t0 close; +connection: flush handler t1 read next; -ERROR 42S02: Table 'test.t1' doesn't exist +ERROR 42S02: Unknown table 't1' in HANDLER handler t1 close; -handler t2 close; -drop table t2; +ERROR 42S02: Unknown table 't1' in HANDLER +drop table t0; drop table if exists t1; create temporary table t1 (a int, b char(1), key a(a), key b(a,b)); insert into t1 values (0,"a"),(1,"b"),(2,"c"),(3,"d"),(4,"e"), @@ -731,15 +745,722 @@ drop table t1; handler t1 read a next; ERROR 42S02: Unknown table 't1' in HANDLER drop table if exists t1; +# First test case which is supposed trigger the execution +# path on which problem was discovered. create table t1 (a int); insert into t1 values (1); handler t1 open; +lock table t1 write; alter table t1 engine=memory; handler t1 read a next; ERROR HY000: Table storage engine for 't1' doesn't have this option handler t1 close; +unlock tables; +drop table t1; +# Now test case which was reported originally but which no longer +# triggers execution path which has caused the problem. +create table t1 (a int, key(a)); +insert into t1 values (1); +handler t1 open; +alter table t1 engine=memory; +# Since S metadata lock was already acquired at HANDLER OPEN time +# and TL_READ lock requested by HANDLER READ is compatible with +# ALTER's TL_WRITE_ALLOW_READ the below statement should succeed +# without waiting. The old version of table should be used in it. +handler t1 read a next; +a +1 +handler t1 close; drop table t1; USE information_schema; HANDLER COLUMNS OPEN; ERROR HY000: Incorrect usage of HANDLER OPEN and information_schema USE test; +# +# Add test coverage for HANDLER and LOCK TABLES, HANDLER and DDL. +# +drop table if exists t1, t2, t3; +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 (a int, key a (a)) select * from t1; +create temporary table t3 (a int, key a (a)) select * from t2; +handler t1 open; +handler t2 open; +handler t3 open; +# +# LOCK TABLES implicitly closes all handlers. +# +lock table t3 read; +# +# No HANDLER sql is available under lock tables anyway. +# +handler t1 open; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +handler t1 read next; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +handler t2 close; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +handler t3 open; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +# After UNLOCK TABLES no handlers are around, they were +# implicitly closed. +unlock tables; +drop temporary table t3; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +handler t2 close; +ERROR 42S02: Unknown table 't2' in HANDLER +handler t3 read next; +ERROR 42S02: Unknown table 't3' in HANDLER +# +# Other operations also implicitly close handler: +# +# TRUNCATE +# +handler t1 open; +truncate table t1; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +handler t1 open; +# +# CREATE TRIGGER +# +create trigger t1_ai after insert on t1 for each row set @a=1; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# DROP TRIGGER +# +handler t1 open; +drop trigger t1_ai; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# ALTER TABLE +# +handler t1 open; +alter table t1 add column b int; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# ANALYZE TABLE +# +handler t1 open; +analyze table t1; +Table Op Msg_type Msg_text +test.t1 analyze status OK +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# OPTIMIZE TABLE +# +handler t1 open; +optimize table t1; +Table Op Msg_type Msg_text +test.t1 optimize note Table does not support optimize, doing recreate + analyze instead +test.t1 optimize status OK +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# REPAIR TABLE +# +handler t1 open; +repair table t1; +Table Op Msg_type Msg_text +test.t1 repair note The storage engine for the table doesn't support repair +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# DROP TABLE, naturally. +# +handler t1 open; +drop table t1; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +create table t1 (a int, b int, key a (a)) select a from t2; +# +# RENAME TABLE, naturally +# +handler t1 open; +rename table t1 to t3; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# CREATE TABLE (even with IF NOT EXISTS clause, +# and the table exists). +# +handler t2 open; +create table if not exists t2 (a int); +Warnings: +Note 1050 Table 't2' already exists +handler t2 read next; +ERROR 42S02: Unknown table 't2' in HANDLER +rename table t3 to t1; +drop table t2; +# +# FLUSH TABLE doesn't close the table but loses the position +# +handler t1 open; +handler t1 read a prev; +b a +NULL 5 +flush table t1; +handler t1 read a prev; +b a +NULL 5 +handler t1 close; +# +# FLUSH TABLES WITH READ LOCK behaves like FLUSH TABLE. +# +handler t1 open; +handler t1 read a prev; +b a +NULL 5 +flush tables with read lock; +handler t1 read a prev; +b a +NULL 5 +handler t1 close; +unlock tables; +# +# Explore the effect of HANDLER locks on concurrent DDL +# +handler t1 open; +# Establishing auxiliary connections con1, con2, con3 +# --> connection con1; +# Sending: +drop table t1 ; +# We can't use connection 'default' as wait_condition will +# autoclose handlers. +# --> connection con2 +# Waitng for 'drop table t1' to get blocked... +# --> connection default +handler t1 read a prev; +b a +NULL 5 +handler t1 read a prev; +b a +NULL 4 +handler t1 close; +# --> connection con1 +# Reaping 'drop table t1'... +# --> connection default +# +# Explore the effect of HANDLER locks in parallel with SELECT +# +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +begin; +select * from t1; +a +1 +2 +3 +4 +5 +handler t1 open; +handler t1 read a prev; +a +5 +handler t1 read a prev; +a +4 +handler t1 close; +# --> connection con1; +# Sending: +drop table t1 ; +# --> connection con2 +# Waiting for 'drop table t1' to get blocked... +# --> connection default +# We can still use the table, it's part of the transaction +select * from t1; +a +1 +2 +3 +4 +5 +# Such are the circumstances that t1 is a part of transaction, +# thus we can reopen it in the handler +handler t1 open; +# We can commit the transaction, it doesn't close the handler +# and doesn't let DROP to proceed. +commit; +handler t1 read a prev; +a +5 +handler t1 read a prev; +a +4 +handler t1 read a prev; +a +3 +handler t1 close; +# --> connection con1 +# Now drop can proceed +# Reaping 'drop table t1'... +# --> connection default +# +# Demonstrate that HANDLER locks and transaction locks +# reside in the same context, and we don't back-off +# when have transaction or handler locks. +# +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t0 (a int, key a (a)); +insert into t0 (a) values (1), (2), (3), (4), (5); +begin; +select * from t1; +a +1 +2 +3 +4 +5 +# --> connection con2 +# Sending: +rename table t0 to t3, t1 to t0, t3 to t1; +# --> connection con1 +# Waiting for 'rename table ...' to get blocked... +# --> connection default +handler t0 open; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +select * from t0; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +handler t1 open; +commit; +handler t1 close; +# --> connection con2 +# Reaping 'rename table ...'... +# --> connection default +handler t1 open; +handler t1 read a prev; +a +5 +handler t1 close; +drop table t0; +# +# Originally there was a deadlock error in this test. +# With implementation of deadlock detector +# we no longer deadlock, but block and wait on a lock. +# The HANDLER is auto-closed as soon as the connection +# sees a pending conflicting lock against it. +# +create table t2 (a int, key a (a)); +handler t1 open; +# --> connection con1 +lock tables t2 read; +# --> connection con2 +# Sending 'drop table t2'... +drop table t2; +# --> connection con1 +# Waiting for 'drop table t2' to get blocked... +# --> connection default +# Sending 'select * from t2' +select * from t2; +# --> connection con1 +# Waiting for 'select * from t2' to get blocked... +unlock tables; +# --> connection con2 +# Reaping 'drop table t2'... +# --> connection default +# Reaping 'select * from t2' +ERROR 42S02: Table 'test.t2' doesn't exist +handler t1 close; +# +# ROLLBACK TO SAVEPOINT releases transactional locks, +# but has no effect on open HANDLERs +# +create table t2 like t1; +create table t3 like t1; +begin; +# Have something before the savepoint +select * from t3; +a +savepoint sv; +handler t1 open; +handler t1 read a first; +a +1 +handler t1 read a next; +a +2 +select * from t2; +a +# --> connection con1 +# Sending: +drop table t1; +# --> connection con2 +# Sending: +drop table t2; +# --> connection default +# Let DROP TABLE statements sync in. We must use +# a separate connection for that, because otherwise SELECT +# will auto-close the HANDLERs, becaues there are pending +# exclusive locks against them. +# --> connection con3 +# Waiting for 'drop table t1' to get blocked... +# Waiting for 'drop table t2' to get blocked... +# Demonstrate that t2 lock was released and t2 was dropped +# after ROLLBACK TO SAVEPOINT +# --> connection default +rollback to savepoint sv; +# --> connection con2 +# Reaping 'drop table t2'... +# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +# lock. +# --> connection default +handler t1 read a next; +a +3 +handler t1 read a next; +a +4 +# Demonstrate that the drop will go through as soon as we close the +# HANDLER +handler t1 close; +# connection con1 +# Reaping 'drop table t1'... +# --> connection default +commit; +drop table t3; +# +# A few special cases when using SAVEPOINT/ROLLBACK TO +# SAVEPOINT and HANDLER. +# +# Show that rollback to the savepoint taken in the beginning +# of the transaction doesn't release mdl lock on +# the HANDLER that was opened later. +# +create table t1 (a int, key a(a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 like t1; +begin; +savepoint sv; +handler t1 open; +handler t1 read a first; +a +1 +handler t1 read a next; +a +2 +select * from t2; +a +# --> connection con1 +# Sending: +drop table t1; +# --> connection con2 +# Sending: +drop table t2; +# --> connection default +# Let DROP TABLE statements sync in. We must use +# a separate connection for that, because otherwise SELECT +# will auto-close the HANDLERs, becaues there are pending +# exclusive locks against them. +# --> connection con3 +# Waiting for 'drop table t1' to get blocked... +# Waiting for 'drop table t2' to get blocked... +# Demonstrate that t2 lock was released and t2 was dropped +# after ROLLBACK TO SAVEPOINT +# --> connection default +rollback to savepoint sv; +# --> connection con2 +# Reaping 'drop table t2'... +# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +# lock. +# --> connection default +handler t1 read a next; +a +3 +handler t1 read a next; +a +4 +# Demonstrate that the drop will go through as soon as we close the +# HANDLER +handler t1 close; +# connection con1 +# Reaping 'drop table t1'... +# --> connection default +commit; +# +# Show that rollback to the savepoint taken in the beginning +# of the transaction works properly (no valgrind warnins, etc), +# even though it's done after the HANDLER mdl lock that was there +# at the beginning is released and added again. +# +create table t1 (a int, key a(a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 like t1; +create table t3 like t1; +insert into t3 (a) select a from t1; +begin; +handler t1 open; +savepoint sv; +handler t1 read a first; +a +1 +select * from t2; +a +handler t1 close; +handler t3 open; +handler t3 read a first; +a +1 +rollback to savepoint sv; +# --> connection con1 +drop table t1, t2; +# Sending: +drop table t3; +# Let DROP TABLE statement sync in. +# --> connection con2 +# Waiting for 'drop table t3' to get blocked... +# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +# lock. +# --> connection default +handler t3 read a next; +a +2 +# Demonstrate that the drop will go through as soon as we close the +# HANDLER +handler t3 close; +# connection con1 +# Reaping 'drop table t3'... +# --> connection default +commit; +# +# If we have to wait on an exclusive locks while having +# an open HANDLER, ER_LOCK_DEADLOCK is reported. +# +create table t1 (a int, key a(a)); +create table t2 like t1; +handler t1 open; +# --> connection con1 +lock table t1 write, t2 write; +# --> connection default +drop table t2; +# --> connection con2 +# Waiting for 'drop table t2' to get blocked... +# --> connection con1 +drop table t1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +unlock tables; +# --> connection default +# Demonstrate that there is no deadlock with FLUSH TABLE, +# even though it is waiting for the other table to go away +create table t2 like t1; +# Sending: +flush table t2; +# --> connection con2 +drop table t1; +# --> connection con1 +unlock tables; +# --> connection default +# Reaping 'flush table t2'... +drop table t2; +# +# Bug #46224 HANDLER statements within a transaction might +# lead to deadlocks +# +create table t1 (a int, key a(a)); +insert into t1 values (1), (2); +# --> connection default +begin; +select * from t1; +a +1 +2 +handler t1 open; +# --> connection con1 +# Sending: +lock tables t1 write; +# --> connection con2 +# Check that 'lock tables t1 write' waits until transaction which +# has read from the table commits. +# --> connection default +# The below 'handler t1 read ...' should not be blocked as +# 'lock tables t1 write' has not succeeded yet. +handler t1 read a next; +a +1 +# Unblock 'lock tables t1 write'. +commit; +# --> connection con1 +# Reap 'lock tables t1 write'. +# --> connection default +# Sending: +handler t1 read a next; +# --> connection con1 +# Waiting for 'handler t1 read a next' to get blocked... +# The below 'drop table t1' should be able to proceed without +# waiting as it will force HANDLER to be closed. +drop table t1; +unlock tables; +# --> connection default +# Reaping 'handler t1 read a next'... +ERROR 42S02: Table 'test.t1' doesn't exist +handler t1 close; +# --> connection con1 +# --> connection con2 +# --> connection con3 +# +# A temporary table test. +# Check that we don't loose positions of HANDLER opened +# against a temporary table. +# +create table t1 (a int, b int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create temporary table t2 (a int, b int, key a (a)); +insert into t2 (a) select a from t1; +handler t1 open; +handler t1 read a next; +a b +1 NULL +handler t2 open; +handler t2 read a next; +a b +1 NULL +flush table t1; +handler t2 read a next; +a b +2 NULL +# Sic: the position is lost +handler t1 read a next; +a b +1 NULL +select * from t1; +a b +1 NULL +2 NULL +3 NULL +4 NULL +5 NULL +# Sic: the position is not lost +handler t2 read a next; +a b +3 NULL +select * from t2; +ERROR HY000: Can't reopen table: 't2' +handler t2 read a next; +a b +4 NULL +drop table t1; +drop temporary table t2; +# +# A test for lock_table_names()/unlock_table_names() function. +# It should work properly in presence of open HANDLER. +# +create table t1 (a int, b int, key a (a)); +create table t2 like t1; +create table t3 like t1; +create table t4 like t1; +handler t1 open; +handler t2 open; +rename table t4 to t5, t3 to t4, t5 to t3; +handler t1 read first; +a b +handler t2 read first; +a b +drop table t1, t2, t3, t4; +# +# A test for FLUSH TABLES WITH READ LOCK and HANDLER statements. +# +set autocommit=0; +create table t1 (a int, b int, key a (a)); +insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5); +create table t2 like t1; +insert into t2 (a, b) select a, b from t1; +create table t3 like t1; +insert into t3 (a, b) select a, b from t1; +commit; +flush tables with read lock; +handler t1 open; +lock table t1 read; +handler t1 read next; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +# This implicitly leaves LOCK TABLES but doesn't drop the GLR +lock table not_exists_write read; +ERROR 42S02: Table 'test.not_exists_write' doesn't exist +# We still have the read lock. +drop table t1; +ERROR HY000: Can't execute the query because you have a conflicting read lock +handler t1 open; +select a from t2; +a +1 +2 +3 +4 +5 +handler t1 read next; +a b +1 1 +flush tables with read lock; +handler t2 open; +flush tables with read lock; +handler t1 read next; +a b +1 1 +select a from t3; +a +1 +2 +3 +4 +5 +handler t2 read next; +a b +1 1 +handler t1 close; +rollback; +handler t2 close; +drop table t1; +ERROR HY000: Can't execute the query because you have a conflicting read lock +commit; +flush tables; +drop table t1; +ERROR HY000: Can't execute the query because you have a conflicting read lock +unlock tables; +drop table t1; +set autocommit=default; +drop table t2, t3; +# +# HANDLER statement and operation-type aware metadata locks. +# Check that when we clone a ticket for HANDLER we downrade +# the lock. +# +# Establish an auxiliary connection con1. +# -> connection default +create table t1 (a int, b int, key a (a)); +insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5); +begin; +insert into t1 (a, b) values (6, 6); +handler t1 open; +handler t1 read a last; +a b +6 6 +insert into t1 (a, b) values (7, 7); +handler t1 read a last; +a b +7 7 +commit; +# -> connection con1 +# Demonstrate that the HANDLER doesn't hold MDL_SHARED_WRITE. +lock table t1 write; +unlock tables; +# -> connection default +handler t1 read a prev; +a b +6 6 +handler t1 close; +# Cleanup. +drop table t1; +# -> connection con1 +# -> connection default +# +# A test for Bug#50555 "handler commands crash server in +# my_hash_first()". +# +handler no_such_table read no_such_index first; +ERROR 42S02: Unknown table 'no_such_table' in HANDLER +handler no_such_table close; +ERROR 42S02: Unknown table 'no_such_table' in HANDLER diff --git a/mysql-test/r/handler_myisam.result b/mysql-test/r/handler_myisam.result index 90a1bdfe6be..80f728a4dcd 100644 --- a/mysql-test/r/handler_myisam.result +++ b/mysql-test/r/handler_myisam.result @@ -547,6 +547,7 @@ c1 1 connection: flush flush tables;; +connection: waiter connection: default handler t2 open; handler t2 read first; @@ -558,23 +559,36 @@ c1 handler t1 close; handler t2 close; drop table t1,t2; -drop table if exists t1,t2; +drop table if exists t1, t0; create table t1 (c1 int); connection: default handler t1 open; handler t1 read first; c1 connection: flush -rename table t1 to t2;; +rename table t1 to t0;; +connection: waiter connection: default -handler t2 open; -handler t2 read first; -c1 +# +# RENAME placed two pending locks and waits. +# When HANDLER t0 OPEN does open_tables(), it calls +# mysql_ha_flush(), which in turn closes the open HANDLER for t1. +# RENAME TABLE gets unblocked. If it gets scheduled quickly +# and manages to complete before open_tables() +# of HANDLER t0 OPEN, open_tables() and therefore the whole +# HANDLER t0 OPEN succeeds. Otherwise open_tables() +# notices a pending or active exclusive metadata lock on t2 +# and the whole HANDLER t0 OPEN fails with ER_LOCK_DEADLOCK +# error. +# +handler t0 open; +handler t0 close; +connection: flush handler t1 read next; -ERROR 42S02: Table 'test.t1' doesn't exist +ERROR 42S02: Unknown table 't1' in HANDLER handler t1 close; -handler t2 close; -drop table t2; +ERROR 42S02: Unknown table 't1' in HANDLER +drop table t0; drop table if exists t1; create temporary table t1 (a int, b char(1), key a(a), key b(a,b)); insert into t1 values (0,"a"),(1,"b"),(2,"c"),(3,"d"),(4,"e"), @@ -729,19 +743,725 @@ drop table t1; handler t1 read a next; ERROR 42S02: Unknown table 't1' in HANDLER drop table if exists t1; +# First test case which is supposed trigger the execution +# path on which problem was discovered. create table t1 (a int); insert into t1 values (1); handler t1 open; +lock table t1 write; alter table t1 engine=memory; handler t1 read a next; ERROR HY000: Table storage engine for 't1' doesn't have this option handler t1 close; +unlock tables; +drop table t1; +# Now test case which was reported originally but which no longer +# triggers execution path which has caused the problem. +create table t1 (a int, key(a)); +insert into t1 values (1); +handler t1 open; +alter table t1 engine=memory; +# Since S metadata lock was already acquired at HANDLER OPEN time +# and TL_READ lock requested by HANDLER READ is compatible with +# ALTER's TL_WRITE_ALLOW_READ the below statement should succeed +# without waiting. The old version of table should be used in it. +handler t1 read a next; +a +1 +handler t1 close; drop table t1; USE information_schema; HANDLER COLUMNS OPEN; ERROR HY000: Incorrect usage of HANDLER OPEN and information_schema USE test; # +# Add test coverage for HANDLER and LOCK TABLES, HANDLER and DDL. +# +drop table if exists t1, t2, t3; +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 (a int, key a (a)) select * from t1; +create temporary table t3 (a int, key a (a)) select * from t2; +handler t1 open; +handler t2 open; +handler t3 open; +# +# LOCK TABLES implicitly closes all handlers. +# +lock table t3 read; +# +# No HANDLER sql is available under lock tables anyway. +# +handler t1 open; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +handler t1 read next; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +handler t2 close; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +handler t3 open; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +# After UNLOCK TABLES no handlers are around, they were +# implicitly closed. +unlock tables; +drop temporary table t3; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +handler t2 close; +ERROR 42S02: Unknown table 't2' in HANDLER +handler t3 read next; +ERROR 42S02: Unknown table 't3' in HANDLER +# +# Other operations also implicitly close handler: +# +# TRUNCATE +# +handler t1 open; +truncate table t1; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +handler t1 open; +# +# CREATE TRIGGER +# +create trigger t1_ai after insert on t1 for each row set @a=1; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# DROP TRIGGER +# +handler t1 open; +drop trigger t1_ai; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# ALTER TABLE +# +handler t1 open; +alter table t1 add column b int; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# ANALYZE TABLE +# +handler t1 open; +analyze table t1; +Table Op Msg_type Msg_text +test.t1 analyze status Table is already up to date +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# OPTIMIZE TABLE +# +handler t1 open; +optimize table t1; +Table Op Msg_type Msg_text +test.t1 optimize status OK +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# REPAIR TABLE +# +handler t1 open; +repair table t1; +Table Op Msg_type Msg_text +test.t1 repair status OK +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# DROP TABLE, naturally. +# +handler t1 open; +drop table t1; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +create table t1 (a int, b int, key a (a)) select a from t2; +# +# RENAME TABLE, naturally +# +handler t1 open; +rename table t1 to t3; +handler t1 read next; +ERROR 42S02: Unknown table 't1' in HANDLER +# +# CREATE TABLE (even with IF NOT EXISTS clause, +# and the table exists). +# +handler t2 open; +create table if not exists t2 (a int); +Warnings: +Note 1050 Table 't2' already exists +handler t2 read next; +ERROR 42S02: Unknown table 't2' in HANDLER +rename table t3 to t1; +drop table t2; +# +# FLUSH TABLE doesn't close the table but loses the position +# +handler t1 open; +handler t1 read a prev; +b a +NULL 5 +flush table t1; +handler t1 read a prev; +b a +NULL 5 +handler t1 close; +# +# FLUSH TABLES WITH READ LOCK behaves like FLUSH TABLE. +# +handler t1 open; +handler t1 read a prev; +b a +NULL 5 +flush tables with read lock; +handler t1 read a prev; +b a +NULL 5 +handler t1 close; +unlock tables; +# +# Explore the effect of HANDLER locks on concurrent DDL +# +handler t1 open; +# Establishing auxiliary connections con1, con2, con3 +# --> connection con1; +# Sending: +drop table t1 ; +# We can't use connection 'default' as wait_condition will +# autoclose handlers. +# --> connection con2 +# Waitng for 'drop table t1' to get blocked... +# --> connection default +handler t1 read a prev; +b a +NULL 5 +handler t1 read a prev; +b a +NULL 4 +handler t1 close; +# --> connection con1 +# Reaping 'drop table t1'... +# --> connection default +# +# Explore the effect of HANDLER locks in parallel with SELECT +# +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +begin; +select * from t1; +a +1 +2 +3 +4 +5 +handler t1 open; +handler t1 read a prev; +a +5 +handler t1 read a prev; +a +4 +handler t1 close; +# --> connection con1; +# Sending: +drop table t1 ; +# --> connection con2 +# Waiting for 'drop table t1' to get blocked... +# --> connection default +# We can still use the table, it's part of the transaction +select * from t1; +a +1 +2 +3 +4 +5 +# Such are the circumstances that t1 is a part of transaction, +# thus we can reopen it in the handler +handler t1 open; +# We can commit the transaction, it doesn't close the handler +# and doesn't let DROP to proceed. +commit; +handler t1 read a prev; +a +5 +handler t1 read a prev; +a +4 +handler t1 read a prev; +a +3 +handler t1 close; +# --> connection con1 +# Now drop can proceed +# Reaping 'drop table t1'... +# --> connection default +# +# Demonstrate that HANDLER locks and transaction locks +# reside in the same context, and we don't back-off +# when have transaction or handler locks. +# +create table t1 (a int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t0 (a int, key a (a)); +insert into t0 (a) values (1), (2), (3), (4), (5); +begin; +select * from t1; +a +1 +2 +3 +4 +5 +# --> connection con2 +# Sending: +rename table t0 to t3, t1 to t0, t3 to t1; +# --> connection con1 +# Waiting for 'rename table ...' to get blocked... +# --> connection default +handler t0 open; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +select * from t0; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +handler t1 open; +commit; +handler t1 close; +# --> connection con2 +# Reaping 'rename table ...'... +# --> connection default +handler t1 open; +handler t1 read a prev; +a +5 +handler t1 close; +drop table t0; +# +# Originally there was a deadlock error in this test. +# With implementation of deadlock detector +# we no longer deadlock, but block and wait on a lock. +# The HANDLER is auto-closed as soon as the connection +# sees a pending conflicting lock against it. +# +create table t2 (a int, key a (a)); +handler t1 open; +# --> connection con1 +lock tables t2 read; +# --> connection con2 +# Sending 'drop table t2'... +drop table t2; +# --> connection con1 +# Waiting for 'drop table t2' to get blocked... +# --> connection default +# Sending 'select * from t2' +select * from t2; +# --> connection con1 +# Waiting for 'select * from t2' to get blocked... +unlock tables; +# --> connection con2 +# Reaping 'drop table t2'... +# --> connection default +# Reaping 'select * from t2' +ERROR 42S02: Table 'test.t2' doesn't exist +handler t1 close; +# +# ROLLBACK TO SAVEPOINT releases transactional locks, +# but has no effect on open HANDLERs +# +create table t2 like t1; +create table t3 like t1; +begin; +# Have something before the savepoint +select * from t3; +a +savepoint sv; +handler t1 open; +handler t1 read a first; +a +1 +handler t1 read a next; +a +2 +select * from t2; +a +# --> connection con1 +# Sending: +drop table t1; +# --> connection con2 +# Sending: +drop table t2; +# --> connection default +# Let DROP TABLE statements sync in. We must use +# a separate connection for that, because otherwise SELECT +# will auto-close the HANDLERs, becaues there are pending +# exclusive locks against them. +# --> connection con3 +# Waiting for 'drop table t1' to get blocked... +# Waiting for 'drop table t2' to get blocked... +# Demonstrate that t2 lock was released and t2 was dropped +# after ROLLBACK TO SAVEPOINT +# --> connection default +rollback to savepoint sv; +# --> connection con2 +# Reaping 'drop table t2'... +# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +# lock. +# --> connection default +handler t1 read a next; +a +3 +handler t1 read a next; +a +4 +# Demonstrate that the drop will go through as soon as we close the +# HANDLER +handler t1 close; +# connection con1 +# Reaping 'drop table t1'... +# --> connection default +commit; +drop table t3; +# +# A few special cases when using SAVEPOINT/ROLLBACK TO +# SAVEPOINT and HANDLER. +# +# Show that rollback to the savepoint taken in the beginning +# of the transaction doesn't release mdl lock on +# the HANDLER that was opened later. +# +create table t1 (a int, key a(a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 like t1; +begin; +savepoint sv; +handler t1 open; +handler t1 read a first; +a +1 +handler t1 read a next; +a +2 +select * from t2; +a +# --> connection con1 +# Sending: +drop table t1; +# --> connection con2 +# Sending: +drop table t2; +# --> connection default +# Let DROP TABLE statements sync in. We must use +# a separate connection for that, because otherwise SELECT +# will auto-close the HANDLERs, becaues there are pending +# exclusive locks against them. +# --> connection con3 +# Waiting for 'drop table t1' to get blocked... +# Waiting for 'drop table t2' to get blocked... +# Demonstrate that t2 lock was released and t2 was dropped +# after ROLLBACK TO SAVEPOINT +# --> connection default +rollback to savepoint sv; +# --> connection con2 +# Reaping 'drop table t2'... +# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +# lock. +# --> connection default +handler t1 read a next; +a +3 +handler t1 read a next; +a +4 +# Demonstrate that the drop will go through as soon as we close the +# HANDLER +handler t1 close; +# connection con1 +# Reaping 'drop table t1'... +# --> connection default +commit; +# +# Show that rollback to the savepoint taken in the beginning +# of the transaction works properly (no valgrind warnins, etc), +# even though it's done after the HANDLER mdl lock that was there +# at the beginning is released and added again. +# +create table t1 (a int, key a(a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create table t2 like t1; +create table t3 like t1; +insert into t3 (a) select a from t1; +begin; +handler t1 open; +savepoint sv; +handler t1 read a first; +a +1 +select * from t2; +a +handler t1 close; +handler t3 open; +handler t3 read a first; +a +1 +rollback to savepoint sv; +# --> connection con1 +drop table t1, t2; +# Sending: +drop table t3; +# Let DROP TABLE statement sync in. +# --> connection con2 +# Waiting for 'drop table t3' to get blocked... +# Demonstrate that ROLLBACK TO SAVEPOINT didn't release the handler +# lock. +# --> connection default +handler t3 read a next; +a +2 +# Demonstrate that the drop will go through as soon as we close the +# HANDLER +handler t3 close; +# connection con1 +# Reaping 'drop table t3'... +# --> connection default +commit; +# +# If we have to wait on an exclusive locks while having +# an open HANDLER, ER_LOCK_DEADLOCK is reported. +# +create table t1 (a int, key a(a)); +create table t2 like t1; +handler t1 open; +# --> connection con1 +lock table t1 write, t2 write; +# --> connection default +drop table t2; +# --> connection con2 +# Waiting for 'drop table t2' to get blocked... +# --> connection con1 +drop table t1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +unlock tables; +# --> connection default +# Demonstrate that there is no deadlock with FLUSH TABLE, +# even though it is waiting for the other table to go away +create table t2 like t1; +# Sending: +flush table t2; +# --> connection con2 +drop table t1; +# --> connection con1 +unlock tables; +# --> connection default +# Reaping 'flush table t2'... +drop table t2; +# +# Bug #46224 HANDLER statements within a transaction might +# lead to deadlocks +# +create table t1 (a int, key a(a)); +insert into t1 values (1), (2); +# --> connection default +begin; +select * from t1; +a +1 +2 +handler t1 open; +# --> connection con1 +# Sending: +lock tables t1 write; +# --> connection con2 +# Check that 'lock tables t1 write' waits until transaction which +# has read from the table commits. +# --> connection default +# The below 'handler t1 read ...' should not be blocked as +# 'lock tables t1 write' has not succeeded yet. +handler t1 read a next; +a +1 +# Unblock 'lock tables t1 write'. +commit; +# --> connection con1 +# Reap 'lock tables t1 write'. +# --> connection default +# Sending: +handler t1 read a next; +# --> connection con1 +# Waiting for 'handler t1 read a next' to get blocked... +# The below 'drop table t1' should be able to proceed without +# waiting as it will force HANDLER to be closed. +drop table t1; +unlock tables; +# --> connection default +# Reaping 'handler t1 read a next'... +ERROR 42S02: Table 'test.t1' doesn't exist +handler t1 close; +# --> connection con1 +# --> connection con2 +# --> connection con3 +# +# A temporary table test. +# Check that we don't loose positions of HANDLER opened +# against a temporary table. +# +create table t1 (a int, b int, key a (a)); +insert into t1 (a) values (1), (2), (3), (4), (5); +create temporary table t2 (a int, b int, key a (a)); +insert into t2 (a) select a from t1; +handler t1 open; +handler t1 read a next; +a b +1 NULL +handler t2 open; +handler t2 read a next; +a b +1 NULL +flush table t1; +handler t2 read a next; +a b +2 NULL +# Sic: the position is lost +handler t1 read a next; +a b +1 NULL +select * from t1; +a b +1 NULL +2 NULL +3 NULL +4 NULL +5 NULL +# Sic: the position is not lost +handler t2 read a next; +a b +3 NULL +select * from t2; +ERROR HY000: Can't reopen table: 't2' +handler t2 read a next; +a b +4 NULL +drop table t1; +drop temporary table t2; +# +# A test for lock_table_names()/unlock_table_names() function. +# It should work properly in presence of open HANDLER. +# +create table t1 (a int, b int, key a (a)); +create table t2 like t1; +create table t3 like t1; +create table t4 like t1; +handler t1 open; +handler t2 open; +rename table t4 to t5, t3 to t4, t5 to t3; +handler t1 read first; +a b +handler t2 read first; +a b +drop table t1, t2, t3, t4; +# +# A test for FLUSH TABLES WITH READ LOCK and HANDLER statements. +# +set autocommit=0; +create table t1 (a int, b int, key a (a)); +insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5); +create table t2 like t1; +insert into t2 (a, b) select a, b from t1; +create table t3 like t1; +insert into t3 (a, b) select a, b from t1; +commit; +flush tables with read lock; +handler t1 open; +lock table t1 read; +handler t1 read next; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +# This implicitly leaves LOCK TABLES but doesn't drop the GLR +lock table not_exists_write read; +ERROR 42S02: Table 'test.not_exists_write' doesn't exist +# We still have the read lock. +drop table t1; +ERROR HY000: Can't execute the query because you have a conflicting read lock +handler t1 open; +select a from t2; +a +1 +2 +3 +4 +5 +handler t1 read next; +a b +1 1 +flush tables with read lock; +handler t2 open; +flush tables with read lock; +handler t1 read next; +a b +1 1 +select a from t3; +a +1 +2 +3 +4 +5 +handler t2 read next; +a b +1 1 +handler t1 close; +rollback; +handler t2 close; +drop table t1; +ERROR HY000: Can't execute the query because you have a conflicting read lock +commit; +flush tables; +drop table t1; +ERROR HY000: Can't execute the query because you have a conflicting read lock +unlock tables; +drop table t1; +set autocommit=default; +drop table t2, t3; +# +# HANDLER statement and operation-type aware metadata locks. +# Check that when we clone a ticket for HANDLER we downrade +# the lock. +# +# Establish an auxiliary connection con1. +# -> connection default +create table t1 (a int, b int, key a (a)); +insert into t1 (a, b) values (1, 1), (2, 1), (3, 2), (4, 2), (5, 5); +begin; +insert into t1 (a, b) values (6, 6); +handler t1 open; +handler t1 read a last; +a b +6 6 +insert into t1 (a, b) values (7, 7); +handler t1 read a last; +a b +7 7 +commit; +# -> connection con1 +# Demonstrate that the HANDLER doesn't hold MDL_SHARED_WRITE. +lock table t1 write; +unlock tables; +# -> connection default +handler t1 read a prev; +a b +6 6 +handler t1 close; +# Cleanup. +drop table t1; +# -> connection con1 +# -> connection default +# +# A test for Bug#50555 "handler commands crash server in +# my_hash_first()". +# +handler no_such_table read no_such_index first; +ERROR 42S02: Unknown table 'no_such_table' in HANDLER +handler no_such_table close; +ERROR 42S02: Unknown table 'no_such_table' in HANDLER +# # BUG #46456: HANDLER OPEN + TRUNCATE + DROP (temporary) TABLE, crash # CREATE TABLE t1 AS SELECT 1 AS f1; diff --git a/mysql-test/r/implicit_commit.result b/mysql-test/r/implicit_commit.result new file mode 100644 index 00000000000..8c330550a3b --- /dev/null +++ b/mysql-test/r/implicit_commit.result @@ -0,0 +1,1061 @@ +SET GLOBAL EVENT_SCHEDULER = OFF; +SET BINLOG_FORMAT = STATEMENT; +CREATE DATABASE db1; +USE db1; +CREATE TABLE t1 (a INT, KEY a(a)) ENGINE=INNODB; +INSERT INTO t1 VALUES (1),(2),(3),(4),(5); +CREATE TABLE t3 (a INT) ENGINE=MyISAM; +INSERT INTO t3 SELECT * FROM t1; +CREATE TABLE trans (a INT) ENGINE=INNODB; +CREATE PROCEDURE test_if_commit() +BEGIN +ROLLBACK; +SELECT IF (COUNT(*) > 0, "YES", "NO") AS "IMPLICIT COMMIT" FROM trans; +DELETE FROM trans; +COMMIT; +END| +SET AUTOCOMMIT = FALSE; +# +# SQLCOM_SELECT +# +INSERT INTO db1.trans (a) VALUES (1); +select 1 as res from t1 where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_CREATE_TABLE LIKE +# +INSERT INTO db1.trans (a) VALUES (1); +create table t2 like t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_SHOW_CREATE +# +INSERT INTO db1.trans (a) VALUES (1); +show create table t2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DROP_TABLE +# +INSERT INTO db1.trans (a) VALUES (1); +drop table t2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_CREATE_TABLE TEMPORARY +# +INSERT INTO db1.trans (a) VALUES (1); +create temporary table t2 as select * from t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DROP_TABLE TEMPORARY +# +INSERT INTO db1.trans (a) VALUES (1); +drop temporary table t2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_CREATE_TABLE +# +INSERT INTO db1.trans (a) VALUES (1); +create table t2 as select * from t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_UPDATE +# +INSERT INTO db1.trans (a) VALUES (1); +update t2 set a=a+1 where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_INSERT +# +INSERT INTO db1.trans (a) VALUES (1); +insert into t2 set a=((1) in (select * from t1)); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_INSERT_SELECT +# +INSERT INTO db1.trans (a) VALUES (1); +insert into t2 select * from t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_REPLACE +# +INSERT INTO db1.trans (a) VALUES (1); +replace t2 set a=((1) in (select * from t1)); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_REPLACE_SELECT +# +INSERT INTO db1.trans (a) VALUES (1); +replace t2 select * from t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DELETE +# +INSERT INTO db1.trans (a) VALUES (1); +delete from t2 where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DELETE_MULTI +# +INSERT INTO db1.trans (a) VALUES (1); +delete t2, t3 from t2, t3 where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_UPDATE_MULTI +# +select * from t2; +a +INSERT INTO db1.trans (a) VALUES (1); +update t2, t3 set t3.a=t2.a, t2.a=null where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_LOAD +# +create table t4 (a varchar(100)); +INSERT INTO db1.trans (a) VALUES (1); +load data infile '../../std_data/words.dat' into table t4; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +drop table t4; +# +# SQLCOM_SHOW_DATABASES +# +INSERT INTO db1.trans (a) VALUES (1); +show databases where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_TABLES +# +INSERT INTO db1.trans (a) VALUES (1); +show tables where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_FIELDS +# +INSERT INTO db1.trans (a) VALUES (1); +show fields from t1 where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_KEYS +# +INSERT INTO db1.trans (a) VALUES (1); +show keys from t1 where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_VARIABLES +# +INSERT INTO db1.trans (a) VALUES (1); +show variables where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_STATUS +# +INSERT INTO db1.trans (a) VALUES (1); +show status where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_ENGINE_MUTEX +# +INSERT INTO db1.trans (a) VALUES (1); +show engine all mutex; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_PROCESSLIST +# +INSERT INTO db1.trans (a) VALUES (1); +show processlist; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_ENGINE_LOGS +# +INSERT INTO db1.trans (a) VALUES (1); +show engine all logs; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_ENGINE_STATUS +# +INSERT INTO db1.trans (a) VALUES (1); +show engine all status; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_CHARSETS +# +INSERT INTO db1.trans (a) VALUES (1); +show charset where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_COLLATIONS +# +INSERT INTO db1.trans (a) VALUES (1); +show collation where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_TABLE_STATUS +# +INSERT INTO db1.trans (a) VALUES (1); +show table status where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_TRIGGERS +# +INSERT INTO db1.trans (a) VALUES (1); +show triggers where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_OPEN_TABLES +# +INSERT INTO db1.trans (a) VALUES (1); +show open tables where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_STATUS_PROC +# +INSERT INTO db1.trans (a) VALUES (1); +show procedure status where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_STATUS_FUNC +# +INSERT INTO db1.trans (a) VALUES (1); +show function status where (1) in (select * from t1); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SET_OPTION +# +INSERT INTO db1.trans (a) VALUES (1); +set @a=((1) in (select * from t1)); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DO +# +INSERT INTO db1.trans (a) VALUES (1); +do ((1) in (select * from t1)); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_CALL +# +create procedure p1(a int) begin end; +INSERT INTO db1.trans (a) VALUES (1); +call p1((1) in (select * from t1)); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +drop procedure p1; +# +# SQLCOM_CREATE_VIEW +# +INSERT INTO db1.trans (a) VALUES (1); +create view v1 as select * from t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_ALTER_VIEW +# +INSERT INTO db1.trans (a) VALUES (1); +alter view v1 as select 2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_DROP_VIEW +# +INSERT INTO db1.trans (a) VALUES (1); +drop view v1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_CREATE_INDEX +# +INSERT INTO db1.trans (a) VALUES (1); +create index idx1 on t1(a); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_DROP_INDEX +# +INSERT INTO db1.trans (a) VALUES (1); +drop index idx1 on t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_ALTER_TABLE +# +INSERT INTO db1.trans (a) VALUES (1); +alter table t1 add column b int; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +INSERT INTO db1.trans (a) VALUES (1); +alter table t1 change b c int; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +INSERT INTO db1.trans (a) VALUES (1); +alter table t1 drop column c; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_ALTER_TABLE TEMPORARY +# +create temporary table t4 (a int); +INSERT INTO db1.trans (a) VALUES (1); +alter table t1 add column b int; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +INSERT INTO db1.trans (a) VALUES (1); +alter table t1 change b c int; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +INSERT INTO db1.trans (a) VALUES (1); +alter table t1 drop column c; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +drop table t4; +# +# SQLCOM_TRUNCATE +# +insert into t2 select * from t1; +INSERT INTO db1.trans (a) VALUES (1); +truncate table t2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +insert into t2 select * from t1; +# +# SQLCOM_TRUNCATE TEMPORARY +# +create temporary table t4 as select * from t1; +INSERT INTO db1.trans (a) VALUES (1); +truncate table t4; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +drop temporary table t4; +# +# SQLCOM_SHOW_MASTER_STAT +# +INSERT INTO db1.trans (a) VALUES (1); +show master status; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_SLAVE_STAT +# +INSERT INTO db1.trans (a) VALUES (1); +show slave status; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_GRANT +# +INSERT INTO db1.trans (a) VALUES (1); +grant all on test.t1 to mysqltest_2@localhost with grant option; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_REVOKE +# +INSERT INTO db1.trans (a) VALUES (1); +revoke select on test.t1 from mysqltest_2@localhost; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_REVOKE_ALL +# +INSERT INTO db1.trans (a) VALUES (1); +revoke all on test.t1 from mysqltest_2@localhost; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +drop user mysqltest_2@localhost; +# +# SQLCOM_SHOW_GRANTS +# +INSERT INTO db1.trans (a) VALUES (1); +show grants; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +INSERT INTO db1.trans (a) VALUES (1); +show grants for current_user(); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_LOCK_TABLES +# +INSERT INTO db1.trans (a) VALUES (1); +lock tables t1 write, trans write; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_UNLOCK_TABLES +# +INSERT INTO db1.trans (a) VALUES (1); +unlock tables; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_CREATE_DB +# +INSERT INTO db1.trans (a) VALUES (1); +create database db2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_CHANGE_DB +# +create table db2.t1 (a int); +insert into db2.t1 values (1); +commit; +INSERT INTO db1.trans (a) VALUES (1); +use db2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_CREATE_DB +# +INSERT INTO db1.trans (a) VALUES (1); +show create database db2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_ALTER_DB +# +# +# SQLCOM_ALTER_DB_UPGRADE +# +# +# SQLCOM_DROP_DB +# +use db1; +INSERT INTO db1.trans (a) VALUES (1); +drop database db2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_REPAIR +# +INSERT INTO db1.trans (a) VALUES (1); +repair table t2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +INSERT INTO db1.trans (a) VALUES (1); +repair table t2 use_frm; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_OPTIMIZE +# +INSERT INTO db1.trans (a) VALUES (1); +optimize table t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_CHECK +# +INSERT INTO db1.trans (a) VALUES (1); +check table t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +INSERT INTO db1.trans (a) VALUES (1); +check table t1 extended; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_ASSIGN_TO_KEYCACHE +# +set global keycache.key_buffer_size=128*1024; +INSERT INTO db1.trans (a) VALUES (1); +cache index t3 in keycache; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +set global keycache.key_buffer_size=0; +# +# SQLCOM_PRELOAD_KEYS +# +INSERT INTO db1.trans (a) VALUES (1); +load index into cache t3; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_FLUSH +# +INSERT INTO db1.trans (a) VALUES (1); +flush local privileges; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +INSERT INTO db1.trans (a) VALUES (1); +flush privileges; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_KILL +# +# +# SQLCOM_ANALYZE +# +INSERT INTO db1.trans (a) VALUES (1); +analyze table t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_ROLLBACK +# +INSERT INTO db1.trans (a) VALUES (1); +rollback; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_ROLLBACK_TO_SAVEPOINT +# +# +# SQLCOM_COMMIT +# +INSERT INTO db1.trans (a) VALUES (1); +commit; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_SAVEPOINT +# +INSERT INTO db1.trans (a) VALUES (1); +savepoint sp1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_RELEASE_SAVEPOINT +# +# +# SQLCOM_SLAVE_START +# +# +# SQLCOM_SLAVE_STOP +# +# +# SQLCOM_BEGIN +# +INSERT INTO db1.trans (a) VALUES (1); +begin; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_CHANGE_MASTER +# +# +# SQLCOM_RENAME_TABLE +# +INSERT INTO db1.trans (a) VALUES (1); +rename table t3 to t4; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +INSERT INTO db1.trans (a) VALUES (1); +rename table t4 to t3; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_RESET +# +# +# SQLCOM_PURGE +# +# +# SQLCOM_PURGE_BEFORE +# +# +# SQLCOM_SHOW_BINLOGS +# +# +# SQLCOM_HA_OPEN +# +INSERT INTO db1.trans (a) VALUES (1); +handler t1 open as ha1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_HA_READ +# +INSERT INTO db1.trans (a) VALUES (1); +handler ha1 read a first; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_HA_CLOSE +# +INSERT INTO db1.trans (a) VALUES (1); +handler ha1 close; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_SLAVE_HOSTS +# +INSERT INTO db1.trans (a) VALUES (1); +show slave hosts; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_BINLOG_EVENTS +# +INSERT INTO db1.trans (a) VALUES (1); +show binlog events; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_NEW_MASTER +# +# +# SQLCOM_SHOW_WARNS +# +INSERT INTO db1.trans (a) VALUES (1); +show warnings; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_EMPTY_QUERY +# +# +# SQLCOM_SHOW_ERRORS +# +INSERT INTO db1.trans (a) VALUES (1); +show errors; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_STORAGE_ENGINES +# +INSERT INTO db1.trans (a) VALUES (1); +show engines; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_PRIVILEGES +# +INSERT INTO db1.trans (a) VALUES (1); +show privileges; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_HELP +# +INSERT INTO db1.trans (a) VALUES (1); +help 'foo'; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_CREATE_USER +# +INSERT INTO db1.trans (a) VALUES (1); +create user trxusr1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_RENAME_USER +# +INSERT INTO db1.trans (a) VALUES (1); +rename user 'trxusr1' to 'trxusr2'; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_DROP_USER +# +INSERT INTO db1.trans (a) VALUES (1); +drop user trxusr2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_CHECKSUM +# +INSERT INTO db1.trans (a) VALUES (1); +checksum table t1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_CREATE_PROCEDURE +# +INSERT INTO db1.trans (a) VALUES (1); +create procedure p1(a int) begin end; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_ALTER_PROCEDURE +# +INSERT INTO db1.trans (a) VALUES (1); +alter procedure p1 comment 'foobar'; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_SHOW_CREATE_PROC +# +INSERT INTO db1.trans (a) VALUES (1); +show create procedure p1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_STATUS_PROC +# +INSERT INTO db1.trans (a) VALUES (1); +show procedure status; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_PROC_CODE +# +INSERT INTO db1.trans (a) VALUES (1); +show procedure code p1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DROP_PROCEDURE +# +INSERT INTO db1.trans (a) VALUES (1); +drop procedure p1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_CREATE_FUNCTION +# +# +# SQLCOM_DROP_FUNCTION +# +# +# SQLCOM_CREATE_SPFUNCTION +# +INSERT INTO db1.trans (a) VALUES (1); +create function f1() returns int return 69; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_ALTER_FUNCTION +# +INSERT INTO db1.trans (a) VALUES (1); +alter function f1 comment 'comment'; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_SHOW_CREATE_FUNC +# +INSERT INTO db1.trans (a) VALUES (1); +show create function f1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_STATUS_FUNC +# +INSERT INTO db1.trans (a) VALUES (1); +show function status like '%f%'; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_FUNC_CODE +# +INSERT INTO db1.trans (a) VALUES (1); +show function code f1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_PREPARE +# +INSERT INTO db1.trans (a) VALUES (1); +prepare stmt1 from "insert into t1 values (5)"; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_EXECUTE +# +INSERT INTO db1.trans (a) VALUES (1); +execute stmt1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DEALLOCATE_PREPARE +# +INSERT INTO db1.trans (a) VALUES (1); +deallocate prepare stmt1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_CREATE_TRIGGER +# +INSERT INTO db1.trans (a) VALUES (1); +create trigger trg1 before insert on t1 for each row set @a:=1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_SHOW_CREATE_TRIGGER +# +INSERT INTO db1.trans (a) VALUES (1); +show create trigger trg1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DROP_TRIGGER +# +INSERT INTO db1.trans (a) VALUES (1); +drop trigger trg1; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_XA_START +# +# +# SQLCOM_XA_END +# +# +# SQLCOM_XA_PREPARE +# +# +# SQLCOM_XA_COMMIT +# +# +# SQLCOM_XA_ROLLBACK +# +# +# SQLCOM_XA_RECOVER +# +# +# SQLCOM_ALTER_TABLESPACE +# +# +# SQLCOM_INSTALL_PLUGIN +# +# +# SQLCOM_SHOW_PLUGINS +# +# +# SQLCOM_UNINSTALL_PLUGIN +# +# +# SQLCOM_SHOW_AUTHORS +# +INSERT INTO db1.trans (a) VALUES (1); +show authors; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_BINLOG_BASE64_EVENT +# +# +# SQLCOM_SHOW_CONTRIBUTORS +# +INSERT INTO db1.trans (a) VALUES (1); +show contributors; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_CREATE_SERVER +# +# +# SQLCOM_ALTER_SERVER +# +# +# SQLCOM_DROP_SERVER +# +# +# SQLCOM_CREATE_EVENT +# +INSERT INTO db1.trans (a) VALUES (1); +create event ev1 on schedule every 1 second do insert into t1 values (6); +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_ALTER_EVENT +# +INSERT INTO db1.trans (a) VALUES (1); +alter event ev1 rename to ev2 disable; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_SHOW_CREATE_EVENT +# +INSERT INTO db1.trans (a) VALUES (1); +show create event ev2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_EVENTS +# +INSERT INTO db1.trans (a) VALUES (1); +show events; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_DROP_EVENT +# +INSERT INTO db1.trans (a) VALUES (1); +drop event ev2; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +YES +# +# SQLCOM_BACKUP +# +# +# SQLCOM_SHOW_ARCHIVE +# +# +# SQLCOM_RESTORE +# +# +# SQLCOM_BACKUP_TEST +# +# +# SQLCOM_SHOW_PROFILE +# +INSERT INTO db1.trans (a) VALUES (1); +show profile memory; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +# +# SQLCOM_SHOW_PROFILES +# +INSERT INTO db1.trans (a) VALUES (1); +show profiles; +CALL db1.test_if_commit(); +IMPLICIT COMMIT +NO +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; +USE test; +DROP DATABASE db1; +End of tests diff --git a/mysql-test/r/information_schema.result b/mysql-test/r/information_schema.result index ea0f28d123d..f00d4ad8b7e 100644 --- a/mysql-test/r/information_schema.result +++ b/mysql-test/r/information_schema.result @@ -1652,6 +1652,57 @@ TEST_RESULT OK SET TIMESTAMP=DEFAULT; End of 5.1 tests. +# +# Additional test for WL#3726 "DDL locking for all metadata objects" +# To avoid possible deadlocks process of filling of I_S tables should +# use high-priority metadata lock requests when opening tables. +# Below we just test that we really use high-priority lock request +# since reproducing a deadlock will require much more complex test. +# +drop tables if exists t1, t2, t3; +create table t1 (i int); +create table t2 (j int primary key auto_increment); +# Switching to connection 'con3726_1' +lock table t2 read; +# Switching to connection 'con3726_2' +# RENAME below will be blocked by 'lock table t2 read' above but +# will add two pending requests for exclusive metadata locks. +rename table t2 to t3; +# Switching to connection 'default' +# These statements should not be blocked by pending lock requests +select table_name, column_name, data_type from information_schema.columns +where table_schema = 'test' and table_name in ('t1', 't2'); +table_name column_name data_type +t1 i int +t2 j int +select table_name, auto_increment from information_schema.tables +where table_schema = 'test' and table_name in ('t1', 't2'); +table_name auto_increment +t1 NULL +t2 1 +# Switching to connection 'con3726_1' +unlock tables; +# Switching to connection 'con3726_2' +# Switching to connection 'default' +drop tables t1, t3; +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE KEY_COLUMN_USAGE ALL NULL NULL NULL NULL NULL Open_full_table; Scanned all databases +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME='t1'; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE PARTITIONS ALL NULL TABLE_NAME NULL NULL NULL Using where; Open_full_table; Scanned 1 database +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS +WHERE CONSTRAINT_SCHEMA='test'; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE REFERENTIAL_CONSTRAINTS ALL NULL CONSTRAINT_SCHEMA NULL NULL NULL Using where; Open_full_table; Scanned 1 database +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS +WHERE TABLE_NAME='t1' and TABLE_SCHEMA='test'; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE TABLE_CONSTRAINTS ALL NULL TABLE_SCHEMA,TABLE_NAME NULL NULL NULL Using where; Open_full_table; Scanned 0 databases +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TRIGGERS +WHERE EVENT_OBJECT_SCHEMA='test'; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE TRIGGERS ALL NULL EVENT_OBJECT_SCHEMA NULL NULL NULL Using where; Open_full_table; Scanned 1 database create table information_schema.t1 (f1 INT); ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_schema' drop table information_schema.t1; @@ -1690,28 +1741,10 @@ ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_ LOCK TABLES t1 READ, information_schema.tables READ; ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_schema' DROP TABLE t1; -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE KEY_COLUMN_USAGE ALL NULL NULL NULL NULL NULL Open_full_table; Scanned all databases -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME='t1'; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE PARTITIONS ALL NULL TABLE_NAME NULL NULL NULL Using where; Open_full_table; Scanned 1 database -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS -WHERE CONSTRAINT_SCHEMA='test'; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE REFERENTIAL_CONSTRAINTS ALL NULL CONSTRAINT_SCHEMA NULL NULL NULL Using where; Open_full_table; Scanned 1 database -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS -WHERE TABLE_NAME='t1' and TABLE_SCHEMA='test'; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE TABLE_CONSTRAINTS ALL NULL TABLE_SCHEMA,TABLE_NAME NULL NULL NULL Using where; Open_full_table; Scanned 0 databases -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TRIGGERS -WHERE EVENT_OBJECT_SCHEMA='test'; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE TRIGGERS ALL NULL EVENT_OBJECT_SCHEMA NULL NULL NULL Using where; Open_full_table; Scanned 1 database SELECT * -FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE -LEFT JOIN INFORMATION_SCHEMA.COLUMNS -USING (TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME) -WHERE COLUMNS.TABLE_SCHEMA = 'test' +FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE +LEFT JOIN INFORMATION_SCHEMA.COLUMNS +USING (TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME) +WHERE COLUMNS.TABLE_SCHEMA = 'test' AND COLUMNS.TABLE_NAME = 't1'; TABLE_SCHEMA TABLE_NAME COLUMN_NAME CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME TABLE_CATALOG ORDINAL_POSITION POSITION_IN_UNIQUE_CONSTRAINT REFERENCED_TABLE_SCHEMA REFERENCED_TABLE_NAME REFERENCED_COLUMN_NAME TABLE_CATALOG ORDINAL_POSITION COLUMN_DEFAULT IS_NULLABLE DATA_TYPE CHARACTER_MAXIMUM_LENGTH CHARACTER_OCTET_LENGTH NUMERIC_PRECISION NUMERIC_SCALE CHARACTER_SET_NAME COLLATION_NAME COLUMN_TYPE COLUMN_KEY EXTRA PRIVILEGES COLUMN_COMMENT diff --git a/mysql-test/r/innodb-lock.result b/mysql-test/r/innodb-lock.result index 4ace4065c34..ab7e9aa7b25 100644 --- a/mysql-test/r/innodb-lock.result +++ b/mysql-test/r/innodb-lock.result @@ -25,6 +25,12 @@ id x 0 2 commit; drop table t1; +# +# Old lock method (where LOCK TABLE was ignored by InnoDB) no longer +# works due to fix for bugs #46272 "MySQL 5.4.4, new MDL: unnecessary +# deadlock" and bug #37346 "innodb does not detect deadlock between +# update and alter table". +# set @@innodb_table_locks=0; create table t1 (id integer primary key, x integer) engine=INNODB; insert into t1 values(0, 0),(1,1),(2,2); @@ -32,26 +38,27 @@ commit; SELECT * from t1 where id = 0 FOR UPDATE; id x 0 0 +# Connection 'con2'. set autocommit=0; set @@innodb_table_locks=0; -lock table t1 write; -update t1 set x=10 where id = 2; -SELECT * from t1 where id = 2; -id x -2 2 -UPDATE t1 set x=3 where id = 2; -commit; -SELECT * from t1; +# The following statement should block because SQL-level lock +# is taken on t1 which will wait until concurrent transaction +# is commited. +# Sending: +lock table t1 write;; +# Connection 'con1'. +# Wait until LOCK TABLE is blocked on SQL-level lock. +# We should be able to do UPDATEs and SELECTs within transaction. +update t1 set x=1 where id = 0; +select * from t1; id x -0 0 +0 1 1 1 -2 3 +2 2 +# Unblock LOCK TABLE. commit; +# Connection 'con2'. +# Reap LOCK TABLE. unlock tables; -commit; -select * from t1; -id x -0 0 -1 1 -2 10 +# Connection 'con1'. drop table t1; diff --git a/mysql-test/r/innodb.result b/mysql-test/r/innodb.result index 6cee55482e3..4f2009764fc 100644 --- a/mysql-test/r/innodb.result +++ b/mysql-test/r/innodb.result @@ -2834,10 +2834,10 @@ t2 CREATE TABLE `t2` ( DROP TABLE t2,t1; create table t1(a int not null, b int, c int, d int, primary key(a)) engine=innodb; insert into t1(a) values (1),(2),(3); +create trigger t1t before insert on t1 for each row begin set NEW.b = NEW.a * 10 + 5, NEW.c = NEW.a / 10; end | commit; set autocommit = 0; update t1 set b = 5 where a = 2; -create trigger t1t before insert on t1 for each row begin set NEW.b = NEW.a * 10 + 5, NEW.c = NEW.a / 10; end | set autocommit = 0; insert into t1(a) values (10),(20),(30),(40),(50),(60),(70),(80),(90),(100), (11),(21),(31),(41),(51),(61),(71),(81),(91),(101), @@ -2885,6 +2885,7 @@ insert into t2(a) values(8); delete from t2 where a = 3; update t4 set b = b + 1 where a = 3; commit; +commit; drop trigger t1t; drop trigger t2t; drop trigger t3t; diff --git a/mysql-test/r/innodb_mysql.result b/mysql-test/r/innodb_mysql.result index 76ff2cfc0f3..bccb5caf7d4 100644 --- a/mysql-test/r/innodb_mysql.result +++ b/mysql-test/r/innodb_mysql.result @@ -1105,6 +1105,8 @@ CREATE PROCEDURE p1 () BEGIN DECLARE i INT DEFAULT 50; DECLARE cnt INT; +# Continue even in the presence of ER_LOCK_DEADLOCK. +DECLARE CONTINUE HANDLER FOR 1213 BEGIN END; START TRANSACTION; ALTER TABLE t1 ENGINE=InnoDB; COMMIT; @@ -1618,6 +1620,7 @@ a b SELECT * FROM t1; a b 1 init+con1+con2 +COMMIT; # Switch to connection con1 # 3. test for updated key column: TRUNCATE t1; diff --git a/mysql-test/r/innodb_mysql_lock.result b/mysql-test/r/innodb_mysql_lock.result new file mode 100644 index 00000000000..375ae8aeb12 --- /dev/null +++ b/mysql-test/r/innodb_mysql_lock.result @@ -0,0 +1,88 @@ +# +# Bug #22876 Four-way deadlock +# +DROP TABLE IF EXISTS t1; +# Connection 1 +set @@autocommit=0; +CREATE TABLE t1(s1 INT UNIQUE) ENGINE=innodb; +INSERT INTO t1 VALUES (1); +# Connection 2 +set @@autocommit=0; +INSERT INTO t1 VALUES (2); +INSERT INTO t1 VALUES (1); +# Connection 3 +set @@autocommit=0; +DROP TABLE t1; +# Connection 1 +# Connection 1 is now holding the lock. +# Issuing insert from connection 1 while connection 2&3 +# is waiting for the lock should give a deadlock error. +INSERT INTO t1 VALUES (2); +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Cleanup +commit; +set @@autocommit=1; +commit; +set @@autocommit=1; +set @@autocommit=1; +# +# Test for bug #37346 "innodb does not detect deadlock between update +# and alter table". +# +drop table if exists t1; +create table t1 (c1 int primary key, c2 int, c3 int) engine=InnoDB; +insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0); +begin; +# Run statement which acquires X-lock on one of table's rows. +update t1 set c3=c3+1 where c2=3; +# +# Switching to connection 'con37346'. +# The below ALTER TABLE statement should wait till transaction +# in connection 'default' is complete and then succeed. +# It should not deadlock or fail with ER_LOCK_DEADLOCK error. +# Sending: +alter table t1 add column c4 int;; +# +# Switching to connection 'default'. +# Wait until the above ALTER TABLE gets blocked because this +# connection holds SW metadata lock on table to be altered. +# The below statement should succeed. It should not +# deadlock or end with ER_LOCK_DEADLOCK error. +update t1 set c3=c3+1 where c2=4; +# Unblock ALTER TABLE by committing transaction. +commit; +# +# Switching to connection 'con37346'. +# Reaping ALTER TABLE. +# +# Switching to connection 'default'. +drop table t1; +# +# Bug #42147 Concurrent DML and LOCK TABLE ... READ for InnoDB +# table cause warnings in errlog +# +# +# Note that this test for now relies on a global suppression of +# the warning "Found lock of type 6 that is write and read locked" +# This suppression rule can be removed once Bug#42147 is properly +# fixed. See bug page for more info. +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (i INT) engine= innodb; +# Connection 2 +# Get user-level lock +SELECT get_lock('bug42147_lock', 60); +get_lock('bug42147_lock', 60) +1 +# Connection 1 +INSERT INTO t1 SELECT get_lock('bug42147_lock', 60); +# Connection 2 +LOCK TABLES t1 READ; +SELECT release_lock('bug42147_lock'); +release_lock('bug42147_lock') +1 +# Connection 1 +# Connection 2 +UNLOCK TABLES; +# Connection 1 +DROP TABLE t1; diff --git a/mysql-test/r/innodb_mysql_sync.result b/mysql-test/r/innodb_mysql_sync.result new file mode 100644 index 00000000000..039d8b74c07 --- /dev/null +++ b/mysql-test/r/innodb_mysql_sync.result @@ -0,0 +1,26 @@ +# +# Bug 42074 concurrent optimize table and +# alter table = Assertion failed: thd->is_error() +# +DROP TABLE IF EXISTS t1; +# Create InnoDB table +CREATE TABLE t1 (id INT) engine=innodb; +# Connection 1 +# Start optimizing table +SET DEBUG_SYNC='ha_admin_try_alter SIGNAL optimize_started WAIT_FOR table_altered'; +OPTIMIZE TABLE t1; +# Connection 2 +# Change table to engine=memory +SET DEBUG_SYNC='now WAIT_FOR optimize_started'; +ALTER TABLE t1 engine=memory; +SET DEBUG_SYNC='now SIGNAL table_altered'; +# Connection 1 +# Complete optimization +Table Op Msg_type Msg_text +test.t1 optimize note Table does not support optimize, doing recreate + analyze instead +test.t1 optimize error Got error -1 from storage engine +test.t1 optimize status Operation failed +Warnings: +Error 1030 Got error -1 from storage engine +DROP TABLE t1; +SET DEBUG_SYNC='RESET'; diff --git a/mysql-test/r/kill.result b/mysql-test/r/kill.result index 8b6830d4798..1f4f4bb32eb 100644 --- a/mysql-test/r/kill.result +++ b/mysql-test/r/kill.result @@ -138,4 +138,107 @@ KILL CONNECTION_ID(); # of close of the connection socket SELECT 1; Got one of the listed errors +# +# Additional test for WL#3726 "DDL locking for all metadata objects" +# Check that DDL and DML statements waiting for metadata locks can +# be killed. Note that we don't cover all situations here since it +# can be tricky to write test case for some of them (e.g. REPAIR or +# ALTER and other statements under LOCK TABLES). +# +drop tables if exists t1, t2, t3; +create table t1 (i int primary key); +# Test for RENAME TABLE +# Switching to connection 'blocker' +lock table t1 read; +# Switching to connection 'ddl' +rename table t1 to t2; +# Switching to connection 'default' +kill query ID; +# Switching to connection 'ddl' +ERROR 70100: Query execution was interrupted +# Test for DROP TABLE +drop table t1; +# Switching to connection 'default' +kill query ID; +# Switching to connection 'ddl' +ERROR 70100: Query execution was interrupted +# Test for CREATE TRIGGER +create trigger t1_bi before insert on t1 for each row set @a:=1; +# Switching to connection 'default' +kill query ID; +# Switching to connection 'ddl' +ERROR 70100: Query execution was interrupted +# +# Tests for various kinds of ALTER TABLE +# +# Full-blown ALTER which should copy table +alter table t1 add column j int; +# Switching to connection 'default' +kill query ID; +# Switching to connection 'ddl' +ERROR 70100: Query execution was interrupted +# Two kinds of simple ALTER +alter table t1 rename to t2; +# Switching to connection 'default' +kill query ID; +# Switching to connection 'ddl' +ERROR 70100: Query execution was interrupted +alter table t1 disable keys; +# Switching to connection 'default' +kill query ID; +# Switching to connection 'ddl' +ERROR 70100: Query execution was interrupted +# Fast ALTER +alter table t1 alter column i set default 100; +# Switching to connection 'default' +kill query ID; +# Switching to connection 'ddl' +ERROR 70100: Query execution was interrupted +# Special case which is triggered only for MERGE tables. +# Switching to connection 'blocker' +unlock tables; +create table t2 (i int primary key) engine=merge union=(t1); +lock tables t2 read; +# Switching to connection 'ddl' +alter table t2 alter column i set default 100; +# Switching to connection 'default' +kill query ID; +# Switching to connection 'ddl' +ERROR 70100: Query execution was interrupted +# Test for DML waiting for meta-data lock +# Switching to connection 'blocker' +unlock tables; +drop table t2; +create table t2 (k int); +lock tables t1 read; +# Switching to connection 'ddl' +rename tables t1 to t3, t2 to t1; +# Switching to connection 'dml' +insert into t2 values (1); +# Switching to connection 'default' +kill query ID2; +# Switching to connection 'dml' +ERROR 70100: Query execution was interrupted +# Switching to connection 'blocker' +unlock tables; +# Switching to connection 'ddl' +# Test for DML waiting for tables to be flushed +# Switching to connection 'blocker' +lock tables t1 read; +# Switching to connection 'ddl' +# Let us mark locked table t1 as old +flush tables; +# Switching to connection 'dml' +select * from t1; +# Switching to connection 'default' +kill query ID2; +# Switching to connection 'dml' +ERROR 70100: Query execution was interrupted +# Switching to connection 'blocker' +unlock tables; +# Switching to connection 'ddl' +# Cleanup. +# Switching to connection 'default' +drop table t3; +drop table t1; set @@global.concurrent_insert= @old_concurrent_insert; diff --git a/mysql-test/r/lock.result b/mysql-test/r/lock.result index 1f8f6aa04ae..c1e1ccb5bce 100644 --- a/mysql-test/r/lock.result +++ b/mysql-test/r/lock.result @@ -1,4 +1,4 @@ -drop table if exists t1,t2; +drop table if exists t1,t2,t3; CREATE TABLE t1 ( `id` int(11) NOT NULL default '0', `id2` int(11) NOT NULL default '0', `id3` int(11) NOT NULL default '0', `dummy1` char(30) default NULL, PRIMARY KEY (`id`,`id2`), KEY `index_id3` (`id3`)) ENGINE=MyISAM; insert into t1 (id,id2) values (1,1),(1,2),(1,3); LOCK TABLE t1 WRITE; @@ -41,7 +41,7 @@ lock tables t1 write; check table t2; Table Op Msg_type Msg_text test.t2 check Error Table 't2' was not locked with LOCK TABLES -test.t2 check error Corrupt +test.t2 check status Operation failed insert into t1 select index1,nr from t1; ERROR HY000: Table 't1' was not locked with LOCK TABLES unlock tables; @@ -128,13 +128,14 @@ select * from v_bug5719; 1 1 drop view v_bug5719; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction sic: did not left LOCK TABLES mode automatically select * from t1; ERROR HY000: Table 't1' was not locked with LOCK TABLES unlock tables; -create view v_bug5719 as select * from t1; +create or replace view v_bug5719 as select * from t1; lock tables v_bug5719 write; select * from v_bug5719; a @@ -150,6 +151,12 @@ select * from t2; a select * from t3; ERROR HY000: Table 't3' was not locked with LOCK TABLES +Dropping of implicitly locked table is disallowed. +drop table t1; +ERROR HY000: Table 't1' was locked with a READ lock and can't be updated +unlock tables; +Now let us also lock table explicitly and drop it. +lock tables t1 write, v_bug5719 write; drop table t1; sic: left LOCK TABLES mode @@ -200,3 +207,266 @@ ERROR HY000: Table 't2' was locked with a READ lock and can't be updated UNLOCK TABLES; DROP TABLE t1,t2; End of 5.1 tests. +# +# Ensure that FLUSH TABLES doesn't substitute a base locked table +# with a temporary one. +# +drop table if exists t1, t2; +create table t1 (a int); +create table t2 (a int); +lock table t1 write, t2 write; +create temporary table t1 (a int); +flush table t1; +drop temporary table t1; +select * from t1; +a +unlock tables; +drop table t1, t2; +# +# Ensure that REPAIR .. USE_FRM works under LOCK TABLES. +# +drop table if exists t1, t2; +create table t1 (a int); +create table t2 (a int); +lock table t1 write, t2 write; +repair table t1 use_frm; +Table Op Msg_type Msg_text +test.t1 repair status OK +repair table t1 use_frm; +Table Op Msg_type Msg_text +test.t1 repair status OK +select * from t1; +a +select * from t2; +a +repair table t2 use_frm; +Table Op Msg_type Msg_text +test.t2 repair status OK +repair table t2 use_frm; +Table Op Msg_type Msg_text +test.t2 repair status OK +select * from t1; +a +unlock tables; +drop table t1, t2; +# +# Ensure that mi_copy_status is called for two instances +# of the same table when it is reopened after a flush. +# +drop table if exists t1; +drop view if exists v1; +create table t1 (c1 int); +create view v1 as select * from t1; +lock tables t1 write, v1 write; +flush table t1; +insert into t1 values (33); +flush table t1; +select * from t1; +c1 +33 +unlock tables; +drop table t1; +drop view v1; +# +# WL#4284: Transactional DDL locking +# +drop table if exists t1; +create table t1 (a int); +set autocommit= 0; +insert into t1 values (1); +lock table t1 write; +# Disconnect +# Ensure that metadata locks will be released if there is an open +# transaction (autocommit=off) in conjunction with lock tables. +drop table t1; +# Same problem but now for BEGIN +drop table if exists t1; +create table t1 (a int); +begin; +insert into t1 values (1); +# Disconnect +# Ensure that metadata locks held by the transaction are released. +drop table t1; +# +# Coverage for situations when we try to execute DDL on tables +# which are locked by LOCK TABLES only implicitly. +# +drop tables if exists t1, t2; +drop view if exists v1; +drop function if exists f1; +create table t1 (i int); +create table t2 (j int); +# +# Try to perform DDL on table which is locked through view. +create view v1 as select * from t2; +lock tables t1 write, v1 write; +flush table t2; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +drop table t2; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +alter table t2 add column k int; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +create trigger t2_bi before insert on t2 for each row set @a:=1; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +# Repair produces error as part of its result set. +repair table t2; +Table Op Msg_type Msg_text +test.t2 repair Error Table 't2' was locked with a READ lock and can't be updated +test.t2 repair status Operation failed +unlock tables; +drop view v1; +# +# Now, try DDL on table which is locked through routine. +create function f1 () returns int +begin +insert into t2 values (1); +return 0; +end| +create view v1 as select f1() from t1; +lock tables v1 read; +flush table t2; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +drop table t2; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +alter table t2 add column k int; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +create trigger t2_bi before insert on t2 for each row set @a:=1; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +# Repair produces error as part of its result set. +repair table t2; +Table Op Msg_type Msg_text +test.t2 repair Error Table 't2' was locked with a READ lock and can't be updated +test.t2 repair status Operation failed +unlock tables; +drop view v1; +drop function f1; +# +# Finally, try DDL on table which is locked thanks to trigger. +create trigger t1_ai after insert on t1 for each row insert into t2 values (1); +lock tables t1 write; +flush table t2; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +drop table t2; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +alter table t2 add column k int; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +create trigger t2_bi before insert on t2 for each row set @a:=1; +ERROR HY000: Table 't2' was locked with a READ lock and can't be updated +# Repair produces error as part of its result set. +repair table t2; +Table Op Msg_type Msg_text +test.t2 repair Error Table 't2' was locked with a READ lock and can't be updated +test.t2 repair status Operation failed +unlock tables; +drop trigger t1_ai; +drop tables t1, t2; +# +# Bug#45035 " Altering table under LOCK TABLES results in +# "Error 1213 Deadlock found..." +# +# When reopening tables under LOCK TABLES after ALTER TABLE, +# 6.0 used to be taking thr_lock locks one by one, and +# that would lead to a lock conflict. +# Check that taking all locks at once works. +# +drop table if exists t1; +create table t1 (i int); +lock tables t1 write, t1 as a read, t1 as b read; +alter table t1 add column j int; +unlock tables; +drop table t1; +create temporary table t1 (i int); +# +# This is just for test coverage purposes, +# when this is allowed, remove the --error. +# +lock tables t1 write, t1 as a read, t1 as b read; +ERROR HY000: Can't reopen table: 't1' +alter table t1 add column j int; +unlock tables; +drop table t1; +# +# Separate case for partitioned tables is important +# because each partition has an own thr_lock object. +# +create table t1 (i int) partition by list (i) +(partition p0 values in (1), +partition p1 values in (2,3), +partition p2 values in (4,5)); +lock tables t1 write, t1 as a read, t1 as b read; +alter table t1 add column j int; +unlock tables; +drop table t1; +# +# Bug #43272 HANDLER SQL command does not work under LOCK TABLES +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (a INT); +LOCK TABLE t1 WRITE; +# HANDLER commands are not allowed in LOCK TABLES mode +HANDLER t1 OPEN; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +HANDLER t1 READ FIRST; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +HANDLER t1 CLOSE; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +UNLOCK TABLES; +DROP TABLE t1; +# +# Bug#45066 FLUSH TABLES WITH READ LOCK deadlocks against +# LOCK TABLE +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1(a INT); +LOCK TABLE t1 READ; +FLUSH TABLES; +ERROR HY000: Table 't1' was locked with a READ lock and can't be updated +LOCK TABLE t1 WRITE; +FLUSH TABLES; +# +# If you allow the next combination, you reintroduce bug Bug#45066 +# +LOCK TABLE t1 READ; +FLUSH TABLES WITH READ LOCK; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +LOCK TABLE t1 WRITE; +FLUSH TABLES WITH READ LOCK; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +UNLOCK TABLES; +DROP TABLE t1; +# +# Simplified test for bug #48538 "Assertion in thr_lock() on LOAD DATA +# CONCURRENT INFILE". +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (f1 INT, f2 INT) ENGINE = MEMORY; +CREATE TRIGGER t1_ai AFTER INSERT ON t1 FOR EACH ROW +UPDATE LOW_PRIORITY t1 SET f2 = 7; +# Statement below should fail with ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +# error instead of failing on assertion in table-level locking subsystem. +INSERT INTO t1(f1) VALUES(0); +ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +DROP TABLE t1; +# +# Bug#43685 Lock table affects other non-related tables +# +DROP TABLE IF EXISTS t1, t2; +CREATE TABLE t1 (id INT); +CREATE TABLE t2 (id INT); +# Connection default +LOCK TABLE t1 WRITE; +ANALYZE TABLE t1; +Table Op Msg_type Msg_text +test.t1 analyze status Table is already up to date +# Connection con2 +LOCK TABLE t2 WRITE; +# This used to hang until the first connection +# unlocked t1. +FLUSH TABLE t2; +UNLOCK TABLES; +# Connection default +UNLOCK TABLES; +DROP TABLE t1, t2; +# +# End of 6.0 tests. +# diff --git a/mysql-test/r/lock_multi.result b/mysql-test/r/lock_multi.result index 3f1165fd069..4b08c175ee2 100644 --- a/mysql-test/r/lock_multi.result +++ b/mysql-test/r/lock_multi.result @@ -1,21 +1,39 @@ drop table if exists t1,t2; create table t1(n int); insert into t1 values (1); -lock tables t1 write; +select get_lock("mysqltest_lock", 100); +get_lock("mysqltest_lock", 100) +1 +update t1 set n = 2 and get_lock('mysqltest_lock', 100); update low_priority t1 set n = 4; select n from t1; -unlock tables; +select release_lock("mysqltest_lock"); +release_lock("mysqltest_lock") +1 +select release_lock("mysqltest_lock"); +release_lock("mysqltest_lock") +1 n 4 drop table t1; create table t1(n int); insert into t1 values (1); -lock tables t1 read; +select get_lock("mysqltest_lock", 100); +get_lock("mysqltest_lock", 100) +1 +select n from t1 where get_lock('mysqltest_lock', 100); update low_priority t1 set n = 4; select n from t1; n 1 -unlock tables; +select release_lock("mysqltest_lock"); +release_lock("mysqltest_lock") +1 +n +1 +select release_lock("mysqltest_lock"); +release_lock("mysqltest_lock") +1 drop table t1; create table t1 (a int, b int); create table t2 (c int, d int); @@ -35,6 +53,7 @@ create table t2 (a int); lock table t1 write, t2 write; insert t1 select * from t2; drop table t2; +unlock tables; ERROR 42S02: Table 'test.t2' doesn't exist drop table t1; create table t1 (a int); @@ -42,6 +61,7 @@ create table t2 (a int); lock table t1 write, t2 write, t1 as t1_2 write, t2 as t2_2 write; insert t1 select * from t2; drop table t2; +unlock tables; ERROR 42S02: Table 'test.t2' doesn't exist drop table t1; End of 4.1 tests @@ -72,9 +92,10 @@ CREATE TABLE t1 (c1 int); LOCK TABLE t1 WRITE; FLUSH TABLES WITH READ LOCK; CREATE TABLE t2 (c1 int); +ERROR HY000: Table 't2' was not locked with LOCK TABLES UNLOCK TABLES; UNLOCK TABLES; -DROP TABLE t1, t2; +DROP TABLE t1; CREATE TABLE t1 (c1 int); LOCK TABLE t1 WRITE; FLUSH TABLES WITH READ LOCK; @@ -203,7 +224,7 @@ drop table if exists t1,t2; create table t1 (a int); flush status; lock tables t1 read; -insert into t1 values(1);; +insert into t1 values(1); unlock tables; drop table t1; select @tlwa < @tlwb; @@ -219,3 +240,65 @@ flush tables with read lock;; connection: default flush tables; drop table t1; +# +# Test for bug #46272 "MySQL 5.4.4, new MDL: unnecessary deadlock". +# +drop table if exists t1; +create table t1 (c1 int primary key, c2 int, c3 int); +insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0); +begin; +update t1 set c3=c3+1 where c2=3; +# +# Switching to connection 'con46272'. +# The below ALTER TABLE statement should wait till transaction +# in connection 'default' is complete and then succeed. +# It should not deadlock or fail with ER_LOCK_DEADLOCK error. +# Sending: +alter table t1 add column c4 int;; +# +# Switching to connection 'default'. +# Wait until the above ALTER TABLE gets blocked because this +# connection holds SW metadata lock on table to be altered. +# The below statement should succeed. It should not +# deadlock or end with ER_LOCK_DEADLOCK error. +update t1 set c3=c3+1 where c2=4; +# Unblock ALTER TABLE by committing transaction. +commit; +# +# Switching to connection 'con46272'. +# Reaping ALTER TABLE. +# +# Switching to connection 'default'. +drop table t1; +# +# Bug#47249 assert in MDL_global_lock::is_lock_type_compatible +# +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS v1; +# +# Test 1: LOCK TABLES v1 WRITE, t1 READ; +# +# Thanks to the fact that we no longer allow DDL on tables +# which are locked for write implicitly, the exact scenario +# in which assert was failing is no longer repeatable. +CREATE TABLE t1 ( f1 integer ); +CREATE VIEW v1 AS SELECT f1 FROM t1 ; +LOCK TABLES v1 WRITE, t1 READ; +FLUSH TABLE t1; +ERROR HY000: Table 't1' was locked with a READ lock and can't be updated +UNLOCK TABLES; +DROP TABLE t1; +DROP VIEW v1; +# +# Test 2: LOCK TABLES t1 WRITE, v1 READ; +# +CREATE TABLE t1 ( f1 integer ); +CREATE VIEW v1 AS SELECT f1 FROM t1 ; +# Connection 2 +LOCK TABLES t1 WRITE, v1 READ; +FLUSH TABLE t1; +# Connection 1 +LOCK TABLES t1 WRITE; +FLUSH TABLE t1; +DROP TABLE t1; +DROP VIEW v1; diff --git a/mysql-test/r/lock_sync.result b/mysql-test/r/lock_sync.result index fc4e8c850f6..0b57b38f5ec 100644 --- a/mysql-test/r/lock_sync.result +++ b/mysql-test/r/lock_sync.result @@ -6,6 +6,7 @@ # statements which tried to acquire stronger write lock (TL_WRITE, # TL_WRITE_ALLOW_READ) on this table might have led to deadlock. drop table if exists t1; +drop view if exists v1; # Create auxiliary connections used through the test. # Reset DEBUG_SYNC facility before using it. set debug_sync= 'RESET'; @@ -14,6 +15,9 @@ set debug_sync= 'RESET'; set @old_general_log = @@global.general_log; set @@global.general_log= OFF; create table t1 (i int) engine=InnoDB; +# We have to use view in order to make LOCK TABLES avoid +# acquiring SNRW metadata lock on table. +create view v1 as select * from t1; insert into t1 values (1); # Prepare user lock which will be used for resuming execution of # the first statement after it acquires TL_WRITE_ALLOW_WRITE lock. @@ -36,7 +40,7 @@ select count(*) > 0 from t1 as a, t1 as b for update;; # acquiring lock for the the first instance of 't1'. set debug_sync= 'now WAIT_FOR parked'; # Send LOCK TABLE statement which will try to get TL_WRITE lock on 't1': -lock table t1 write;; +lock table v1 write;; # Switch to connection 'default'. # Wait until this LOCK TABLES statement starts waiting for table lock. # Allow SELECT ... FOR UPDATE to resume. @@ -63,4 +67,5 @@ unlock tables; # Do clean-up. set debug_sync= 'RESET'; set @@global.general_log= @old_general_log; +drop view v1; drop table t1; diff --git a/mysql-test/r/lowercase_table2.result b/mysql-test/r/lowercase_table2.result index cf87fd1b5a4..b621a466a29 100644 --- a/mysql-test/r/lowercase_table2.result +++ b/mysql-test/r/lowercase_table2.result @@ -226,10 +226,9 @@ drop table t_bug44738_UPPERCASE; create table t_bug44738_UPPERCASE (i int); drop table t_bug44738_UPPERCASE; # Finally, let us check that another issue which was exposed by -# the original test case is solved. I.e. that fuse in CREATE TABLE -# which ensures that table is not created if there is an entry for -# it in TDC even though it was removed from disk uses normalized -# version of the table name. +# the original test case is solved. I.e. that the table is not +# created if there is an entry for it in TDC even though it was +# removed from disk. create table t_bug44738_UPPERCASE (i int) engine = myisam; # Load table definition in TDC. select table_schema, table_name, table_comment from information_schema.tables @@ -237,10 +236,13 @@ where table_schema = 'test' and table_name like 't_bug44738_%'; table_schema table_name table_comment test t_bug44738_UPPERCASE # Simulate manual removal of the table. -# After manual removal of table still there should be an entry for table -# in TDC so attempt to create table with the same name should fail. +# Check that still there is an entry for table in TDC. +show open tables like 't_bug44738_%'; +Database Table In_use Name_locked +test t_bug44738_uppercase 0 0 +# So attempt to create table with the same name should fail. create table t_bug44738_UPPERCASE (i int); -ERROR 42S01: Table 't_bug44738_uppercase' already exists +ERROR HY000: Can't find file: 't_bug44738_uppercase' (errno: 2) # And should succeed after FLUSH TABLES. flush tables; create table t_bug44738_UPPERCASE (i int); diff --git a/mysql-test/r/mdl_sync.result b/mysql-test/r/mdl_sync.result new file mode 100644 index 00000000000..8d8672377f0 --- /dev/null +++ b/mysql-test/r/mdl_sync.result @@ -0,0 +1,2246 @@ +SET DEBUG_SYNC= 'RESET'; +drop table if exists t1,t2,t3; +create table t1 (i int); +create table t2 (i int); +connection: default +lock tables t2 read; +connection: con1 +set debug_sync='mdl_upgrade_shared_lock_to_exclusive SIGNAL parked WAIT_FOR go'; +alter table t1 rename t3; +connection: default +set debug_sync= 'now WAIT_FOR parked'; +connection: con2 +set debug_sync='mdl_acquire_lock_wait SIGNAL go'; +drop table t1,t2; +connection: con1 +connection: default +unlock tables; +connection: con2 +ERROR 42S02: Unknown table 't1' +drop table t3; +SET DEBUG_SYNC= 'RESET'; +# +# Basic test coverage for type-of-operation aware metadata locks. +# +drop table if exists t1, t2, t3; +set debug_sync= 'RESET'; +create table t1 (c1 int); +# +# A) First let us check compatibility rules between differend kinds of +# type-of-operation aware metadata locks. +# Of course, these rules are already covered by the tests scattered +# across the test suite. But it still makes sense to have one place +# which covers all of them. +# +# 1) Acquire S (simple shared) lock on the table (by using HANDLER): +# +handler t1 open; +# +# Switching to connection 'mdl_con1'. +# Check that S, SH, SR and SW locks are compatible with it. +handler t1 open t; +handler t close; +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +select count(*) from t1; +count(*) +0 +insert into t1 values (1), (1); +# Check that SNW lock is compatible with it. To do this use ALTER TABLE +# which will fail after opening the table and thus obtaining SNW metadata +# lock. +alter table t1 add primary key (c1); +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# Check that SNRW lock is compatible with S lock. +lock table t1 write; +insert into t1 values (1); +unlock tables; +# Check that X lock is incompatible with S lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con2'. +# Check that the above RENAME is blocked because of S lock. +# +# Switching to connection 'default'. +# Unblock RENAME TABLE. +handler t1 close; +# +# Switching to connection 'mdl_con1'. +# Reaping RENAME TABLE. +# Restore the original state of the things. +rename table t2 to t1; +# +# Switching to connection 'default'. +handler t1 open; +# +# Switching to connection 'mdl_con1'. +# Check that upgrade from SNW to X is blocked by presence of S lock. +# Sending: +alter table t1 add column c2 int;; +# +# Switching to connection 'mdl_con2'. +# Check that the above ALTER TABLE is blocked because of S lock. +# +# Switching to connection 'default'. +# Unblock ALTER TABLE. +handler t1 close; +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER TABLE. +# Restore the original state of the things. +alter table t1 drop column c2; +# +# Switching to connection 'default'. +handler t1 open; +# +# Switching to connection 'mdl_con1'. +# Check that upgrade from SNRW to X is blocked by presence of S lock. +lock table t1 write; +# Sending: +alter table t1 add column c2 int;; +# +# Switching to connection 'mdl_con2'. +# Check that the above upgrade of SNRW to X in ALTER TABLE is blocked +# because of S lock. +# +# Switching to connection 'default'. +# Unblock ALTER TABLE. +handler t1 close; +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER TABLE. +# Restore the original state of the things. +alter table t1 drop column c2; +unlock tables; +# +# Switching to connection 'default'. +# +# 2) Acquire SH (shared high-priority) lock on the table. +# We have to involve DEBUG_SYNC facility for this as usually +# such kind of locks are short-lived. +# +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t1';; +# +# Switching to connection 'mdl_con1'. +set debug_sync= 'now WAIT_FOR locked'; +# Check that S, SH, SR and SW locks are compatible with it. +handler t1 open; +handler t1 close; +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +select count(*) from t1; +count(*) +3 +insert into t1 values (1); +# Check that SNW lock is compatible with it. To do this use ALTER TABLE +# which will fail after opening the table and thus obtaining SNW metadata +# lock. +alter table t1 add primary key (c1); +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# Check that SNRW lock is compatible with SH lock. +lock table t1 write; +delete from t1 limit 1; +unlock tables; +# Check that X lock is incompatible with SH lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con2'. +# Check that the above RENAME is blocked because of SH lock. +# Unblock RENAME TABLE. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'default'. +# Reaping SELECT ... FROM I_S. +table_name table_type auto_increment table_comment +t1 BASE TABLE NULL +# +# Switching to connection 'mdl_con1'. +# Reaping RENAME TABLE. +# Restore the original state of the things. +rename table t2 to t1; +# +# Switching to connection 'default'. +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t1';; +# +# Switching to connection 'mdl_con1'. +set debug_sync= 'now WAIT_FOR locked'; +# Check that upgrade from SNW to X is blocked by presence of SH lock. +# Sending: +alter table t1 add column c2 int;; +# +# Switching to connection 'mdl_con2'. +# Check that the above ALTER TABLE is blocked because of SH lock. +# Unblock RENAME TABLE. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'default'. +# Reaping SELECT ... FROM I_S. +table_name table_type auto_increment table_comment +t1 BASE TABLE NULL +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER TABLE. +# Restore the original state of the things. +alter table t1 drop column c2; +# +# Switching to connection 'default'. +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t1';; +# +# Switching to connection 'mdl_con1'. +set debug_sync= 'now WAIT_FOR locked'; +# Check that upgrade from SNRW to X is blocked by presence of S lock. +lock table t1 write; +# Sending: +alter table t1 add column c2 int;; +# +# Switching to connection 'mdl_con2'. +# Check that the above upgrade of SNRW to X in ALTER TABLE is blocked +# because of S lock. +# Unblock RENAME TABLE. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'default'. +# Reaping SELECT ... FROM I_S. +table_name table_type auto_increment table_comment +t1 BASE TABLE NULL +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER TABLE. +# Restore the original state of the things. +alter table t1 drop column c2; +unlock tables; +# +# Switching to connection 'default'. +# +# +# 3) Acquire SR lock on the table. +# +# +begin; +select count(*) from t1; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Check that S, SH, SR and SW locks are compatible with it. +handler t1 open; +handler t1 close; +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +select count(*) from t1; +count(*) +3 +insert into t1 values (1); +# Check that SNW lock is compatible with it. To do this use ALTER TABLE +# which will fail after opening the table and thus obtaining SNW metadata +# lock. +alter table t1 add primary key (c1); +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# Check that SNRW lock is not compatible with SR lock. +# Sending: +lock table t1 write;; +# +# Switching to connection 'default'. +# Check that the above LOCK TABLES is blocked because of SR lock. +# Unblock LOCK TABLES. +commit; +# +# Switching to connection 'mdl_con1'. +# Reaping LOCK TABLES. +delete from t1 limit 1; +unlock tables; +# +# Switching to connection 'default'. +begin; +select count(*) from t1; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Check that X lock is incompatible with SR lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con2'. +# Check that the above RENAME is blocked because of SR lock. +# +# Switching to connection 'default'. +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reaping RENAME TABLE. +# Restore the original state of the things. +rename table t2 to t1; +# +# Switching to connection 'default'. +begin; +select count(*) from t1; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Check that upgrade from SNW to X is blocked by presence of SR lock. +# Sending: +alter table t1 add column c2 int;; +# +# Switching to connection 'mdl_con2'. +# Check that the above ALTER TABLE is blocked because of SR lock. +# +# Switching to connection 'default'. +# Unblock ALTER TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER TABLE. +# Restore the original state of the things. +alter table t1 drop column c2; +# +# There is no need to check that upgrade from SNRW to X is blocked +# by presence of SR lock because SNRW is incompatible with SR anyway. +# +# +# Switching to connection 'default'. +# +# +# 4) Acquire SW lock on the table. +# +# +begin; +insert into t1 values (1); +# +# Switching to connection 'mdl_con1'. +# Check that S, SH, SR and SW locks are compatible with it. +handler t1 open; +handler t1 close; +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +select count(*) from t1; +count(*) +4 +insert into t1 values (1); +# Check that SNW lock is not compatible with SW lock. +# Again we use ALTER TABLE which fails after opening +# the table to avoid upgrade of SNW -> X. +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'default'. +# Check that the above ALTER TABLE is blocked because of SW lock. +# Unblock ALTER TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'default'. +begin; +insert into t1 values (1); +# +# Switching to connection 'mdl_con1'. +# Check that SNRW lock is not compatible with SW lock. +# Sending: +lock table t1 write;; +# +# Switching to connection 'default'. +# Check that the above LOCK TABLES is blocked because of SW lock. +# Unblock LOCK TABLES. +commit; +# +# Switching to connection 'mdl_con1'. +# Reaping LOCK TABLES. +delete from t1 limit 2; +unlock tables; +# +# Switching to connection 'default'. +begin; +insert into t1 values (1); +# +# Switching to connection 'mdl_con1'. +# Check that X lock is incompatible with SW lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con2'. +# Check that the above RENAME is blocked because of SW lock. +# +# Switching to connection 'default'. +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reaping RENAME TABLE. +# Restore the original state of the things. +rename table t2 to t1; +# +# There is no need to check that upgrade from SNW/SNRW to X is +# blocked by presence of SW lock because SNW/SNRW is incompatible +# with SW anyway. +# +# +# Switching to connection 'default'. +# +# +# 5) Acquire SNW lock on the table. We have to use DEBUG_SYNC for +# this, to prevent SNW from being immediately upgraded to X. +# +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'mdl_con1'. +set debug_sync= 'now WAIT_FOR locked'; +# Check that S, SH and SR locks are compatible with it. +handler t1 open; +handler t1 close; +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +select count(*) from t1; +count(*) +5 +# Check that SW lock is incompatible with SNW lock. +# Sending: +delete from t1 limit 2;; +# +# Switching to connection 'mdl_con2'. +# Check that the above DELETE is blocked because of SNW lock. +# Unblock ALTER and thus DELETE. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'default'. +# Reaping ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'mdl_con1'. +# Reaping DELETE. +# +# Switching to connection 'default'. +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'mdl_con1'. +set debug_sync= 'now WAIT_FOR locked'; +# Check that SNW lock is incompatible with SNW lock. +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'mdl_con2'. +# Check that the above ALTER is blocked because of SNW lock. +# Unblock ALTERs. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'default'. +# Reaping first ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'mdl_con1'. +# Reaping another ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'default'. +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'mdl_con1'. +set debug_sync= 'now WAIT_FOR locked'; +# Check that SNRW lock is incompatible with SNW lock. +# Sending: +lock table t1 write;; +# +# Switching to connection 'mdl_con2'. +# Check that the above LOCK TABLES is blocked because of SNW lock. +# Unblock ALTER and thus LOCK TABLES. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'default'. +# Reaping ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'mdl_con1'. +# Reaping LOCK TABLES +insert into t1 values (1); +unlock tables; +# +# Switching to connection 'default'. +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'mdl_con1'. +set debug_sync= 'now WAIT_FOR locked'; +# Check that X lock is incompatible with SNW lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con2'. +# Check that the above RENAME is blocked because of SNW lock. +# Unblock ALTER and thus RENAME TABLE. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'default'. +# Reaping ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'mdl_con1'. +# Reaping RENAME TABLE +# Revert back to original state of things. +rename table t2 to t1; +# +# There is no need to check that upgrade from SNW/SNRW to X is +# blocked by presence of another SNW lock because SNW/SNRW is +# incompatible with SNW anyway. +# +# Switching to connection 'default'. +# +# +# 6) Acquire SNRW lock on the table. +# +# +lock table t1 write; +# +# Switching to connection 'mdl_con1'. +# Check that S and SH locks are compatible with it. +handler t1 open; +handler t1 close; +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +# Check that SR lock is incompatible with SNRW lock. +# Sending: +select count(*) from t1;; +# +# Switching to connection 'default'. +# Check that the above SELECT is blocked because of SNRW lock. +# Unblock SELECT. +unlock tables; +# +# Switching to connection 'mdl_con1'. +# Reaping SELECT. +count(*) +4 +# +# Switching to connection 'default'. +lock table t1 write; +# +# Switching to connection 'mdl_con1'. +# Check that SW lock is incompatible with SNRW lock. +# Sending: +delete from t1 limit 1;; +# +# Switching to connection 'default'. +# Check that the above DELETE is blocked because of SNRW lock. +# Unblock DELETE. +unlock tables; +# +# Switching to connection 'mdl_con1'. +# Reaping DELETE. +# +# Switching to connection 'default'. +lock table t1 write; +# +# Switching to connection 'mdl_con1'. +# Check that SNW lock is incompatible with SNRW lock. +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'default'. +# Check that the above ALTER is blocked because of UNWR lock. +# Unblock ALTER. +unlock tables; +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'default'. +lock table t1 write; +# +# Switching to connection 'mdl_con1'. +# Check that SNRW lock is incompatible with SNRW lock. +# Sending: +lock table t1 write;; +# +# Switching to connection 'default'. +# Check that the above LOCK TABLES is blocked because of SNRW lock. +# Unblock waiting LOCK TABLES. +unlock tables; +# +# Switching to connection 'mdl_con1'. +# Reaping LOCK TABLES +insert into t1 values (1); +unlock tables; +# +# Switching to connection 'default'. +lock table t1 write; +# +# Switching to connection 'mdl_con1'. +# Check that X lock is incompatible with SNRW lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'default'. +# Check that the above RENAME is blocked because of SNRW lock. +# Unblock RENAME TABLE +unlock tables; +# +# Switching to connection 'mdl_con1'. +# Reaping RENAME TABLE +# Revert back to original state of things. +rename table t2 to t1; +# +# There is no need to check that upgrade from SNW/SNRW to X is +# blocked by presence of another SNRW lock because SNW/SNRW is +# incompatible with SNRW anyway. +# +# Switching to connection 'default'. +# +# +# 7) Now do the same round of tests for X lock. We use additional +# table to get long-lived lock of this type. +# +create table t2 (c1 int); +# +# Switching to connection 'mdl_con2'. +# Take a lock on t2, so RENAME TABLE t1 TO t2 will get blocked +# after acquiring X lock on t1. +lock tables t2 read; +# +# Switching to connection 'default'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME has acquired X lock on t1 and is waiting for t2. +# Check that S lock in incompatible with X lock. +# Sending: +handler t1 open;; +# +# Switching to connection 'mdl_con2'. +# Check that the above HANDLER statement is blocked because of X lock. +# Unblock RENAME TABLE +unlock tables; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping HANDLER. +handler t1 close; +# +# Switching to connection 'mdl_con2'. +# Prepare for blocking RENAME TABLE. +lock tables t2 read; +# +# Switching to connection 'default'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME has acquired X lock on t1 and is waiting for t2. +# Check that SH lock in incompatible with X lock. +# Sending: +select column_name from information_schema.columns where table_schema='test' and table_name='t1';; +# +# Switching to connection 'mdl_con2'. +# Check that the above SELECT ... FROM I_S ... statement is blocked +# because of X lock. +# Unblock RENAME TABLE +unlock tables; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping SELECT ... FROM I_S. +column_name +c1 +# +# Switching to connection 'mdl_con2'. +# Prepare for blocking RENAME TABLE. +lock tables t2 read; +# +# Switching to connection 'default'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME has acquired X lock on t1 and is waiting for t2. +# Check that SR lock in incompatible with X lock. +# Sending: +select count(*) from t1;; +# +# Switching to connection 'mdl_con2'. +# Check that the above SELECT statement is blocked +# because of X lock. +# Unblock RENAME TABLE +unlock tables; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping SELECT. +count(*) +4 +# +# Switching to connection 'mdl_con2'. +# Prepare for blocking RENAME TABLE. +lock tables t2 read; +# +# Switching to connection 'default'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME has acquired X lock on t1 and is waiting for t2. +# Check that SW lock in incompatible with X lock. +# Sending: +delete from t1 limit 1;; +# +# Switching to connection 'mdl_con2'. +# Check that the above DELETE statement is blocked +# because of X lock. +# Unblock RENAME TABLE +unlock tables; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping DELETE. +# +# Switching to connection 'mdl_con2'. +# Prepare for blocking RENAME TABLE. +lock tables t2 read; +# +# Switching to connection 'default'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME has acquired X lock on t1 and is waiting for t2. +# Check that SNW lock is incompatible with X lock. +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'mdl_con2'. +# Check that the above ALTER statement is blocked +# because of X lock. +# Unblock RENAME TABLE +unlock tables; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'mdl_con2'. +# Prepare for blocking RENAME TABLE. +lock tables t2 read; +# +# Switching to connection 'default'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME has acquired X lock on t1 and is waiting for t2. +# Check that SNRW lock is incompatible with X lock. +# Sending: +lock table t1 write;; +# +# Switching to connection 'mdl_con2'. +# Check that the above LOCK TABLE statement is blocked +# because of X lock. +# Unblock RENAME TABLE +unlock tables; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping LOCK TABLE. +unlock tables; +# +# Switching to connection 'mdl_con2'. +# Prepare for blocking RENAME TABLE. +lock tables t2 read; +# +# Switching to connection 'default'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME has acquired X lock on t1 and is waiting for t2. +# Check that X lock is incompatible with X lock. +# Sending: +rename table t1 to t3;; +# +# Switching to connection 'mdl_con2'. +# Check that the above RENAME statement is blocked +# because of X lock. +# Unblock RENAME TABLE +unlock tables; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping RENAME. +rename table t3 to t1; +# +# B) Now let us test compatibility in cases when both locks +# are pending. I.e. let us test rules for priorities between +# different types of metadata locks. +# +# +# Switching to connection 'mdl_con2'. +# +# 1) Check compatibility for pending SNW lock. +# +# Acquire SW lock in order to create pending SNW lock later. +begin; +insert into t1 values (1); +# +# Switching to connection 'default'. +# Add pending SNW lock. +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'mdl_con1'. +# Check that ALTER TABLE is waiting with pending SNW lock. +# Check that S, SH and SR locks are compatible with pending SNW +handler t1 open t; +handler t close; +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +select count(*) from t1; +count(*) +4 +# Check that SW is incompatible with pending SNW +# Sending: +delete from t1 limit 1;; +# +# Switching to connection 'mdl_con2'. +# Check that the above DELETE is blocked because of pending SNW lock. +# Unblock ALTER TABLE. +commit; +# +# Switching to connection 'default'. +# Reaping ALTER. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'mdl_con1'. +# Reaping DELETE. +# +# We can't do similar check for SNW, SNRW and X locks because +# they will also be blocked by active SW lock. +# +# +# Switching to connection 'mdl_con2'. +# +# 2) Check compatibility for pending SNRW lock. +# +# Acquire SR lock in order to create pending SNRW lock. +begin; +select count(*) from t1; +count(*) +3 +# +# Switching to connection 'default'. +# Add pending SNRW lock. +# Sending: +lock table t1 write;; +# +# Switching to connection 'mdl_con1'. +# Check that LOCK TABLE is waiting with pending SNRW lock. +# Check that S and SH locks are compatible with pending SNRW +handler t1 open t; +handler t close; +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +# Check that SR is incompatible with pending SNRW +# Sending: +select count(*) from t1;; +# +# Switching to connection 'mdl_con2'. +# Check that the above SELECT is blocked because of pending SNRW lock. +# Unblock LOCK TABLE. +commit; +# +# Switching to connection 'default'. +# Reaping LOCK TABLE. +unlock tables; +# +# Switching to connection 'mdl_con1'. +# Reaping SELECT. +count(*) +3 +# Restore pending SNRW lock. +# +# Switching to connection 'mdl_con2'. +begin; +select count(*) from t1; +count(*) +3 +# +# Switching to connection 'default'. +# Sending: +lock table t1 write;; +# +# Switching to connection 'mdl_con1'. +# Check that LOCK TABLE is waiting with pending SNRW lock. +# Check that SW is incompatible with pending SNRW +# Sending: +insert into t1 values (1);; +# +# Switching to connection 'mdl_con2'. +# Check that the above INSERT is blocked because of pending SNRW lock. +# Unblock LOCK TABLE. +commit; +# +# Switching to connection 'default'. +# Reaping LOCK TABLE. +unlock tables; +# +# Switching to connection 'mdl_con1'. +# Reaping INSERT. +# Restore pending SNRW lock. +# +# Switching to connection 'mdl_con2'. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'default'. +# Sending: +lock table t1 write;; +# +# Switching to connection 'mdl_con1'. +# Check that LOCK TABLE is waiting with pending SNRW lock. +# Check that SNW is compatible with pending SNRW +# So ALTER TABLE statements are not starved by LOCK TABLEs. +alter table t1 add primary key (c1); +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'mdl_con2'. +# Unblock LOCK TABLE. +commit; +# +# Switching to connection 'default'. +# Reaping LOCK TABLE. +unlock tables; +# +# We can't do similar check for SNRW and X locks because +# they will also be blocked by active SR lock. +# +# +# Switching to connection 'mdl_con2'. +# +# 3) Check compatibility for pending X lock. +# +# Acquire SR lock in order to create pending X lock. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'default'. +# Add pending X lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME TABLE is waiting with pending X lock. +# Check that SH locks are compatible with pending X +select column_name from information_schema.columns where +table_schema='test' and table_name='t1'; +column_name +c1 +# Check that S is incompatible with pending X +# Sending: +handler t1 open;; +# +# Switching to connection 'mdl_con2'. +# Check that the above HANDLER OPEN is blocked because of pending X lock. +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping HANDLER t1 OPEN. +handler t1 close; +# Restore pending X lock. +# +# Switching to connection 'mdl_con2'. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'default'. +# Add pending X lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME TABLE is waiting with pending X lock. +# Check that SR is incompatible with pending X +# Sending: +select count(*) from t1;; +# +# Switching to connection 'mdl_con2'. +# Check that the above SELECT is blocked because of pending X lock. +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping SELECT. +count(*) +4 +# Restore pending X lock. +# +# Switching to connection 'mdl_con2'. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'default'. +# Add pending X lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME TABLE is waiting with pending X lock. +# Check that SW is incompatible with pending X +# Sending: +delete from t1 limit 1;; +# +# Switching to connection 'mdl_con2'. +# Check that the above DELETE is blocked because of pending X lock. +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping DELETE. +# Restore pending X lock. +# +# Switching to connection 'mdl_con2'. +begin; +select count(*) from t1; +count(*) +3 +# +# Switching to connection 'default'. +# Add pending X lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME TABLE is waiting with pending X lock. +# Check that SNW is incompatible with pending X +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'mdl_con2'. +# Check that the above ALTER TABLE is blocked because of pending X lock. +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# Restore pending X lock. +# +# Switching to connection 'mdl_con2'. +handler t1 open; +# +# Switching to connection 'default'. +# Add pending X lock. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'mdl_con1'. +# Check that RENAME TABLE is waiting with pending X lock. +# Check that SNRW is incompatible with pending X +# Sending: +lock table t1 write;; +# +# Switching to connection 'mdl_con3'. +# Check that the above LOCK TABLES is blocked because of pending X lock. +# +# Switching to connection 'mdl_con2'. +# Unblock RENAME TABLE. +handler t1 close; +# +# Switching to connection 'default'. +# Reaping RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'mdl_con1'. +# Reaping LOCK TABLES. +unlock tables; +# +# Switching to connection 'default'. +# +# +# C) Now let us test how type-of-operation locks are handled in +# transactional context. Obviously we are mostly interested +# in conflicting types of locks. +# +# +# 1) Let us check how various locks used within transactional +# context interact with active/pending SNW lock. +# +# We start with case when we are acquiring lock on the table +# which was not used in the transaction before. +begin; +select count(*) from t1; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Create an active SNW lock on t2. +# We have to use DEBUG_SYNC facility as otherwise SNW lock +# will be immediately released (or upgraded to X lock). +insert into t2 values (1), (1); +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +alter table t2 add primary key (c1);; +# +# Switching to connection 'default'. +set debug_sync= 'now WAIT_FOR locked'; +# SR lock should be acquired without any waiting. +select count(*) from t2; +count(*) +2 +commit; +# Now let us check that we will wait in case of SW lock. +begin; +select count(*) from t1; +count(*) +3 +# Sending: +insert into t2 values (1);; +# +# Switching to connection 'mdl_con2'. +# Check that the above INSERT is blocked. +# Unblock ALTER TABLE and thus INSERT. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'mdl_con1'. +# Reap ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'default'. +# Reap INSERT. +commit; +# +# Now let us see what happens when we are acquiring lock on the table +# which is already used in transaction. +# +# *) First, case when transaction which has SR lock on the table also +# locked in SNW mode acquires yet another SR lock and then tries +# to acquire SW lock. +begin; +select count(*) from t1; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Create an active SNW lock on t1. +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'default'. +set debug_sync= 'now WAIT_FOR locked'; +# We should still be able to get SR lock without waiting. +select count(*) from t1; +count(*) +3 +# Since the above ALTER TABLE is not upgrading SNW lock to X by waiting +# for SW lock we won't create deadlock. +# So the below INSERT should not end-up with ER_LOCK_DEADLOCK error. +# Sending: +insert into t1 values (1);; +# +# Switching to connection 'mdl_con2'. +# Check that the above INSERT is blocked. +# Unblock ALTER TABLE and thus INSERT. +set debug_sync= 'now SIGNAL finish'; +# +# Switching to connection 'mdl_con1'. +# Reap ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'default'. +# Reap INSERT. +commit; +# +# **) Now test in which transaction that has SW lock on the table +# against which there is pending SNW lock acquires SR and SW +# locks on this table. +# +begin; +insert into t1 values (1); +# +# Switching to connection 'mdl_con1'. +# Create pending SNW lock on t1. +# Sending: +alter table t1 add primary key (c1);; +# +# Switching to connection 'default'. +# Wait until ALTER TABLE starts waiting for SNW lock. +# We should still be able to get both SW and SR locks without waiting. +select count(*) from t1; +count(*) +5 +delete from t1 limit 1; +# Unblock ALTER TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap ALTER TABLE. +ERROR 23000: Duplicate entry '1' for key 'PRIMARY' +# +# Switching to connection 'default'. +# +# 2) Now similar tests for active SNW lock which is being upgraded +# to X lock. +# +# Again we start with case when we are acquiring lock on the +# table which was not used in the transaction before. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con2'. +# Start transaction which will prevent SNW -> X upgrade from +# completing immediately. +begin; +select count(*) from t2; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Create SNW lock pending upgrade to X on t2. +# Sending: +alter table t2 add column c2 int;; +# +# Switching to connection 'default'. +# Wait until ALTER TABLE starts waiting X lock. +# Check that attempt to acquire SR lock on t2 causes waiting. +# Sending: +select count(*) from t2;; +# +# Switching to connection 'mdl_con2'. +# Check that the above SELECT is blocked. +# Unblock ALTER TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap ALTER TABLE. +# +# Switching to connection 'default'. +# Reap SELECT. +count(*) +3 +commit; +# Do similar check for SW lock. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con2'. +# Start transaction which will prevent SNW -> X upgrade from +# completing immediately. +begin; +select count(*) from t2; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Create SNW lock pending upgrade to X on t2. +# Sending: +alter table t2 drop column c2;; +# +# Switching to connection 'default'. +# Wait until ALTER TABLE starts waiting X lock. +# Check that attempt to acquire SW lock on t2 causes waiting. +# Sending: +insert into t2 values (1);; +# +# Switching to connection 'mdl_con2'. +# Check that the above INSERT is blocked. +# Unblock ALTER TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap ALTER TABLE. +# +# Switching to connection 'default'. +# Reap INSERT. +commit; +# +# Test for the case in which we are acquiring lock on the table +# which is already used in transaction. +# +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con1'. +# Create SNW lock pending upgrade to X. +# Sending: +alter table t1 add column c2 int;; +# +# Switching to connection 'default'. +# Wait until ALTER TABLE starts waiting X lock. +# Check that transaction is still able to acquire SR lock. +select count(*) from t1; +count(*) +4 +# Waiting trying to acquire SW lock will cause deadlock and +# therefore should cause an error. +delete from t1 limit 1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Unblock ALTER TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap ALTER TABLE. +# +# Switching to connection 'default'. +# +# 3) Check how various locks used within transactional context +# interact with active/pending SNRW lock. +# +# Once again we start with case when we are acquiring lock on +# the table which was not used in the transaction before. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con1'. +lock table t2 write; +# +# Switching to connection 'default'. +# Attempt to acquire SR should be blocked. It should +# not cause errors as it does not creates deadlock. +# Sending: +select count(*) from t2;; +# +# Switching to connection 'mdl_con1'. +# Check that the above SELECT is blocked +# Unblock SELECT. +unlock tables; +# +# Switching to connection 'default'. +# Reap SELECT. +count(*) +4 +commit; +# Repeat the same test for SW lock. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con1'. +lock table t2 write; +# +# Switching to connection 'default'. +# Again attempt to acquire SW should be blocked and should +# not cause any errors. +# Sending: +delete from t2 limit 1;; +# +# Switching to connection 'mdl_con1'. +# Check that the above DELETE is blocked +# Unblock DELETE. +unlock tables; +# +# Switching to connection 'default'. +# Reap DELETE. +commit; +# +# Now coverage for the case in which we are acquiring lock on +# the table which is already used in transaction and against +# which there is a pending SNRW lock request. +# +# *) Let us start with case when transaction has only a SR lock. +# +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con1'. +# Sending: +lock table t1 write;; +# +# Switching to connection 'default'. +# Wait until LOCK TABLE is blocked creating pending request for X lock. +# Check that another instance of SR lock is granted without waiting. +select count(*) from t1; +count(*) +4 +# Attempt to wait for SW lock will lead to deadlock, thus +# the below statement should end with ER_LOCK_DEADLOCK error. +delete from t1 limit 1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Unblock LOCK TABLES. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap LOCK TABLES. +unlock tables; +# +# Switching to connection 'default'. +# +# **) Now case when transaction has a SW lock. +# +begin; +delete from t1 limit 1; +# +# Switching to connection 'mdl_con1'. +# Sending: +lock table t1 write;; +# +# Switching to connection 'default'. +# Wait until LOCK TABLE is blocked creating pending request for X lock. +# Check that both SR and SW locks are granted without waiting +# and errors. +select count(*) from t1; +count(*) +3 +insert into t1 values (1, 1); +# Unblock LOCK TABLES. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap LOCK TABLES. +unlock tables; +# +# Switching to connection 'default'. +# +# 4) Check how various locks used within transactional context +# interact with active/pending X lock. +# +# As usual we start with case when we are acquiring lock on +# the table which was not used in the transaction before. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con2'. +# Start transaction which will prevent X lock from going away +# immediately. +begin; +select count(*) from t2; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Create pending X lock on t2. +# Sending: +rename table t2 to t3;; +# +# Switching to connection 'default'. +# Wait until RENAME TABLE starts waiting with pending X lock. +# Check that attempt to acquire SR lock on t2 causes waiting. +# Sending: +select count(*) from t2;; +# +# Switching to connection 'mdl_con2'. +# Check that the above SELECT is blocked. +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap RENAME TABLE. +# +# Switching to connection 'default'. +# Reap SELECT. +ERROR 42S02: Table 'test.t2' doesn't exist +commit; +rename table t3 to t2; +# The same test for SW lock. +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con2'. +# Start transaction which will prevent X lock from going away +# immediately. +begin; +select count(*) from t2; +count(*) +3 +# +# Switching to connection 'mdl_con1'. +# Create pending X lock on t2. +# Sending: +rename table t2 to t3;; +# +# Switching to connection 'default'. +# Wait until RENAME TABLE starts waiting with pending X lock. +# Check that attempt to acquire SW lock on t2 causes waiting. +# Sending: +delete from t2 limit 1;; +# +# Switching to connection 'mdl_con2'. +# Check that the above DELETE is blocked. +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap RENAME TABLE. +# +# Switching to connection 'default'. +# Reap DELETE. +ERROR 42S02: Table 'test.t2' doesn't exist +commit; +rename table t3 to t2; +# +# Coverage for the case in which we are acquiring lock on +# the table which is already used in transaction and against +# which there is a pending X lock request. +# +# *) The first case is when transaction has only a SR lock. +# +begin; +select count(*) from t1; +count(*) +4 +# +# Switching to connection 'mdl_con1'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'default'. +# Wait until RENAME TABLE is blocked creating pending request for X lock. +# Check that another instance of SR lock is granted without waiting. +select count(*) from t1; +count(*) +4 +# Attempt to wait for SW lock will lead to deadlock, thus +# the below statement should end with ER_LOCK_DEADLOCK error. +delete from t1 limit 1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'default'. +# +# **) The second case is when transaction has a SW lock. +# +begin; +delete from t1 limit 1; +# +# Switching to connection 'mdl_con1'. +# Sending: +rename table t1 to t2;; +# +# Switching to connection 'default'. +# Wait until RENAME TABLE is blocked creating pending request for X lock. +# Check that both SR and SW locks are granted without waiting +# and errors. +select count(*) from t1; +count(*) +3 +insert into t1 values (1, 1); +# Unblock RENAME TABLE. +commit; +# +# Switching to connection 'mdl_con1'. +# Reap RENAME TABLE. +ERROR 42S01: Table 't2' already exists +# +# Switching to connection 'default'. +# Clean-up. +set debug_sync= 'RESET'; +drop table t1, t2; +# +# Additional coverage for some scenarios in which not quite +# correct use of S metadata locks by HANDLER statement might +# have caused deadlocks. +# +drop table if exists t1, t2; +create table t1 (i int); +create table t2 (j int); +insert into t1 values (1); +# +# First, check scenario in which we upgrade SNRW lock to X lock +# on a table while having HANDLER READ trying to acquire TL_READ +# on the same table. +# +handler t1 open; +# +# Switching to connection 'handler_con1'. +lock table t1 write; +# Upgrade SNRW to X lock. +# Sending: +alter table t1 add column j int;; +# +# Switching to connection 'handler_con2'. +# Wait until ALTER is blocked during upgrade. +# +# Switching to connection 'default'. +# The below statement should not cause deadlock. +handler t1 read first;; +# +# Switching to connection 'handler_con1'. +# Reap ALTER TABLE. +unlock tables; +# +# Switching to connection 'default'. +# Reap HANDLER READ. +i j +1 NULL +handler t1 close; +# +# Now, check scenario in which upgrade of SNRW lock to X lock +# can be blocked by HANDLER which is open in connection currently +# waiting to get table-lock owned by connection doing upgrade. +# +handler t1 open; +# +# Switching to connection 'handler_con1'. +lock table t1 write, t2 read; +# +# Switching to connection 'default'. +# Execute statement which will be blocked on table-level lock +# owned by connection 'handler_con1'. +# Sending: +insert into t2 values (1);; +# +# Switching to connection 'handler_con1'. +# Wait until INSERT is blocked on table-level lock. +# The below statement should not cause deadlock. +alter table t1 drop column j; +unlock tables; +# +# Switching to connection 'default'. +# Reap INSERT. +handler t1 close; +# +# Then, check the scenario in which upgrade of SNRW lock to X +# lock is blocked by HANDLER which is open in connection currently +# waiting to get SW lock on the same table. +# +handler t1 open; +# +# Switching to connection 'handler_con1'. +lock table t1 write; +# +# Switching to connection 'default'. +# The below insert should be blocked because active SNRW lock on 't1'. +# Sending: +insert into t1 values (1);; +# +# Switching to connection 'handler_con1'. +# Wait until INSERT is blocked because of SNRW lock. +# The below ALTER TABLE will be blocked because of presence of HANDLER. +# Sending: +alter table t1 add column j int;; +# +# Switching to connection 'default'. +# INSERT should be chosen as victim for resolving deadlock. +# Reaping INSERT. +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Close HANDLER to unblock ALTER TABLE. +handler t1 close; +# +# Switching to connection 'handler_con1'. +# Reaping ALTER TABLE. +unlock tables; +# +# Switching to connection 'default'. +# +# Finally, test in which upgrade of SNRW lock to X lock is blocked +# by HANDLER which is open in connection currently waiting to get +# SR lock on the table on which lock is upgraded. +# +handler t1 open; +# +# Switching to connection 'handler_con1'. +lock table t1 write, t2 write; +# +# Switching to connection 'default'. +# The below insert should be blocked because active SNRW lock on 't1'. +# Sending: +insert into t2 values (1);; +# +# Switching to connection 'handler_con1'. +# Wait until INSERT is blocked because of SNRW lock. +# The below ALTER TABLE will be blocked because of presence of HANDLER. +# Sending: +alter table t1 drop column j;; +# +# Switching to connection 'default'. +# INSERT should be chosen as victim for resolving deadlock. +# Reaping INSERT. +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Close HANDLER to unblock ALTER TABLE. +handler t1 close; +# +# Switching to connection 'handler_con1'. +# Reaping ALTER TABLE. +unlock tables; +# +# Switching to connection 'default'. +# Clean-up. +drop tables t1, t2; +# +# Test coverage for basic deadlock detection in metadata +# locking subsystem. +# +drop tables if exists t0, t1, t2, t3, t4, t5; +create table t1 (i int); +create table t2 (j int); +create table t3 (k int); +create table t4 (k int); +# +# Test for the case in which no deadlock occurs. +# +# +# Switching to connection 'deadlock_con1'. +begin; +insert into t1 values (1); +# +# Switching to connection 'deadlock_con2'. +begin; +insert into t2 values (1); +# +# Switching to connection 'default'. +# Send: +rename table t2 to t0, t3 to t2, t0 to t3;; +# +# Switching to connection 'deadlock_con1'. +# Wait until the above RENAME TABLE is blocked because it has to wait +# for 'deadlock_con2' which holds shared metadata lock on 't2'. +# The below statement should wait for exclusive metadata lock +# on 't2' to go away and should not produce ER_LOCK_DEADLOCK +# as no deadlock is possible in this situation. +# Send: +select * from t2;; +# +# Switching to connection 'deadlock_con2'. +# Wait until the above SELECT * FROM t2 is starts waiting +# for an exclusive metadata lock to go away. +# +# Unblock RENAME TABLE by releasing shared metadata lock on t2. +commit; +# +# Switching to connection 'default'. +# Reap RENAME TABLE. +# +# Switching to connection 'deadlock_con1'. +# Reap SELECT. +k +# +# Switching to connection 'default'. +# +# Let us check that in the process of waiting for conflicting lock +# on table 't2' to go away transaction in connection 'deadlock_con1' +# has not released metadata lock on table 't1'. +# Send: +rename table t1 to t0, t3 to t1, t0 to t3;; +# +# Switching to connection 'deadlock_con1'. +# Wait until the above RENAME TABLE is blocked because it has to wait +# for 'deadlock_con1' which should still hold shared metadata lock on +# table 't1'. +# Commit transaction to unblock RENAME TABLE. +commit; +# +# Switching to connection 'default'. +# Reap RENAME TABLE. +# +# Test for case when deadlock occurs and should be detected immediately. +# +# +# Switching to connection 'deadlock_con1'. +begin; +insert into t2 values (2); +# +# Switching to connection 'default'. +# Send: +rename table t2 to t0, t1 to t2, t0 to t1;; +# +# Switching to connection 'deadlock_con1'. +# Wait until the above RENAME TABLE is blocked because it has to wait +# for 'deadlock_con1' which holds shared metadata lock on 't2'. +# +# The below statement should not wait as doing so will cause deadlock. +# Instead it should fail and emit ER_LOCK_DEADLOCK statement. +select * from t1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# +# Let us check that failure of the above statement has not released +# metadata lock on table 't1', i.e. that RENAME TABLE is still blocked. +# Commit transaction to unblock RENAME TABLE. +commit; +# +# Switching to connection 'default'. +# Reap RENAME TABLE. +# +# Test for the case in which deadlock also occurs but not immediately. +# +# +# Switching to connection 'deadlock_con1'. +begin; +insert into t2 values (1); +# +# Switching to connection 'default'. +lock table t1 write; +# +# Switching to connection 'deadlock_con1'. +# The below SELECT statement should wait for metadata lock +# on table 't1' and should not produce ER_LOCK_DEADLOCK +# immediately as no deadlock is possible at the moment. +select * from t1;; +# +# Switching to connection 'deadlock_con2'. +# Wait until the above SELECT * FROM t1 is starts waiting +# for an UNRW metadata lock to go away. +# Send RENAME TABLE statement that will deadlock with the +# SELECT statement and thus should abort the latter. +rename table t1 to t0, t2 to t1, t0 to t2;; +# +# Switching to connection 'default'. +# Wait till above RENAME TABLE is blocked while holding +# pending X lock on t1. +# Allow the above RENAME TABLE to acquire lock on t1 and +# create pending lock on t2 thus creating deadlock. +unlock tables; +# +# Switching to connection 'deadlock_con1'. +# Since the latest RENAME TABLE entered in deadlock with SELECT +# statement the latter should be aborted and emit ER_LOCK_DEADLOCK +# error. +# Reap SELECT * FROM t1. +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# +# Again let us check that failure of the SELECT statement has not +# released metadata lock on table 't2', i.e. that the latest RENAME +# is blocked. +# Commit transaction to unblock this RENAME TABLE. +commit; +# +# Switching to connection 'deadlock_con2'. +# Reap RENAME TABLE ... . +# +# Switching to connection 'default'. +drop tables t1, t2, t3, t4; +# +# Now, test case which shows that deadlock detection empiric +# also takes into account requests for metadata lock upgrade. +# +create table t1 (i int); +insert into t1 values (1); +# Avoid race which occurs when SELECT in 'deadlock_con1' connection +# accesses table before the above INSERT unlocks the table and thus +# its result becomes visible to other connections. +select * from t1; +i +1 +# +# Switching to connection 'deadlock_con1'. +begin; +select * from t1; +i +1 +# +# Switching to connection 'default'. +# Send: +alter table t1 add column j int, rename to t2;; +# +# Switching to connection 'deadlock_con1'. +# Wait until the above ALTER TABLE ... RENAME acquires exclusive +# metadata lock on 't2' and starts waiting for connection +# 'deadlock_con1' which holds shared lock on 't1'. +# The below statement should not wait as it will cause deadlock. +# An appropriate error should be reported instead. +select * from t2; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# Again let us check that failure of the above statement has not +# released all metadata locks in connection 'deadlock_con1' and +# so ALTER TABLE ... RENAME is still blocked. +# Commit transaction to unblock ALTER TABLE ... RENAME. +commit; +# +# Switching to connection 'default'. +# Reap ALTER TABLE ... RENAME. +drop table t2; +# +# Test for bug #46748 "Assertion in MDL_context::wait_for_locks() +# on INSERT + CREATE TRIGGER". +# +drop tables if exists t1, t2, t3, t4, t5; +# Let us simulate scenario in which we open some tables from extended +# part of prelocking set but then encounter conflicting metadata lock, +# so have to back-off and wait for it to go away. +create table t1 (i int); +create table t2 (j int); +create table t3 (k int); +create table t4 (l int); +create trigger t1_bi before insert on t1 for each row +insert into t2 values (new.i); +create trigger t2_bi before insert on t2 for each row +insert into t3 values (new.j); +# +# Switching to connection 'con1root'. +lock tables t4 read; +# +# Switching to connection 'con2root'. +# Send : +rename table t3 to t5, t4 to t3;; +# +# Switching to connection 'default'. +# Wait until the above RENAME TABLE adds pending requests for exclusive +# metadata lock on its tables and blocks due to 't4' being used by LOCK +# TABLES. +# Send : +insert into t1 values (1);; +# +# Switching to connection 'con1root'. +# Wait until INSERT statement waits due to encountering pending +# exclusive metadata lock on 't3'. +unlock tables; +# +# Switching to connection 'con2root'. +# Reap RENAME TABLE. +# +# Switching to connection 'default'. +# Reap INSERT. +# Clean-up. +drop tables t1, t2, t3, t5; +# +# Bug#42546 - Backup: RESTORE fails, thinking it finds an existing table +# +DROP TABLE IF EXISTS t1; +set @save_log_output=@@global.log_output; +set global log_output=file; +# +# Test 1: CREATE TABLE +# +# Connection 2 +# Start insert on the not-yet existing table +# Wait after taking the MDL lock +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +INSERT INTO t1 VALUES(1,"def"); +# Connection 1 +SET DEBUG_SYNC= 'now WAIT_FOR locked'; +# Now INSERT has a MDL on the non-existent table t1. +# +# Continue the INSERT once CREATE waits for exclusive lock +SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL finish'; +# Try to create that table. +CREATE TABLE t1 (c1 INT, c2 VARCHAR(100), KEY(c1)); +# Connection 2 +# Insert fails +ERROR 42S02: Table 'test.t1' doesn't exist +# Connection 1 +SET DEBUG_SYNC= 'RESET'; +SHOW TABLES; +Tables_in_test +t1 +DROP TABLE IF EXISTS t1; +# +# Test 2: CREATE TABLE LIKE +# +CREATE TABLE t2 (c1 INT, c2 VARCHAR(100), KEY(c1)); +# Connection 2 +# Start insert on the not-yet existing table +# Wait after taking the MDL +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +INSERT INTO t1 VALUES(1,"def"); +# Connection 1 +SET DEBUG_SYNC= 'now WAIT_FOR locked'; +# Now INSERT has a MDL on the non-existent table t1. +# +# Continue the INSERT once CREATE waits for exclusive lock +SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL finish'; +# Try to create that table. +CREATE TABLE t1 LIKE t2; +# Connection 2 +# Insert fails +ERROR 42S02: Table 'test.t1' doesn't exist +# Connection 1 +SET DEBUG_SYNC= 'RESET'; +SHOW TABLES; +Tables_in_test +t1 +t2 +DROP TABLE t2; +DROP TABLE IF EXISTS t1; +set global log_output=@save_log_output; +# +# Bug #46044 "MDL deadlock on LOCK TABLE + CREATE TABLE HIGH_PRIORITY +# FOR UPDATE" +# +drop tables if exists t1, t2; +create table t1 (i int); +# Let us check that we won't deadlock if during filling +# of I_S table we encounter conflicting metadata lock +# which owner is in its turn waiting for our connection. +lock tables t1 read; +# Switching to connection 'con46044'. +# Sending: +create table t2 select * from t1 for update;; +# Switching to connection 'default'. +# Waiting until CREATE TABLE ... SELECT ... is blocked. +# First let us check that SHOW FIELDS/DESCRIBE doesn't +# gets blocked and emits and error. +show fields from t2; +ERROR HY000: Table 'test'.'t2' was skipped since its definition is being modified by concurrent DDL statement +# Now test for I_S query which reads only .FRMs. +# +# Query below should only emit a warning. +select column_name from information_schema.columns +where table_schema='test' and table_name='t2'; +column_name +Warnings: +Warning 1652 Table 'test'.'t2' was skipped since its definition is being modified by concurrent DDL statement +# Finally, test for I_S query which does full-blown table open. +# +# Query below should not be blocked. Warning message should be +# stored in the 'table_comment' column. +select table_name, table_type, auto_increment, table_comment +from information_schema.tables where table_schema='test' and table_name='t2'; +table_name table_type auto_increment table_comment +t2 BASE TABLE NULL Table 'test'.'t2' was skipped since its definition is being modified by concurre +# Switching to connection 'default'. +unlock tables; +# Switching to connection 'con46044'. +# Reaping CREATE TABLE ... SELECT ... . +drop table t2; +# +# Let us also check that queries to I_S wait for conflicting metadata +# locks to go away instead of skipping table with a warning in cases +# when deadlock is not possible. This is a nice thing from compatibility +# and ease of use points of view. +# +# We check same three queries to I_S in this new situation. +# Switching to connection 'con46044_2'. +lock tables t1 read; +# Switching to connection 'con46044'. +# Sending: +create table t2 select * from t1 for update;; +# Switching to connection 'default'. +# Waiting until CREATE TABLE ... SELECT ... is blocked. +# Let us check that SHOW FIELDS/DESCRIBE gets blocked. +# Sending: +show fields from t2;; +# Switching to connection 'con46044_2'. +# Wait until SHOW FIELDS gets blocked. +unlock tables; +# Switching to connection 'con46044'. +# Reaping CREATE TABLE ... SELECT ... . +# Switching to connection 'default'. +# Reaping SHOW FIELDS ... +Field Type Null Key Default Extra +i int(11) YES NULL +drop table t2; +# Switching to connection 'con46044_2'. +lock tables t1 read; +# Switching to connection 'con46044'. +# Sending: +create table t2 select * from t1 for update;; +# Switching to connection 'default'. +# Waiting until CREATE TABLE ... SELECT ... is blocked. +# Check that I_S query which reads only .FRMs gets blocked. +# Sending: +select column_name from information_schema.columns where table_schema='test' and table_name='t2';; +# Switching to connection 'con46044_2'. +# Wait until SELECT COLUMN_NAME FROM I_S.COLUMNS gets blocked. +unlock tables; +# Switching to connection 'con46044'. +# Reaping CREATE TABLE ... SELECT ... . +# Switching to connection 'default'. +# Reaping SELECT COLUMN_NAME FROM I_S.COLUMNS +column_name +i +drop table t2; +# Switching to connection 'con46044_2'. +lock tables t1 read; +# Switching to connection 'con46044'. +# Sending: +create table t2 select * from t1 for update;; +# Switching to connection 'default'. +# Waiting until CREATE TABLE ... SELECT ... is blocked. +# Finally, check that I_S query which does full-blown table open +# also gets blocked. +# Sending: +select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t2';; +# Switching to connection 'con46044_2'. +# Wait until SELECT ... FROM I_S.TABLES gets blocked. +unlock tables; +# Switching to connection 'con46044'. +# Reaping CREATE TABLE ... SELECT ... . +# Switching to connection 'default'. +# Reaping SELECT ... FROM I_S.TABLES +table_name table_type auto_increment table_comment +t2 BASE TABLE NULL +drop table t2; +# Switching to connection 'default'. +# Clean-up. +drop table t1; +# +# Test for bug #46273 "MySQL 5.4.4 new MDL: Bug#989 is not fully fixed +# in case of ALTER". +# +drop table if exists t1; +set debug_sync= 'RESET'; +create table t1 (c1 int primary key, c2 int, c3 int); +insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0); +begin; +select * from t1 where c2 = 3; +c1 c2 c3 +3 3 0 +# +# Switching to connection 'con46273'. +set debug_sync='after_lock_tables_takes_lock SIGNAL alter_table_locked WAIT_FOR alter_go'; +alter table t1 add column e int, rename to t2;; +# +# Switching to connection 'default'. +set debug_sync='now WAIT_FOR alter_table_locked'; +set debug_sync='before_open_table_wait_refresh SIGNAL alter_go'; +# The below statement should get ER_LOCK_DEADLOCK error +# (i.e. it should not allow ALTER to proceed, and then +# fail due to 't1' changing its name to 't2'). +update t1 set c3=c3+1 where c2 = 3; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +# +# Let us check that failure of the above statement has not released +# metadata lock on table 't1', i.e. that ALTER TABLE is still blocked. +# Unblock ALTER TABLE by commiting transaction and thus releasing +# metadata lock on 't1'. +commit; +# +# Switching to connection 'con46273'. +# Reap ALTER TABLE. +# +# Switching to connection 'default'. +# Clean-up. +set debug_sync= 'RESET'; +drop table t2; +# +# Test for bug #46673 "Deadlock between FLUSH TABLES WITH READ LOCK +# and DML". +# +drop tables if exists t1; +create table t1 (i int); +# Switching to connection 'con46673'. +begin; +insert into t1 values (1); +# Switching to connection 'default'. +# Statement below should not get blocked. And if after some +# changes to code it is there should not be a deadlock between +# it and transaction from connection 'con46673'. +flush tables with read lock; +unlock tables; +# Switching to connection 'con46673'. +delete from t1 where i = 1; +commit; +# Switching to connection 'default'. +# Clean-up +drop table t1; +# +# Bug#48210 FLUSH TABLES WITH READ LOCK deadlocks +# against concurrent CREATE PROCEDURE +# +# Test 1: CREATE PROCEDURE +# Connection 1 +# Start CREATE PROCEDURE and open mysql.proc +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL table_opened WAIT_FOR grlwait'; +CREATE PROCEDURE p1() SELECT 1; +# Connection 2 +SET DEBUG_SYNC= 'now WAIT_FOR table_opened'; +# Check that FLUSH must wait to get the GRL +# and let CREATE PROCEDURE continue +SET DEBUG_SYNC= 'wait_lock_global_read_lock SIGNAL grlwait'; +FLUSH TABLES WITH READ LOCK; +# Connection 1 +# Connection 2 +UNLOCK TABLES; +# Connection 1 +SET DEBUG_SYNC= 'RESET'; +# Test 2: DROP PROCEDURE +# Start DROP PROCEDURE and open tables +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL table_opened WAIT_FOR grlwait'; +DROP PROCEDURE p1; +# Connection 2 +SET DEBUG_SYNC= 'now WAIT_FOR table_opened'; +# Check that FLUSH must wait to get the GRL +# and let DROP PROCEDURE continue +SET DEBUG_SYNC= 'wait_lock_global_read_lock SIGNAL grlwait'; +FLUSH TABLES WITH READ LOCK; +# Connection 1 +# Connection 2 +UNLOCK TABLES; +# Connection 1 +SET DEBUG_SYNC= 'RESET'; diff --git a/mysql-test/r/merge.result b/mysql-test/r/merge.result index 83152a0dd8d..a215c818b0f 100644 --- a/mysql-test/r/merge.result +++ b/mysql-test/r/merge.result @@ -578,23 +578,23 @@ select max(b) from t1 where a = 2; max(b) 1 drop table t3,t1,t2; -create table t1 (a int not null); -create table t2 (a int not null); -insert into t1 values (1); -insert into t2 values (2); -create temporary table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2); -select * from t3; +CREATE TABLE t1 (c1 INT NOT NULL); +CREATE TABLE t2 (c1 INT NOT NULL); +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (2); +CREATE TEMPORARY TABLE t3 (c1 INT NOT NULL) ENGINE=MRG_MYISAM UNION=(t1,t2); +SELECT * FROM t3; ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist -create temporary table t4 (a int not null); -create temporary table t5 (a int not null); -insert into t4 values (1); -insert into t5 values (2); -create temporary table t6 (a int not null) ENGINE=MERGE UNION=(t4,t5); -select * from t6; -a -1 -2 -drop table t6, t3, t1, t2, t4, t5; +CREATE TEMPORARY TABLE t4 (c1 INT NOT NULL); +CREATE TEMPORARY TABLE t5 (c1 INT NOT NULL); +INSERT INTO t4 VALUES (4); +INSERT INTO t5 VALUES (5); +CREATE TEMPORARY TABLE t6 (c1 INT NOT NULL) ENGINE=MRG_MYISAM UNION=(t4,t5); +SELECT * FROM t6; +c1 +4 +5 +DROP TABLE t6, t3, t1, t2, t4, t5; create temporary table t1 (a int not null); create temporary table t2 (a int not null); insert into t1 values (1); @@ -1046,18 +1046,21 @@ c1 LOCK TABLE t1 WRITE, t2 WRITE, t3 WRITE; INSERT INTO t1 VALUES (1); TRUNCATE TABLE t3; -ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction SELECT * FROM t3; c1 -1 -2 +UNLOCK TABLES; +SELECT * FROM t1; +c1 +SELECT * FROM t2; +c1 # # Truncate child table under locked tables. +LOCK TABLE t1 WRITE, t2 WRITE, t3 WRITE; +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (2); TRUNCATE TABLE t1; -ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction SELECT * FROM t3; c1 -1 2 UNLOCK TABLES; DROP TABLE t1, t2, t3; @@ -1089,18 +1092,24 @@ INSERT INTO t1 VALUES (1); CREATE TABLE t4 (c1 INT, INDEX(c1)); LOCK TABLE t4 WRITE; TRUNCATE TABLE t3; -ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction SELECT * FROM t3; c1 -1 -2 +SELECT * FROM t1; +c1 +SELECT * FROM t2; +c1 # # Truncate temporary child table under locked tables. +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (2); TRUNCATE TABLE t1; -ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction SELECT * FROM t3; c1 -1 +2 +SELECT * FROM t1; +c1 +SELECT * FROM t2; +c1 2 UNLOCK TABLES; DROP TABLE t1, t2, t3, t4; @@ -1149,7 +1158,8 @@ SHOW CREATE TABLE t3; ERROR 42S02: Table 'test.t3' doesn't exist DROP TABLE t1, t2; # -# CREATE ... LIKE +# Bug#37371 "CREATE TABLE LIKE merge loses UNION parameter" +# Demonstrate that this is no longer the case. # # 1. Create like. CREATE TABLE t1 (c1 INT); @@ -1164,26 +1174,26 @@ SHOW CREATE TABLE t4; Table Create Table t4 CREATE TABLE `t4` ( `c1` int(11) DEFAULT NULL -) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 +) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 INSERT_METHOD=LAST UNION=(`t1`,`t2`) INSERT INTO t4 VALUES (4); -ERROR HY000: Table 't4' is read only DROP TABLE t4; # # 1. Create like with locked tables. LOCK TABLES t3 WRITE, t2 WRITE, t1 WRITE; CREATE TABLE t4 LIKE t3; +ERROR HY000: Table 't4' was not locked with LOCK TABLES SHOW CREATE TABLE t4; ERROR HY000: Table 't4' was not locked with LOCK TABLES INSERT INTO t4 VALUES (4); ERROR HY000: Table 't4' was not locked with LOCK TABLES -UNLOCK TABLES; +CREATE TEMPORARY TABLE t4 LIKE t3; SHOW CREATE TABLE t4; -Table Create Table -t4 CREATE TABLE `t4` ( - `c1` int(11) DEFAULT NULL -) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 +ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist INSERT INTO t4 VALUES (4); -ERROR HY000: Table 't4' is read only +ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist +UNLOCK TABLES; +INSERT INTO t4 VALUES (4); +ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist DROP TABLE t4; # # Rename child. @@ -1210,6 +1220,7 @@ c1 1 2 3 +4 RENAME TABLE t2 TO t5; SELECT * FROM t3 ORDER BY c1; ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist @@ -1219,6 +1230,7 @@ c1 1 2 3 +4 # # 3. Normal rename with locked tables. LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE; @@ -1227,6 +1239,7 @@ c1 1 2 3 +4 RENAME TABLE t2 TO t5; ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction SELECT * FROM t3 ORDER BY c1; @@ -1234,6 +1247,7 @@ c1 1 2 3 +4 RENAME TABLE t5 TO t2; ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction SELECT * FROM t3 ORDER BY c1; @@ -1241,6 +1255,7 @@ c1 1 2 3 +4 UNLOCK TABLES; # # 4. Alter table rename. @@ -1253,12 +1268,13 @@ c1 1 2 3 +4 # # 5. Alter table rename with locked tables. LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE; ALTER TABLE t2 RENAME TO t5; SELECT * FROM t3 ORDER BY c1; -ERROR HY000: Table 't3' was not locked with LOCK TABLES +ERROR HY000: Table 't2' was not locked with LOCK TABLES ALTER TABLE t5 RENAME TO t2; ERROR HY000: Table 't5' was not locked with LOCK TABLES UNLOCK TABLES; @@ -1268,6 +1284,7 @@ c1 1 2 3 +4 # # Rename parent. # @@ -1278,6 +1295,7 @@ c1 1 2 3 +4 RENAME TABLE t3 TO t5; ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction SELECT * FROM t3 ORDER BY c1; @@ -1285,6 +1303,7 @@ c1 1 2 3 +4 RENAME TABLE t5 TO t3; ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction SELECT * FROM t3 ORDER BY c1; @@ -1292,6 +1311,7 @@ c1 1 2 3 +4 # # 5. Alter table rename with locked tables. ALTER TABLE t3 RENAME TO t5; @@ -1306,6 +1326,7 @@ c1 1 2 3 +4 DROP TABLE t1, t2, t3; # # Drop locked tables. @@ -1330,9 +1351,9 @@ LOCK TABLES t1 WRITE, t2 WRITE; INSERT INTO t1 VALUES (1); DROP TABLE t1; SELECT * FROM t2; -ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist +ERROR HY000: Table 't1' was not locked with LOCK TABLES SELECT * FROM t1; -ERROR 42S02: Table 'test.t1' doesn't exist +ERROR HY000: Table 't1' was not locked with LOCK TABLES UNLOCK TABLES; DROP TABLE t2; # @@ -2256,3 +2277,280 @@ deallocate prepare stmt; # drop table t_parent; set @@global.table_definition_cache=@save_table_definition_cache; +DROP DATABASE IF EXISTS mysql_test1; +CREATE DATABASE mysql_test1; +CREATE TABLE t1 ... DATA DIRECTORY=... INDEX DIRECTORY=... +CREATE TABLE mysql_test1.t2 ... DATA DIRECTORY=... INDEX DIRECTORY=... +CREATE TABLE m1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,mysql_test1.t2) +INSERT_METHOD=LAST; +INSERT INTO t1 VALUES (1); +INSERT INTO mysql_test1.t2 VALUES (2); +SELECT * FROM m1; +c1 +1 +2 +DROP TABLE t1, mysql_test1.t2, m1; +DROP DATABASE mysql_test1; +CREATE TABLE t1 (c1 INT); +CREATE TABLE t2 (c1 INT); +INSERT INTO t1 (c1) VALUES (1); +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2) INSERT_METHOD=FIRST; +CREATE TABLE t3 (c1 INT); +INSERT INTO t3 (c1) VALUES (1); +CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MAX(c1) FROM t3); +CREATE VIEW v1 AS SELECT foo.c1 c1, f1() c2, bar.c1 c3, f1() c4 +FROM tm1 foo, tm1 bar, t3; +SELECT * FROM v1; +c1 c2 c3 c4 +1 1 1 1 +DROP FUNCTION f1; +DROP VIEW v1; +DROP TABLE tm1, t1, t2, t3; +CREATE TEMPORARY TABLE t1 (c1 INT); +CREATE TEMPORARY TABLE t2 (c1 INT); +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2) +INSERT_METHOD=FIRST; +CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MAX(c1) FROM tm1); +INSERT INTO tm1 (c1) VALUES (1); +SELECT f1() FROM (SELECT 1) AS c1; +f1() +1 +DROP FUNCTION f1; +DROP TABLE tm1, t1, t2; +CREATE FUNCTION f1() RETURNS INT +BEGIN +CREATE TEMPORARY TABLE t1 (c1 INT); +CREATE TEMPORARY TABLE t2 (c1 INT); +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2); +INSERT INTO t1 (c1) VALUES (1); +RETURN (SELECT MAX(c1) FROM tm1); +END| +SELECT f1() FROM (SELECT 1 UNION SELECT 1) c1; +f1() +1 +DROP FUNCTION f1; +DROP TABLE tm1, t1, t2; +CREATE TEMPORARY TABLE t1 (c1 INT); +INSERT INTO t1 (c1) VALUES (1); +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1); +CREATE FUNCTION f1() RETURNS INT +BEGIN +CREATE TEMPORARY TABLE t2 (c1 INT); +ALTER TEMPORARY TABLE tm1 UNION=(t1,t2); +INSERT INTO t2 (c1) VALUES (2); +RETURN (SELECT MAX(c1) FROM tm1); +END| +ERROR 0A000: ALTER VIEW is not allowed in stored procedures +DROP TABLE tm1, t1; +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +c1 +1 +DROP TABLE tm1, t1; +CREATE FUNCTION f1() RETURNS INT +BEGIN +INSERT INTO tm1 VALUES (1); +RETURN (SELECT MAX(c1) FROM tm1); +END| +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +SELECT f1(); +f1() +1 +DROP FUNCTION f1; +DROP TABLE tm1, t1; +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +LOCK TABLE tm1 WRITE; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +c1 +1 +UNLOCK TABLES; +DROP TABLE tm1, t1; +CREATE FUNCTION f1() RETURNS INT +BEGIN +INSERT INTO tm1 VALUES (1); +RETURN (SELECT MAX(c1) FROM tm1); +END| +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +LOCK TABLE tm1 WRITE; +SELECT f1(); +f1() +1 +UNLOCK TABLES; +DROP FUNCTION f1; +DROP TABLE tm1, t1; +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t2 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +CREATE TRIGGER t2_ai AFTER INSERT ON t2 +FOR EACH ROW INSERT INTO tm1 VALUES(11); +LOCK TABLE t2 WRITE; +INSERT INTO t2 VALUES (2); +SELECT * FROM tm1; +c1 +11 +SELECT * FROM t2; +c1 +2 +UNLOCK TABLES; +DROP TRIGGER t2_ai; +DROP TABLE tm1, t1, t2; +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) +INSERT_METHOD=LAST; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +c1 +1 +DROP TABLE tm1, t1; +CREATE FUNCTION f1() RETURNS INT +BEGIN +INSERT INTO tm1 VALUES (1); +RETURN (SELECT MAX(c1) FROM tm1); +END| +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) +INSERT_METHOD=LAST; +SELECT f1(); +f1() +1 +DROP FUNCTION f1; +DROP TABLE tm1, t1; +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) +INSERT_METHOD=LAST; +CREATE TABLE t9 (c1 INT) ENGINE=MyISAM; +LOCK TABLE t9 WRITE; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +c1 +1 +UNLOCK TABLES; +DROP TABLE tm1, t1, t9; +CREATE FUNCTION f1() RETURNS INT +BEGIN +INSERT INTO tm1 VALUES (1); +RETURN (SELECT MAX(c1) FROM tm1); +END| +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) +INSERT_METHOD=LAST; +CREATE TABLE t9 (c1 INT) ENGINE=MyISAM; +LOCK TABLE t9 WRITE; +SELECT f1(); +f1() +1 +UNLOCK TABLES; +DROP FUNCTION f1; +DROP TABLE tm1, t1, t9; +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) +INSERT_METHOD=LAST; +CREATE TABLE t2 (c1 INT) ENGINE=MyISAM; +CREATE TRIGGER t2_ai AFTER INSERT ON t2 +FOR EACH ROW INSERT INTO tm1 VALUES(11); +LOCK TABLE t2 WRITE; +INSERT INTO t2 VALUES (2); +SELECT * FROM tm1; +c1 +11 +SELECT * FROM t2; +c1 +2 +UNLOCK TABLES; +DROP TRIGGER t2_ai; +DROP TABLE tm1, t1, t2; +# +# Don't select MERGE child when trying to get prelocked table. +# +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) +INSERT_METHOD=LAST; +CREATE TRIGGER tm1_ai AFTER INSERT ON tm1 +FOR EACH ROW INSERT INTO t1 VALUES(11); +LOCK TABLE tm1 WRITE, t1 WRITE; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +c1 +1 +11 +UNLOCK TABLES; +LOCK TABLE t1 WRITE, tm1 WRITE; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +c1 +1 +11 +1 +11 +UNLOCK TABLES; +DROP TRIGGER tm1_ai; +DROP TABLE tm1, t1; +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t2 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t3 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t4 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t5 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2,t3,t4,t5) +INSERT_METHOD=LAST; +CREATE TRIGGER t2_au AFTER UPDATE ON t2 +FOR EACH ROW INSERT INTO t3 VALUES(33); +CREATE FUNCTION f1() RETURNS INT +RETURN (SELECT MAX(c1) FROM t4); +LOCK TABLE tm1 WRITE, t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE, t5 WRITE; +INSERT INTO t1 VALUES(1); +INSERT INTO t2 VALUES(2); +INSERT INTO t3 VALUES(3); +INSERT INTO t4 VALUES(4); +INSERT INTO t5 VALUES(5); +UPDATE t2, tm1 SET t2.c1=f1(); +FLUSH TABLES; +FLUSH TABLES; +UNLOCK TABLES; +SELECT * FROM tm1; +c1 +1 +4 +3 +33 +4 +5 +DROP TRIGGER t2_au; +DROP FUNCTION f1; +DROP TABLE tm1, t1, t2, t3, t4, t5; +# +# Bug47098 assert in MDL_context::destroy on HANDLER +# <damaged merge table> OPEN +# +# Test that merge tables are closed correctly when opened using +# HANDLER ... OPEN. +# The general case. +DROP TABLE IF EXISTS t1, t2, t3; +# Connection con1. +CREATE TABLE t1 (c1 int); +CREATE TABLE t2 (c1 int); +CREATE TABLE t3 (c1 int) ENGINE = MERGE UNION (t1,t2); +START TRANSACTION; +HANDLER t3 OPEN; +ERROR HY000: Table storage engine for 't3' doesn't have this option +DROP TABLE t1, t2, t3; +# Connection default. +# Disconnecting con1, all mdl_tickets must have been released. +# The bug-specific case. +# Connection con1. +CREATE TABLE t1 (c1 int); +CREATE TABLE t2 (c1 int); +CREATE TABLE t3 (c1 int) ENGINE = MERGE UNION (t1,t2); +DROP TABLE t2; +START TRANSACTION; +HANDLER t3 OPEN; +ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist +DROP TABLE t1, t3; +# Connection default. +# Disconnecting con1, all mdl_tickets must have been released. +End of 6.0 tests diff --git a/mysql-test/r/merge_recover.result b/mysql-test/r/merge_recover.result new file mode 100644 index 00000000000..871c12ca4c0 --- /dev/null +++ b/mysql-test/r/merge_recover.result @@ -0,0 +1,103 @@ +# +# Test of MyISAM MRG tables with corrupted children. +# Run with --myisam-recover=force option. +# +# Preparation: we need to make sure that the merge parent +# is never left in the table cache when closed, since this may +# have effect on merge children. +# For that, we set the table cache to minimal size and populate it +# in a concurrent connection. +# +# Switching to connection con1 +# +# +# Minimal values. +# +call mtr.add_suppression("Got an error from thread_id=.*ha_myisam.cc:"); +call mtr.add_suppression("MySQL thread id .*, query id .* localhost.*root Checking table"); +call mtr.add_suppression(" '\..test.t1'"); +set global table_open_cache=256; +set global table_definition_cache=400; +drop procedure if exists p_create; +create procedure p_create() +begin +declare i int default 1; +set @lock_table_stmt="lock table "; +set @drop_table_stmt="drop table "; +while i < @@global.table_definition_cache + 1 do +set @table_name=concat("t_", i); +set @opt_comma=if(i=1, "", ", "); +set @lock_table_stmt=concat(@lock_table_stmt, @opt_comma, +@table_name, " read"); +set @drop_table_stmt=concat(@drop_table_stmt, @opt_comma, @table_name); +set @create_table_stmt=concat("create table if not exists ", +@table_name, " (a int)"); +prepare stmt from @create_table_stmt; +execute stmt; +deallocate prepare stmt; +set i= i+1; +end while; +end| +call p_create(); +drop procedure p_create; +# +# Switching to connection 'default' +# +# +# We have to disable the ps-protocol, to avoid +# "Prepared statement needs to be re-prepared" errors +# -- table def versions change all the time with full table cache. +# +drop table if exists t1, t1_mrg, t1_copy; +# +# Prepare a MERGE engine table, that refers to a corrupted +# child. +# +create table t1 (a int, key(a)) engine=myisam; +create table t1_mrg (a int) union (t1) engine=merge; +# +# Create a table with a corrupted index file: +# save an old index file, insert more rows, +# overwrite the new index file with the old one. +# +insert into t1 (a) values (1), (2), (3); +flush table t1; +insert into t1 (a) values (4), (5), (6); +flush table t1; +# check table is needed to mark the table as crashed. +check table t1; +Table Op Msg_type Msg_text +test.t1 check warning Size of datafile is: 42 Should be: 21 +test.t1 check error Record-count is not ok; is 6 Should be: 3 +test.t1 check warning Found 6 key parts. Should be: 3 +test.t1 check error Corrupt +# +# At this point we have a merge table t1_mrg pointing to t1, +# and t1 is corrupted, and will be auto-repaired at open. +# Check that this doesn't lead to memory corruption. +# +select * from t1_mrg; +a +1 +2 +3 +4 +5 +6 +Warnings: +Error 145 Table 't1' is marked as crashed and should be repaired +Error 1194 Table 't1' is marked as crashed and should be repaired +Error 1034 Number of rows changed from 3 to 6 +# +# Cleanup +# +drop table t1, t1_mrg; +# +# Switching to connection con1 +# +unlock tables; +prepare stmt from @drop_table_stmt; +execute stmt; +deallocate prepare stmt; +set @@global.table_definition_cache=default; +set @@global.table_open_cache=default; diff --git a/mysql-test/r/mix2_myisam.result b/mysql-test/r/mix2_myisam.result index cabc4de8d21..99596c7774d 100644 --- a/mysql-test/r/mix2_myisam.result +++ b/mysql-test/r/mix2_myisam.result @@ -2063,6 +2063,7 @@ insert into t1(a) values (1),(2),(3); commit; set autocommit = 0; update t1 set b = 5 where a = 2; +commit; create trigger t1t before insert on t1 for each row begin set NEW.b = NEW.a * 10 + 5, NEW.c = NEW.a / 10; end | set autocommit = 0; insert into t1(a) values (10),(20),(30),(40),(50),(60),(70),(80),(90),(100), @@ -2105,6 +2106,7 @@ update t2 set b = b + 5 where a = 1; update t3 set b = b + 5 where a = 1; update t4 set b = b + 5 where a = 1; insert into t5(a) values(20); +commit; set autocommit = 0; insert into t1(a) values(7); insert into t2(a) values(8); diff --git a/mysql-test/r/not_embedded_server.result b/mysql-test/r/not_embedded_server.result index 60c92bd0196..fac38624695 100644 --- a/mysql-test/r/not_embedded_server.result +++ b/mysql-test/r/not_embedded_server.result @@ -1,6 +1,16 @@ -select 1; -1 -1 +call mtr.add_suppression("Can't open and lock privilege tables: Table 'host' was not locked with LOCK TABLES"); SHOW VARIABLES like 'slave_skip_errors'; Variable_name Value slave_skip_errors OFF +# +# WL#4284: Transactional DDL locking +# +# FLUSH PRIVILEGES should not implicitly unlock locked tables. +# +drop table if exists t1; +create table t1 (c1 int); +lock tables t1 read; +flush privileges; +ERROR HY000: Table 'host' was not locked with LOCK TABLES +unlock tables; +drop table t1; diff --git a/mysql-test/r/partition.result b/mysql-test/r/partition.result index b72cc607fe2..e855ffb23e3 100644 --- a/mysql-test/r/partition.result +++ b/mysql-test/r/partition.result @@ -78,6 +78,15 @@ show indexes from t1; Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment t1 1 a 1 a A 1 NULL NULL YES BTREE drop table t1; +create table t1 (a int) +partition by hash (a); +create index i on t1 (a); +insert into t1 values (1); +insert into t1 select * from t1; +create index i on t1 (a); +ERROR 42000: Duplicate key name 'i' +create index i2 on t1 (a); +drop table t1; CREATE TABLE t1 (a INT, FOREIGN KEY (a) REFERENCES t0 (a)) ENGINE=MyISAM PARTITION BY HASH (a); diff --git a/mysql-test/r/partition_column_prune.result b/mysql-test/r/partition_column_prune.result index 82c49453d43..844429d24a6 100644 --- a/mysql-test/r/partition_column_prune.result +++ b/mysql-test/r/partition_column_prune.result @@ -56,11 +56,11 @@ insert into t1 values (2,5), (2,15), (2,25), insert into t1 select * from t1; explain partitions select * from t1 where a=2; id select_type table partitions type possible_keys key key_len ref rows Extra -1 SIMPLE t1 p01,p02,p03,p11 ALL NULL NULL NULL NULL 13 Using where +1 SIMPLE t1 p01,p02,p03,p11 ALL NULL NULL NULL NULL 8 Using where explain partitions select * from t1 where a=4; id select_type table partitions type possible_keys key key_len ref rows Extra -1 SIMPLE t1 p11,p12,p13,p21 ALL NULL NULL NULL NULL 16 Using where +1 SIMPLE t1 p11,p12,p13,p21 ALL NULL NULL NULL NULL 14 Using where explain partitions select * from t1 where a=2 and b < 22; id select_type table partitions type possible_keys key key_len ref rows Extra -1 SIMPLE t1 p01,p02,p03 ALL NULL NULL NULL NULL 16 Using where +1 SIMPLE t1 p01,p02,p03 ALL NULL NULL NULL NULL 14 Using where drop table t1; diff --git a/mysql-test/r/partition_innodb_semi_consistent.result b/mysql-test/r/partition_innodb_semi_consistent.result index 471da4c1c2e..48a1bb3d258 100644 --- a/mysql-test/r/partition_innodb_semi_consistent.result +++ b/mysql-test/r/partition_innodb_semi_consistent.result @@ -102,7 +102,7 @@ a b # Switch to connection con1 # 3. test for updated key column: TRUNCATE t1; -TRUNCATE t2; +DELETE FROM t2; INSERT INTO t1 VALUES (1,'init'); BEGIN; UPDATE t1 SET a = 2, b = CONCAT(b, '+con1') WHERE a = 1; diff --git a/mysql-test/r/partition_pruning.result b/mysql-test/r/partition_pruning.result index d7790cd8075..4c22c25ddb9 100644 --- a/mysql-test/r/partition_pruning.result +++ b/mysql-test/r/partition_pruning.result @@ -1960,7 +1960,7 @@ id select_type table partitions type possible_keys key key_len ref rows Extra explain partitions select * from t1 X, t1 Y where X.a = Y.a and (X.a=1 or X.a=2); id select_type table partitions type possible_keys key key_len ref rows Extra -1 SIMPLE X p1,p2 ALL a NULL NULL NULL 4 Using where +1 SIMPLE X p1,p2 ALL a NULL NULL NULL 2 Using where 1 SIMPLE Y p1,p2 ref a a 4 test.X.a 2 drop table t1; create table t1 (a int) partition by hash(a) partitions 20; diff --git a/mysql-test/r/partition_sync.result b/mysql-test/r/partition_sync.result index 31cf0569464..d48362cb57c 100644 --- a/mysql-test/r/partition_sync.result +++ b/mysql-test/r/partition_sync.result @@ -23,3 +23,35 @@ a 1 # Connection 1 DROP TABLE t1; +# +# Bug #46654 False deadlock on concurrent DML/DDL +# with partitions, inconsistent behavior +# +DROP TABLE IF EXISTS tbl_with_partitions; +CREATE TABLE tbl_with_partitions ( i INT ) +PARTITION BY HASH(i); +INSERT INTO tbl_with_partitions VALUES (1); +# Connection 3 +LOCK TABLE tbl_with_partitions READ; +# Connection 1 +# Access table with disabled autocommit +SET AUTOCOMMIT = 0; +SELECT * FROM tbl_with_partitions; +i +1 +# Connection 2 +# Alter table, abort after prepare +set session debug="+d,abort_copy_table"; +ALTER TABLE tbl_with_partitions ADD COLUMN f INT; +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +# Connection 1 +# Try accessing the table after Alter aborted. +# This used to give ER_LOCK_DEADLOCK. +SELECT * FROM tbl_with_partitions; +i +1 +# Connection 3 +UNLOCK TABLES; +# Connection 1 +# Cleanup +DROP TABLE tbl_with_partitions; diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index 23ba7e14532..dfd08bfe8b2 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -3130,5 +3130,29 @@ DROP PROCEDURE p1; DROP PROCEDURE p2; # End of WL#4435. - -End of 6.0 tests. +# +# WL#4284: Transactional DDL locking +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (a INT); +BEGIN; +SELECT * FROM t1; +a +# Test that preparing a CREATE TABLE does not take a exclusive metdata lock. +PREPARE stmt1 FROM "CREATE TABLE t1 AS SELECT 1"; +EXECUTE stmt1; +ERROR 42S01: Table 't1' already exists +DEALLOCATE PREPARE stmt1; +DROP TABLE t1; +# +# WL#4284: Transactional DDL locking +# +# Test that metadata locks taken during prepare are released. +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (a INT); +BEGIN; +PREPARE stmt1 FROM "SELECT * FROM t1"; +DROP TABLE t1; +# +# End of 6.0 tests. diff --git a/mysql-test/r/ps_ddl.result b/mysql-test/r/ps_ddl.result index c7e8812320c..2ce43d363c3 100644 --- a/mysql-test/r/ps_ddl.result +++ b/mysql-test/r/ps_ddl.result @@ -269,8 +269,6 @@ Part 7: TABLE -> TABLE (TRIGGER dependencies) transitions ===================================================================== # Test 7-a: dependent PROCEDURE has changed # -# Note, this scenario is not supported, subject of Bug#12093 -# create table t1 (a int); create trigger t1_ai after insert on t1 for each row call p1(new.a); @@ -282,10 +280,9 @@ drop procedure p1; create procedure p1 (a int) begin end; set @var= 2; execute stmt using @var; -ERROR 42000: PROCEDURE test.p1 does not exist # Cleanup drop procedure p1; -call p_verify_reprepare_count(0); +call p_verify_reprepare_count(1); SUCCESS # Test 7-b: dependent FUNCTION has changed @@ -361,11 +358,13 @@ set @var=8; # XXX: bug, the SQL statement in the trigger is still # pointing at table 't3', since the view was expanded # at first statement execution. +# Since the view definition is inlined in the statement +# at prepare, changing the view definition does not cause +# repreparation. # Repreparation of the main statement doesn't cause repreparation # of trigger statements. execute stmt using @var; -ERROR 42S02: Table 'test.t3' doesn't exist -call p_verify_reprepare_count(1); +call p_verify_reprepare_count(0); SUCCESS # @@ -382,6 +381,7 @@ select * from t3; a 6 7 +8 flush table t1; set @var=9; execute stmt using @var; @@ -396,6 +396,7 @@ select * from t3; a 6 7 +8 drop view v1; drop table t1,t2,t3; # Test 7-d: dependent TABLE has changed @@ -707,6 +708,9 @@ deallocate prepare stmt; ===================================================================== Part 16: VIEW -> TEMPORARY TABLE transitions ===================================================================== +# +# Test 1: Merged view +# create table t2 (a int); insert into t2 (a) values (1); create view t1 as select * from t2; @@ -720,18 +724,74 @@ SUCCESS create temporary table t1 (a int); execute stmt; a -call p_verify_reprepare_count(1); +1 +call p_verify_reprepare_count(0); SUCCESS drop view t1; execute stmt; +ERROR 42S02: Table 'test.t1' doesn't exist +call p_verify_reprepare_count(0); +SUCCESS + +drop table t2; +drop temporary table t1; +deallocate prepare stmt; +# +# Test 2: Materialized view +# +create table t2 (a int); +insert into t2 (a) values (1); +create algorithm = temptable view t1 as select * from t2; +prepare stmt from "select * from t1"; +execute stmt; +a +1 +call p_verify_reprepare_count(0); +SUCCESS + +create temporary table t1 (a int); +execute stmt; a +1 +call p_verify_reprepare_count(0); +SUCCESS + +drop view t1; +execute stmt; +ERROR 42S02: Table 'test.t1' doesn't exist call p_verify_reprepare_count(0); SUCCESS drop table t2; drop temporary table t1; deallocate prepare stmt; +# +# Test 3: View referencing an Information schema table +# +create view t1 as select table_name from information_schema.views; +prepare stmt from "select * from t1"; +execute stmt; +table_name +t1 +call p_verify_reprepare_count(0); +SUCCESS + +create temporary table t1 (a int); +execute stmt; +table_name +t1 +call p_verify_reprepare_count(0); +SUCCESS + +drop view t1; +execute stmt; +table_name +call p_verify_reprepare_count(0); +SUCCESS + +drop temporary table t1; +deallocate prepare stmt; ===================================================================== Part 17: VIEW -> VIEW (DDL) transitions ===================================================================== @@ -1706,7 +1766,7 @@ SUCCESS drop temporary table t2; execute stmt; -call p_verify_reprepare_count(1); +call p_verify_reprepare_count(0); SUCCESS drop table t2; @@ -1755,21 +1815,21 @@ SUCCESS drop table t1; deallocate prepare stmt; -# XXX: no validation of the first table in case of -# CREATE TEMPORARY TABLE. This is a shortcoming of the current code, -# but since validation is not strictly necessary, nothing is done -# about it. -# Will be fixed as part of work on Bug#21431 "Incomplete support of -# temporary tables" create table t1 (a int); insert into t1 (a) values (1); prepare stmt from "create temporary table if not exists t2 as select * from t1"; execute stmt; drop table t2; execute stmt; +call p_verify_reprepare_count(0); +SUCCESS + execute stmt; Warnings: Note 1050 Table 't2' already exists +call p_verify_reprepare_count(1); +SUCCESS + select * from t2; a 1 @@ -1777,6 +1837,9 @@ a execute stmt; Warnings: Note 1050 Table 't2' already exists +call p_verify_reprepare_count(0); +SUCCESS + select * from t2; a 1 @@ -1790,7 +1853,7 @@ Note 1050 Table 't2' already exists select * from t2; a 1 -call p_verify_reprepare_count(0); +call p_verify_reprepare_count(1); SUCCESS drop table t1; diff --git a/mysql-test/r/ps_ddl1.result b/mysql-test/r/ps_ddl1.result index e41a72ceb96..87abcd90590 100644 --- a/mysql-test/r/ps_ddl1.result +++ b/mysql-test/r/ps_ddl1.result @@ -460,7 +460,7 @@ create schema mysqltest; end| execute stmt; ERROR 42000: PROCEDURE test.p1 does not exist -call p_verify_reprepare_count(1); +call p_verify_reprepare_count(0); SUCCESS execute stmt; diff --git a/mysql-test/r/repair.result b/mysql-test/r/repair.result index 5bb3dd76fed..6ab5ecb7e18 100644 --- a/mysql-test/r/repair.result +++ b/mysql-test/r/repair.result @@ -157,3 +157,29 @@ REPAIR TABLE tt1 USE_FRM; Table Op Msg_type Msg_text tt1 repair error Cannot repair temporary table from .frm file DROP TABLE tt1; +# +# Bug #48248 assert in MDL_ticket::upgrade_shared_lock_to_exclusive +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1(a INT); +LOCK TABLES t1 READ; +REPAIR TABLE t1; +Table Op Msg_type Msg_text +test.t1 repair Error Table 't1' was locked with a READ lock and can't be updated +test.t1 repair status Operation failed +UNLOCK TABLES; +DROP TABLE t1; +# +# Test for bug #50784 "MDL: Assertion `m_tickets.is_empty() || +# m_tickets.front() == m_trans_sentinel'" +# +drop tables if exists t1, t2; +create table t1 (i int); +create table t2 (j int); +set @@autocommit= 0; +repair table t1, t2; +Table Op Msg_type Msg_text +test.t1 repair status OK +test.t2 repair status OK +set @@autocommit= default; +drop tables t1, t2; diff --git a/mysql-test/r/schema.result b/mysql-test/r/schema.result index 564fb3626df..b43f601caef 100644 --- a/mysql-test/r/schema.result +++ b/mysql-test/r/schema.result @@ -11,3 +11,39 @@ mtr mysql test drop schema foo; +# +# Bug #48940 MDL deadlocks against mysql_rm_db +# +DROP SCHEMA IF EXISTS schema1; +# Connection default +CREATE SCHEMA schema1; +CREATE TABLE schema1.t1 (a INT); +SET autocommit= FALSE; +INSERT INTO schema1.t1 VALUES (1); +# Connection 2 +DROP SCHEMA schema1; +# Connection default +ALTER SCHEMA schema1 DEFAULT CHARACTER SET utf8; +Got one of the listed errors +SET autocommit= TRUE; +# Connection 2 +# Connection default +# +# Bug #49988 MDL deadlocks with mysql_create_db, reload_acl_and_cache +# +DROP SCHEMA IF EXISTS schema1; +# Connection default +CREATE SCHEMA schema1; +CREATE TABLE schema1.t1 (id INT); +LOCK TABLE schema1.t1 WRITE; +# Connection con2 +DROP SCHEMA schema1; +# Connection default +# CREATE SCHEMA used to give a deadlock. +# Now we prohibit CREATE SCHEMA in LOCK TABLES mode. +CREATE SCHEMA IF NOT EXISTS schema1; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +# UNLOCK TABLES so DROP SCHEMA can continue. +UNLOCK TABLES; +# Connection con2 +# Connection default diff --git a/mysql-test/r/sp-error.result b/mysql-test/r/sp-error.result index 00ae7dd4eca..4cefee95903 100644 --- a/mysql-test/r/sp-error.result +++ b/mysql-test/r/sp-error.result @@ -512,7 +512,7 @@ select * from t1; end| lock table t1 read| alter procedure bug9566 comment 'Some comment'| -ERROR HY000: Table 'proc' was not locked with LOCK TABLES +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction unlock tables| drop procedure bug9566| drop procedure if exists bug7299| @@ -1687,6 +1687,17 @@ NULL SELECT non_existent (a) FROM t1 WHERE b = 999999; ERROR 42000: FUNCTION test.non_existent does not exist DROP TABLE t1; +CREATE TABLE t1 ( f2 INTEGER, f3 INTEGER ); +INSERT INTO t1 VALUES ( 1, 1 ); +CREATE FUNCTION func_1 () RETURNS INTEGER +BEGIN +INSERT INTO t1 SELECT * FROM t1 ; +RETURN 1 ; +END| +INSERT INTO t1 SELECT * FROM (SELECT 2 AS f1, 2 AS f2) AS A WHERE func_1() = 5; +ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +DROP FUNCTION func_1; +DROP TABLE t1; # # Bug #47788: Crash in TABLE_LIST::hide_view_error on UPDATE + VIEW + # SP + MERGE + ALTER diff --git a/mysql-test/r/sp-lock.result b/mysql-test/r/sp-lock.result new file mode 100644 index 00000000000..e9087f61807 --- /dev/null +++ b/mysql-test/r/sp-lock.result @@ -0,0 +1,697 @@ +# +# Test coverage for changes performed by the fix +# for Bug#30977 "Concurrent statement using stored function +# and DROP FUNCTION breaks SBR. +# +# +# 1) Verify that the preceding transaction is +# (implicitly) committed before CREATE/ALTER/DROP +# PROCEDURE. Note, that this is already tested +# in implicit_commit.test, but here we use an alternative +# approach. +# +# Start a transaction, create a savepoint, +# then call a DDL operation on a procedure, and then check +# that the savepoint is no longer present. +drop table if exists t1; +drop procedure if exists p1; +drop procedure if exists p2; +drop procedure if exists p3; +drop procedure if exists p4; +drop function if exists f1; +create table t1 (a int); +# +# Test 'CREATE PROCEDURE'. +# +begin; +savepoint sv; +create procedure p1() begin end; +rollback to savepoint sv; +ERROR 42000: SAVEPOINT sv does not exist +# +# Test 'ALTER PROCEDURE'. +# +begin; +savepoint sv; +alter procedure p1 comment 'changed comment'; +rollback to savepoint sv; +ERROR 42000: SAVEPOINT sv does not exist +# +# Test 'DROP PROCEDURE'. +# +begin; +savepoint sv; +drop procedure p1; +rollback to savepoint sv; +ERROR 42000: SAVEPOINT sv does not exist +# +# Test 'CREATE FUNCTION'. +# +begin; +savepoint sv; +create function f1() returns int return 1; +rollback to savepoint sv; +ERROR 42000: SAVEPOINT sv does not exist +# +# Test 'ALTER FUNCTION'. +# +begin; +savepoint sv; +alter function f1 comment 'new comment'; +rollback to savepoint sv; +ERROR 42000: SAVEPOINT sv does not exist +# +# Test 'DROP FUNCTION'. +# +begin; +savepoint sv; +drop function f1; +rollback to savepoint sv; +ERROR 42000: SAVEPOINT sv does not exist +# +# 2) Verify that procedure DDL operations fail +# under lock tables. +# +# Auxiliary routines to test ALTER. +create procedure p1() begin end; +create function f1() returns int return 1; +lock table t1 write; +create procedure p2() begin end; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +alter procedure p1 comment 'changed comment'; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +drop procedure p1; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +create function f2() returns int return 1; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +alter function f1 comment 'changed comment'; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +lock table t1 read; +create procedure p2() begin end; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +alter procedure p1 comment 'changed comment'; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +drop procedure p1; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +create function f2() returns int return 1; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +alter function f1 comment 'changed comment'; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +unlock tables; +# +# Even if we locked a temporary table. +# Todo: this is a restriction we could possibly lift. +# +drop table t1; +create temporary table t1 (a int); +lock table t1 read; +create procedure p2() begin end; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +alter procedure p1 comment 'changed comment'; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +drop procedure p1; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +create function f2() returns int return 1; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +alter function f1 comment 'changed comment'; +ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction +unlock tables; +drop function f1; +drop procedure p1; +drop temporary table t1; +# +# 3) Verify that CREATE/ALTER/DROP routine grab an +# exclusive lock. +# +# For that, start a transaction, use a routine. In a concurrent +# connection, try to drop or alter the routine. It should place +# a pending or exclusive lock and block. In another concurrnet +# connection, try to use the routine. +# That should block on the pending exclusive lock. +# +# Establish helper connections. +# +# Test DROP PROCEDURE. +# +# --> connection default +create procedure p1() begin end; +create function f1() returns int +begin +call p1(); +return 1; +end| +begin; +select f1(); +f1() +1 +# --> connection con1 +# Sending 'drop procedure p1'... +drop procedure p1; +# --> connection con2 +# Waitng for 'drop procedure t1' to get blocked on MDL lock... +# Demonstrate that there is a pending exclusive lock. +# Sending 'select f1()'... +select f1(); +# --> connection con3 +# Waitng for 'select f1()' to get blocked by a pending MDL lock... +# --> connection default +commit; +# --> connection con1 +# Reaping 'drop procedure p1'... +# --> connection con2 +# Reaping 'select f1()' +ERROR 42000: PROCEDURE test.p1 does not exist +# --> connection default +# +# Test CREATE PROCEDURE. +# +create procedure p1() begin end; +begin; +select f1(); +f1() +1 +# --> connection con1 +# Sending 'create procedure p1'... +create procedure p1() begin end; +# --> connection con2 +# Waitng for 'create procedure t1' to get blocked on MDL lock... +# Demonstrate that there is a pending exclusive lock. +# Sending 'select f1()'... +select f1(); +# --> connection con3 +# Waitng for 'select f1()' to get blocked by a pending MDL lock... +# --> connection default +commit; +# --> connection con1 +# Reaping 'create procedure p1'... +ERROR 42000: PROCEDURE p1 already exists +# --> connection con2 +# Reaping 'select f1()' +f1() +1 +# +# Test ALTER PROCEDURE. +# +begin; +select f1(); +f1() +1 +# --> connection con1 +# Sending 'alter procedure p1'... +alter procedure p1 contains sql; +# --> connection con2 +# Waitng for 'alter procedure t1' to get blocked on MDL lock... +# Demonstrate that there is a pending exclusive lock. +# Sending 'select f1()'... +select f1(); +# --> connection con3 +# Waitng for 'select f1()' to get blocked by a pending MDL lock... +# --> connection default +commit; +# --> connection con1 +# Reaping 'alter procedure p1'... +# --> connection con2 +# Reaping 'select f1()' +f1() +1 +# --> connection default +# +# Test DROP FUNCTION. +# +begin; +select f1(); +f1() +1 +# --> connection con1 +# Sending 'drop function f1'... +drop function f1; +# --> connection con2 +# Waitng for 'drop function f1' to get blocked on MDL lock... +# Demonstrate that there is a pending exclusive lock. +# Sending 'select f1()'... +select f1(); +# --> connection con3 +# Waitng for 'select f1()' to get blocked by a pending MDL lock... +# --> connection default +commit; +# --> connection con1 +# Reaping 'drop function f1'... +# --> connection con2 +# Reaping 'select f1()' +ERROR 42000: FUNCTION test.f1 does not exist +# --> connection default +# +# Test CREATE FUNCTION. +# +create function f1() returns int return 1; +begin; +select f1(); +f1() +1 +# --> connection con1 +# Sending 'create function f1'... +create function f1() returns int return 2; +# --> connection con2 +# Waitng for 'create function f1' to get blocked on MDL lock... +# Demonstrate that there is a pending exclusive lock. +# Sending 'select f1()'... +select f1(); +# --> connection con3 +# Waitng for 'select f1()' to get blocked by a pending MDL lock... +# --> connection default +commit; +# --> connection con1 +# Reaping 'create function f1'... +ERROR 42000: FUNCTION f1 already exists +# --> connection con2 +# Reaping 'select f1()' +f1() +1 +# --> connection default +# +# Test ALTER FUNCTION. +# +begin; +select f1(); +f1() +1 +# --> connection con1 +# Sending 'alter function f1'... +alter function f1 contains sql; +# --> connection con2 +# Waitng for 'alter function f1' to get blocked on MDL lock... +# Demonstrate that there is a pending exclusive lock. +# Sending 'select f1()'... +select f1(); +# --> connection con3 +# Waitng for 'select f1()' to get blocked by a pending MDL lock... +# --> connection default +commit; +# --> connection con1 +# Reaping 'alter function f1'... +# --> connection con2 +# Reaping 'select f1()' +f1() +1 +# --> connection default +drop function f1; +drop procedure p1; +# +# 4) MDL lock should not be taken for +# unrolled CALL statements. +# The primary goal of metadata locks is a consistent binary log. +# When a call statement is unrolled, it doesn't get to the +# binary log, instead the statements that are contained +# in the procedure body do. This can nest to any level. +# +create procedure p1() begin end; +create procedure p2() begin end; +create table t1 (a int); +create procedure p3() +begin +call p1(); +call p1(); +call p2(); +end| +create procedure p4() +begin +call p1(); +call p1(); +call p2(); +call p2(); +call p3(); +end| +begin; +select * from t1; +a +savepoint sv; +call p4(); +# Prepared statement should not add any locks either. +prepare stmt from "call p4()"; +execute stmt; +execute stmt; +# --> connection con1 +drop procedure p1; +drop procedure p2; +drop procedure p3; +drop procedure p4; +# --> connection default +# This is to verify there was no implicit commit. +rollback to savepoint sv; +call p4(); +ERROR 42000: PROCEDURE test.p4 does not exist +commit; +drop table t1; +# +# 5) Locks should be taken on routines +# used indirectly by views or triggers. +# +# +# A function is used from a trigger. +# +create function f1() returns int return 1; +create table t1 (a int); +create table t2 (a int, b int); +create trigger t1_ai after insert on t1 for each row +insert into t2 (a, b) values (new.a, f1()); +begin; +insert into t1 (a) values (1); +# --> connection con1 +# Sending 'drop function f1' +drop function f1; +# --> connection con2 +# Waitng for 'drop function f1' to get blocked on MDL lock... +# --> connnection default +commit; +# --> connection con1 +# Reaping 'drop function f1'... +# --> connection default +# +# A function is used from a view. +# +create function f1() returns int return 1; +create view v1 as select f1() as a; +begin; +select * from v1; +a +1 +# --> connection con1 +# Sending 'drop function f1' +drop function f1; +# --> connection con2 +# Waitng for 'drop function f1' to get blocked on MDL lock... +# --> connnection default +commit; +# --> connection con1 +# Reaping 'drop function f1'... +# --> connection default +# +# A procedure is used from a function. +# +create function f1() returns int +begin +declare v_out int; +call p1(v_out); +return v_out; +end| +create procedure p1(out v_out int) set v_out=3; +begin; +select * from v1; +a +3 +# --> connection con1 +# Sending 'drop procedure p1' +drop procedure p1; +# --> connection con2 +# Waitng for 'drop procedure p1' to get blocked on MDL lock... +# --> connnection default +commit; +# --> connection con1 +# Reaping 'drop procedure p1'... +# --> connection default +# +# Deep nesting: a function is used from a procedure used +# from a function used from a view used in a trigger. +# +create function f2() returns int return 4; +create procedure p1(out v_out int) set v_out=f2(); +drop trigger t1_ai; +create trigger t1_ai after insert on t1 for each row +insert into t2 (a, b) values (new.a, (select max(a) from v1)); +begin; +insert into t1 (a) values (3); +# --> connection con1 +# Sending 'drop function f2' +drop function f2; +# --> connection con2 +# Waitng for 'drop function f2' to get blocked on MDL lock... +# --> connnection default +commit; +# --> connection con1 +# Reaping 'drop function f2'... +# --> connection default +drop view v1; +drop function f1; +drop procedure p1; +drop table t1, t2; +# +# 6) Check that ER_LOCK_DEADLOCK is reported if +# acquisition of a shared lock fails during a transaction or +# we need to back off to flush the sp cache. +# +# Sic: now this situation does not require a back off since we +# flush the cache on the fly. +# +create function f1() returns int return 7; +create table t1 (a int); +begin; +select * from t1; +a +select f1(); +f1() +7 +commit; +drop table t1; +drop function f1; +# +# 7) Demonstrate that under LOCK TABLES we accumulate locks +# on stored routines, and release metadata locks in +# ROLLBACK TO SAVEPOINT. That is done only for those stored +# routines that are not part of LOCK TABLES prelocking list. +# Those stored routines that are part of LOCK TABLES +# prelocking list are implicitly locked when entering +# LOCK TABLES, and ROLLBACK TO SAVEPOINT has no effect on +# them. +# +create function f1() returns varchar(20) return "f1()"; +create function f2() returns varchar(20) return "f2()"; +create view v1 as select f1() as a; +set @@session.autocommit=0; +lock table v1 read; +select * from v1; +a +f1() +savepoint sv; +select f2(); +f2() +f2() +# --> connection con1 +# Sending 'drop function f1'... +drop function f1; +# --> connection con2 +# Waitng for 'drop function f1' to get blocked on MDL lock... +# Sending 'drop function f2'... +drop function f2; +# --> connection default +# Waitng for 'drop function f2' to get blocked on MDL lock... +rollback to savepoint sv; +# --> connection con2 +# Reaping 'drop function f2'... +# --> connection default +unlock tables; +# --> connection con1 +# Reaping 'drop function f1'... +# --> connection default +drop function f1; +ERROR 42000: FUNCTION test.f1 does not exist +drop function f2; +ERROR 42000: FUNCTION test.f2 does not exist +drop view v1; +set @@session.autocommit=default; +# +# 8) Check the situation when we're preparing or executing a +# prepared statement, and as part of that try to flush the +# session sp cache. However, one of the procedures that +# needs a flush is in use. Verify that there is no infinite +# reprepare loop and no crash. +# +create function f1() returns int return 1; +# +# We just mention p1() in the body of f2() to make +# sure that p1() metadata is validated when validating +# 'select f2()'. +# Recursion is not allowed in stored functions, so +# an attempt to just invoke p1() from f2() which is in turn +# called from p1() would have given a run-time error. +# +create function f2() returns int +begin +if @var is null then +call p1(); +end if; +return 1; +end| +create procedure p1() +begin +select f1() into @var; +execute stmt; +end| +# --> connection con2 +prepare stmt from "select f2()"; +# --> connection default +begin; +select f1(); +f1() +1 +# --> connection con1 +# Sending 'alter function f1 ...'... +alter function f1 comment "comment"; +# --> connection con2 +# Waitng for 'alter function f1 ...' to get blocked on MDL lock... +# Sending 'call p1()'... +call p1(); +# Waitng for 'call p1()' to get blocked on MDL lock on f1... +# Let 'alter function f1 ...' go through... +commit; +# --> connection con1 +# Reaping 'alter function f1 ...' +# --> connection con2 +# Reaping 'call p1()'... +f2() +1 +deallocate prepare stmt; +# --> connection default +drop function f1; +drop function f2; +drop procedure p1; +# +# 9) Check the situation when a stored function is invoked +# from a stored procedure, and recursively invokes the +# stored procedure that is in use. But for the second +# invocation, a cache flush is requested. We can't +# flush the procedure that's in use, and are forced +# to use an old version. It is not a violation of +# consistency, since we unroll top-level calls. +# Just verify the code works. +# +create function f1() returns int return 1; +begin; +select f1(); +f1() +1 +# --> connection con1 +# Sending 'alter function f1 ...'... +alter function f1 comment "comment"; +# --> connection con2 +# Waitng for 'alter function f1 ...' to get blocked on MDL lock... +# +# We just mention p1() in the body of f2() to make +# sure that p1() is prelocked for f2(). +# Recursion is not allowed in stored functions, so +# an attempt to just invoke p1() from f2() which is in turn +# called from p1() would have given a run-time error. +# +create function f2() returns int +begin +if @var is null then +call p1(); +end if; +return 1; +end| +create procedure p1() +begin +select f1() into @var; +select f2() into @var; +end| +# Sending 'call p1()'... +call p1(); +# Waitng for 'call p1()' to get blocked on MDL lock on f1... +# Let 'alter function f1 ...' go through... +commit; +# --> connection con1 +# Reaping 'alter function f1 ...' +# --> connection con2 +# Reaping 'call p1()'... +# --> connection default +drop function f1; +drop function f2; +drop procedure p1; +# +# 10) A select from information_schema.routines now +# flushes the stored routines caches. Test that this +# does not remove from the cache a stored routine +# that is already prelocked. +# +create function f1() returns int return get_lock("30977", 100000); +create function f2() returns int return 2; +create function f3() returns varchar(255) +begin +declare res varchar(255); +declare c cursor for select routine_name from +information_schema.routines where routine_name='f1'; +select f1() into @var; +open c; +fetch c into res; +close c; +select f2() into @var; +return res; +end| +# --> connection con1 +select get_lock("30977", 0); +get_lock("30977", 0) +1 +# --> connection default +# Sending 'select f3()'... +select f3(); +# --> connection con1 +# Waitng for 'select f3()' to get blocked on the user level lock... +# Do something to change the cache version. +create function f4() returns int return 4; +drop function f4; +select release_lock("30977"); +release_lock("30977") +1 +# --> connection default +# Reaping 'select f3()'... +# Routine 'f2()' should exist and get executed successfully. +f3() +f1 +select @var; +@var +2 +drop function f1; +drop function f2; +drop function f3; +# 11) Check the situation when the connection is flushing the +# SP cache which contains a procedure that is being executed. +# +# Function f1() calls p1(). Procedure p1() has a DROP +# VIEW statement, which, we know, invalidates the routines cache. +# During cache flush p1() must not be flushed since it's in +# use. +# +create function f1() returns int +begin +call p1(); +return 1; +end| +create procedure p1() +begin +create view v1 as select 1; +drop view v1; +select f1() into @var; +set @exec_count=@exec_count+1; +end| +set @exec_count=0; +call p1(); +ERROR HY000: Recursive limit 0 (as set by the max_sp_recursion_depth variable) was exceeded for routine p1 +select @exec_count; +@exec_count +0 +set @@session.max_sp_recursion_depth=5; +set @exec_count=0; +call p1(); +ERROR HY000: Explicit or implicit commit is not allowed in stored function or trigger. +select @exec_count; +@exec_count +0 +drop procedure p1; +drop function f1; +set @@session.max_sp_recursion_depth=default; +# --> connection con1 +# --> connection con2 +# --> connection con3 +# --> connection default +# +# End of 5.5 tests +# diff --git a/mysql-test/r/sp-threads.result b/mysql-test/r/sp-threads.result index 953830ecc87..a14d099c673 100644 --- a/mysql-test/r/sp-threads.result +++ b/mysql-test/r/sp-threads.result @@ -35,7 +35,7 @@ call bug9486(); show processlist; Id User Host db Command Time State Info # root localhost test Sleep # NULL -# root localhost test Query # Locked update t1, t2 set val= 1 where id1=id2 +# root localhost test Query # Waiting for table update t1, t2 set val= 1 where id1=id2 # root localhost test Query # NULL show processlist # root localhost test Sleep # NULL unlock tables; diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result index 8f756a4c303..8e07f500c08 100644 --- a/mysql-test/r/sp.result +++ b/mysql-test/r/sp.result @@ -1083,11 +1083,9 @@ select f0()| f0() 100 select * from v0| -f0() -100 +ERROR HY000: Table 'v0' was not locked with LOCK TABLES select *, f0() from v0, (select 123) as d1| -f0() 123 f0() -100 123 100 +ERROR HY000: Table 'v0' was not locked with LOCK TABLES select id, f3() from t1| ERROR HY000: Table 't1' was not locked with LOCK TABLES select f4()| @@ -7133,3 +7131,62 @@ SELECT routine_comment FROM information_schema.routines WHERE routine_name = "p1 routine_comment 12345678901234567890123456789012345678901234567890123456789012345678901234567890 DROP PROCEDURE p1; +# +# Bug #47313 assert in check_key_in_view during CALL procedure +# +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS t1, t2_unrelated; +DROP PROCEDURE IF EXISTS p1; +CREATE PROCEDURE p1(IN x INT) INSERT INTO t1 VALUES (x); +CREATE VIEW t1 AS SELECT 10 AS f1; +# t1 refers to the view +CALL p1(1); +ERROR HY000: The target table t1 of the INSERT is not insertable-into +CREATE TEMPORARY TABLE t1 (f1 INT); +# t1 still refers to the view since it was inlined +CALL p1(2); +ERROR HY000: The target table t1 of the INSERT is not insertable-into +DROP VIEW t1; +# t1 now refers to the temporary table +CALL p1(3); +# Check which values were inserted into the temp table. +SELECT * FROM t1; +f1 +3 +DROP TEMPORARY TABLE t1; +DROP PROCEDURE p1; +# Now test what happens if the sp cache is invalidated. +CREATE PROCEDURE p1(IN x INT) INSERT INTO t1 VALUES (x); +CREATE VIEW t1 AS SELECT 10 AS f1; +CREATE VIEW v2_unrelated AS SELECT 1 AS r1; +# Load the procedure into the sp cache +CALL p1(4); +ERROR HY000: The target table t1 of the INSERT is not insertable-into +CREATE TEMPORARY TABLE t1 (f1 int); +ALTER VIEW v2_unrelated AS SELECT 2 AS r1; +# Alter view causes the sp cache to be invalidated. +# Now t1 refers to the temporary table, not the view. +CALL p1(5); +# Check which values were inserted into the temp table. +SELECT * FROM t1; +f1 +5 +DROP TEMPORARY TABLE t1; +DROP VIEW t1, v2_unrelated; +DROP PROCEDURE p1; +CREATE PROCEDURE p1(IN x INT) INSERT INTO t1 VALUES (x); +CREATE TEMPORARY TABLE t1 (f1 INT); +# t1 refers to the temporary table +CALL p1(6); +CREATE VIEW t1 AS SELECT 10 AS f1; +# Create view causes the sp cache to be invalidated. +# t1 still refers to the temporary table since it shadows the view. +CALL p1(7); +DROP VIEW t1; +# Check which values were inserted into the temp table. +SELECT * FROM t1; +f1 +6 +7 +DROP TEMPORARY TABLE t1; +DROP PROCEDURE p1; diff --git a/mysql-test/r/trigger-compat.result b/mysql-test/r/trigger-compat.result index 14ef4f54f27..14949c227bd 100644 --- a/mysql-test/r/trigger-compat.result +++ b/mysql-test/r/trigger-compat.result @@ -34,8 +34,6 @@ TRIGGER_CATALOG TRIGGER_SCHEMA TRIGGER_NAME EVENT_MANIPULATION EVENT_OBJECT_CATA def mysqltest_db1 wl2818_trg1 INSERT def mysqltest_db1 t1 0 NULL INSERT INTO t2 VALUES(CURRENT_USER()) ROW BEFORE NULL NULL OLD NEW NULL latin1 latin1_swedish_ci latin1_swedish_ci def mysqltest_db1 wl2818_trg2 INSERT def mysqltest_db1 t1 0 NULL INSERT INTO t2 VALUES(CURRENT_USER()) ROW AFTER NULL NULL OLD NEW NULL mysqltest_dfn@localhost latin1 latin1_swedish_ci latin1_swedish_ci DROP TRIGGER wl2818_trg1; -Warnings: -Warning 1454 No definer attribute for trigger 'mysqltest_db1'.'wl2818_trg1'. The trigger will be activated under the authorization of the invoker, which may have insufficient privileges. Please recreate the trigger. DROP TRIGGER wl2818_trg2; use mysqltest_db1; DROP TABLE t1; diff --git a/mysql-test/r/trigger.result b/mysql-test/r/trigger.result index 47b50b233b3..5c63604a325 100644 --- a/mysql-test/r/trigger.result +++ b/mysql-test/r/trigger.result @@ -2162,3 +2162,23 @@ Warning 1265 Data truncated for column 'trg2' at row 1 DROP TRIGGER trg1; DROP TRIGGER trg2; DROP TABLE t1; +# +# Bug #46747 "Crash in MDL_ticket::upgrade_shared_lock_to_exclusive +# on TRIGGER + TEMP table". +# +drop trigger if exists t1_bi; +drop temporary table if exists t1; +drop table if exists t1; +create table t1 (i int); +create trigger t1_bi before insert on t1 for each row set @a:=1; +# Create temporary table which shadows base table with trigger. +create temporary table t1 (j int); +# Dropping of trigger should succeed. +drop trigger t1_bi; +select trigger_name from information_schema.triggers +where event_object_schema = 'test' and event_object_table = 't1'; +trigger_name +# Clean-up. +drop temporary table t1; +drop table t1; +End of 6.0 tests. diff --git a/mysql-test/r/truncate.result b/mysql-test/r/truncate.result index b194f9b7dc6..8f237c81e75 100644 --- a/mysql-test/r/truncate.result +++ b/mysql-test/r/truncate.result @@ -1,4 +1,4 @@ -drop table if exists t1; +drop table if exists t1, t2; create table t1 (a integer, b integer,c1 CHAR(10)); insert into t1 (a) values (1),(2); truncate table t1; @@ -60,3 +60,102 @@ truncate table v1; ERROR 42S02: Table 'test.v1' doesn't exist drop view v1; drop table t1; +# +# Bug#20667 - Truncate table fails for a write locked table +# +CREATE TABLE t1 (c1 INT); +LOCK TABLE t1 WRITE; +INSERT INTO t1 VALUES (1); +SELECT * FROM t1; +c1 +1 +TRUNCATE TABLE t1; +SELECT * FROM t1; +c1 +UNLOCK TABLES; +LOCK TABLE t1 READ; +TRUNCATE TABLE t1; +ERROR HY000: Table 't1' was locked with a READ lock and can't be updated +UNLOCK TABLES; +CREATE TABLE t2 (c1 INT); +LOCK TABLE t2 WRITE; +TRUNCATE TABLE t1; +ERROR HY000: Table 't1' was not locked with LOCK TABLES +UNLOCK TABLES; +CREATE VIEW v1 AS SELECT t1.c1 FROM t1,t2 WHERE t1.c1 = t2.c1; +INSERT INTO t1 VALUES (1), (2), (3); +INSERT INTO t2 VALUES (1), (3), (4); +SELECT * FROM v1; +c1 +1 +3 +TRUNCATE v1; +ERROR 42S02: Table 'test.v1' doesn't exist +SELECT * FROM v1; +c1 +1 +3 +LOCK TABLE t1 WRITE; +SELECT * FROM v1; +ERROR HY000: Table 'v1' was not locked with LOCK TABLES +TRUNCATE v1; +ERROR 42S02: Table 'test.v1' doesn't exist +SELECT * FROM v1; +ERROR HY000: Table 'v1' was not locked with LOCK TABLES +UNLOCK TABLES; +LOCK TABLE t1 WRITE, t2 WRITE; +SELECT * FROM v1; +ERROR HY000: Table 'v1' was not locked with LOCK TABLES +TRUNCATE v1; +ERROR 42S02: Table 'test.v1' doesn't exist +SELECT * FROM v1; +ERROR HY000: Table 'v1' was not locked with LOCK TABLES +UNLOCK TABLES; +LOCK TABLE v1 WRITE; +SELECT * FROM v1; +c1 +1 +3 +TRUNCATE v1; +ERROR 42S02: Table 'test.v1' doesn't exist +SELECT * FROM v1; +c1 +1 +3 +UNLOCK TABLES; +LOCK TABLE t1 WRITE, t2 WRITE, v1 WRITE; +SELECT * FROM v1; +c1 +1 +3 +TRUNCATE v1; +ERROR 42S02: Table 'test.v1' doesn't exist +SELECT * FROM v1; +c1 +1 +3 +UNLOCK TABLES; +DROP VIEW v1; +DROP TABLE t1, t2; +CREATE PROCEDURE p1() SET @a = 5; +TRUNCATE p1; +ERROR 42S02: Table 'test.p1' doesn't exist +SHOW CREATE PROCEDURE p1; +Procedure sql_mode Create Procedure character_set_client collation_connection Database Collation +p1 CREATE DEFINER=`root`@`localhost` PROCEDURE `p1`() +SET @a = 5 latin1 latin1_swedish_ci latin1_swedish_ci +DROP PROCEDURE p1; +# +# Bug#46452 Crash in MDL, HANDLER OPEN + TRUNCATE TABLE +# +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 AS SELECT 1 AS f1; +HANDLER t1 OPEN; +# Here comes the crash. +TRUNCATE t1; +# Currently TRUNCATE, just like other DDL, implicitly closes +# open HANDLER table. +HANDLER t1 READ FIRST; +ERROR 42S02: Unknown table 't1' in HANDLER +DROP TABLE t1; +# End of 6.0 tests diff --git a/mysql-test/r/truncate_coverage.result b/mysql-test/r/truncate_coverage.result new file mode 100644 index 00000000000..7a5021f55e2 --- /dev/null +++ b/mysql-test/r/truncate_coverage.result @@ -0,0 +1,73 @@ +SET DEBUG_SYNC='RESET'; +DROP TABLE IF EXISTS t1; +# +# Bug#20667 - Truncate table fails for a write locked table +# +CREATE TABLE t1 (c1 INT); +INSERT INTO t1 VALUES (1); +# +# connection con1 +HANDLER t1 OPEN; +# +# connection default +LOCK TABLE t1 WRITE; +SET DEBUG_SYNC='mdl_upgrade_shared_lock_to_exclusive SIGNAL waiting'; +TRUNCATE TABLE t1; +# +# connection con2 +SET DEBUG_SYNC='now WAIT_FOR waiting'; +KILL QUERY @id; +# +# connection con1 +# Release shared metadata lock by closing HANDLER. +HANDLER t1 CLOSE; +# +# connection default +ERROR 70100: Query execution was interrupted +UNLOCK TABLES; +DROP TABLE t1; +SET DEBUG_SYNC='RESET'; +CREATE TABLE t1 (c1 INT); +INSERT INTO t1 VALUES (1); +# +# connection con1 +HANDLER t1 OPEN; +# +# connection default +LOCK TABLE t1 WRITE; +SET DEBUG_SYNC='mdl_upgrade_shared_lock_to_exclusive SIGNAL waiting'; +TRUNCATE TABLE t1; +# +# connection con2 +SET DEBUG_SYNC='now WAIT_FOR waiting'; +# +# connection con1 +HANDLER t1 CLOSE; +# +# connection default +ERROR 42S02: Table 'test.t1' doesn't exist +UNLOCK TABLES; +DROP TABLE t1; +ERROR 42S02: Unknown table 't1' +SET DEBUG_SYNC='RESET'; +CREATE TABLE t1 (c1 INT); +INSERT INTO t1 VALUES (1); +# +# connection con1 +START TRANSACTION; +INSERT INTO t1 VALUES (2); +# +# connection default +SET DEBUG_SYNC='mdl_acquire_lock_wait SIGNAL waiting'; +TRUNCATE TABLE t1; +# +# connection con1 +SET DEBUG_SYNC='now WAIT_FOR waiting'; +KILL QUERY @id; +COMMIT; +# +# connection default +ERROR 70100: Query execution was interrupted +UNLOCK TABLES; +DROP TABLE t1; +SET DEBUG_SYNC='RESET'; diff --git a/mysql-test/r/view.result b/mysql-test/r/view.result index a54db7240a4..4c0acc2eb6a 100644 --- a/mysql-test/r/view.result +++ b/mysql-test/r/view.result @@ -1102,10 +1102,8 @@ select * from v1; a select * from t2; ERROR HY000: Table 't2' was not locked with LOCK TABLES -drop view v1; -drop table t1, t2; -ERROR HY000: Table 't1' was locked with a READ lock and can't be updated unlock tables; +drop view v1; drop table t1, t2; create table t1 (a int); create view v1 as select * from t1 where a < 2 with check option; @@ -1957,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 error Corrupt +test.v1 check status Operation failed 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 error Corrupt +test.v3 check status Operation failed 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 error Corrupt +test.v5 check status Operation failed test.v6 check status OK create function f1 () returns int return (select max(col1) from t1); DROP TABLE t1; @@ -3974,3 +3972,35 @@ create view v_9801 as select sum(s1) from t_9801 group by s1 with rollup with check option; ERROR HY000: CHECK OPTION on non-updatable view 'test.v_9801' drop table t_9801; +# +# Bug #47335 assert in get_table_share +# +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS v1; +CREATE TEMPORARY TABLE t1 (id INT); +ALTER VIEW t1 AS SELECT 1 AS f1; +ERROR 42S02: Table 'test.t1' doesn't exist +DROP TABLE t1; +CREATE VIEW v1 AS SELECT 1 AS f1; +CREATE TEMPORARY TABLE v1 (id INT); +ALTER VIEW v1 AS SELECT 2 AS f1; +DROP TABLE v1; +SELECT * FROM v1; +f1 +2 +DROP VIEW v1; +# +# Bug #47635 assert in start_waiting_global_read_lock +# during CREATE VIEW +# +DROP TABLE IF EXISTS t1, t2; +DROP VIEW IF EXISTS t2; +CREATE TABLE t1 (f1 integer); +CREATE TEMPORARY TABLE IF NOT EXISTS t1 (f1 integer); +CREATE TEMPORARY TABLE t2 (f1 integer); +DROP TABLE t1; +FLUSH TABLES WITH READ LOCK; +CREATE VIEW t2 AS SELECT * FROM t1; +ERROR HY000: Can't execute the query because you have a conflicting read lock +UNLOCK TABLES; +DROP TABLE t1, t2; diff --git a/mysql-test/r/view_grant.result b/mysql-test/r/view_grant.result index 65fbf2d87b6..0c74d8ed91b 100644 --- a/mysql-test/r/view_grant.result +++ b/mysql-test/r/view_grant.result @@ -779,9 +779,9 @@ GRANT CREATE VIEW ON db26813.v2 TO u26813@localhost; GRANT DROP, CREATE VIEW ON db26813.v3 TO u26813@localhost; GRANT SELECT ON db26813.t1 TO u26813@localhost; ALTER VIEW v1 AS SELECT f2 FROM t1; -ERROR 42000: Access denied; you need (at least one of) the SUPER privilege(s) for this operation +ERROR 42000: CREATE VIEW command denied to user 'u26813'@'localhost' for table 'v1' ALTER VIEW v2 AS SELECT f2 FROM t1; -ERROR 42000: Access denied; you need (at least one of) the SUPER privilege(s) for this operation +ERROR 42000: DROP command denied to user 'u26813'@'localhost' for table 'v2' ALTER VIEW v3 AS SELECT f2 FROM t1; ERROR 42000: Access denied; you need (at least one of) the SUPER privilege(s) for this operation SHOW CREATE VIEW v3; diff --git a/mysql-test/suite/binlog/r/binlog_row_drop_tbl.result b/mysql-test/suite/binlog/r/binlog_row_drop_tbl.result new file mode 100644 index 00000000000..8b32e9e5a45 --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_row_drop_tbl.result @@ -0,0 +1,16 @@ +DROP TABLE IF EXISTS t1; +RESET MASTER; +CREATE TABLE t1 (a INT); +SET AUTOCOMMIT=OFF; +BEGIN; +INSERT INTO t1 VALUES(1); +DROP TABLE t1;; +COMMIT; +show binlog events from <binlog_start>; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # use `test`; CREATE TABLE t1 (a INT) +master-bin.000001 # Query # # BEGIN +master-bin.000001 # Table_map # # table_id: # (test.t1) +master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F +master-bin.000001 # Query # # COMMIT +master-bin.000001 # Query # # use `test`; DROP TABLE t1 diff --git a/mysql-test/suite/binlog/r/binlog_row_mix_innodb_myisam.result b/mysql-test/suite/binlog/r/binlog_row_mix_innodb_myisam.result index 4ccc3b5e797..c1254643a18 100644 --- a/mysql-test/suite/binlog/r/binlog_row_mix_innodb_myisam.result +++ b/mysql-test/suite/binlog/r/binlog_row_mix_innodb_myisam.result @@ -238,6 +238,7 @@ select (@after:=unix_timestamp())*0; select (@after-@before) >= 2; (@after-@before) >= 2 1 +commit; drop table t1,t2; commit; begin; diff --git a/mysql-test/suite/binlog/r/binlog_stm_drop_tbl.result b/mysql-test/suite/binlog/r/binlog_stm_drop_tbl.result new file mode 100644 index 00000000000..f4596b808cf --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_stm_drop_tbl.result @@ -0,0 +1,13 @@ +DROP TABLE IF EXISTS t1; +RESET MASTER; +CREATE TABLE t1 (a INT); +SET AUTOCOMMIT=OFF; +BEGIN; +INSERT INTO t1 VALUES(1); +DROP TABLE t1;; +COMMIT; +show binlog events from <binlog_start>; +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Query # # use `test`; CREATE TABLE t1 (a INT) +master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES(1) +master-bin.000001 # Query # # use `test`; DROP TABLE t1 diff --git a/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result b/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result index cdc793395ad..32c8ea3e00e 100644 --- a/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result +++ b/mysql-test/suite/binlog/r/binlog_stm_mix_innodb_myisam.result @@ -208,6 +208,7 @@ select (@after:=unix_timestamp())*0; select (@after-@before) >= 2; (@after-@before) >= 2 1 +commit; drop table t1,t2; commit; begin; diff --git a/mysql-test/suite/binlog/r/binlog_unsafe.result b/mysql-test/suite/binlog/r/binlog_unsafe.result index 5ad12d953ab..b525f7324c6 100644 --- a/mysql-test/suite/binlog/r/binlog_unsafe.result +++ b/mysql-test/suite/binlog/r/binlog_unsafe.result @@ -320,10 +320,10 @@ INSERT INTO t2 SET a = func_modify_t1(); SHOW BINLOG EVENTS FROM 12283; Log_name Pos Event_type Server_id End_log_pos Info master-bin.000001 12283 Query 1 12351 BEGIN -master-bin.000001 12351 Table_map 1 12393 table_id: 44 (test.t2) -master-bin.000001 12393 Table_map 1 12435 table_id: 45 (test.t1) -master-bin.000001 12435 Write_rows 1 12473 table_id: 45 -master-bin.000001 12473 Write_rows 1 12511 table_id: 44 flags: STMT_END_F +master-bin.000001 12351 Table_map 1 12393 table_id: 41 (test.t2) +master-bin.000001 12393 Table_map 1 12435 table_id: 42 (test.t1) +master-bin.000001 12435 Write_rows 1 12473 table_id: 42 +master-bin.000001 12473 Write_rows 1 12511 table_id: 41 flags: STMT_END_F master-bin.000001 12511 Query 1 12580 COMMIT DROP TABLE t1,t2; DROP FUNCTION func_modify_t1; @@ -347,12 +347,12 @@ INSERT INTO t1 SET a = 2; SHOW BINLOG EVENTS FROM 13426; Log_name Pos Event_type Server_id End_log_pos Info master-bin.000001 13426 Query 1 13494 BEGIN -master-bin.000001 13494 Table_map 1 13535 table_id: 47 (test.t1) -master-bin.000001 13535 Table_map 1 13577 table_id: 48 (test.t3) -master-bin.000001 13577 Table_map 1 13619 table_id: 49 (test.t2) -master-bin.000001 13619 Write_rows 1 13657 table_id: 49 -master-bin.000001 13657 Write_rows 1 13695 table_id: 48 -master-bin.000001 13695 Write_rows 1 13729 table_id: 47 flags: STMT_END_F +master-bin.000001 13494 Table_map 1 13535 table_id: 44 (test.t1) +master-bin.000001 13535 Table_map 1 13577 table_id: 45 (test.t3) +master-bin.000001 13577 Table_map 1 13619 table_id: 46 (test.t2) +master-bin.000001 13619 Write_rows 1 13657 table_id: 46 +master-bin.000001 13657 Write_rows 1 13695 table_id: 45 +master-bin.000001 13695 Write_rows 1 13729 table_id: 44 flags: STMT_END_F master-bin.000001 13729 Query 1 13798 COMMIT DROP TABLE t1,t2,t3; SET SESSION binlog_format = STATEMENT; diff --git a/mysql-test/suite/binlog/r/binlog_write_error.result b/mysql-test/suite/binlog/r/binlog_write_error.result deleted file mode 100644 index dd8bdb9715a..00000000000 --- a/mysql-test/suite/binlog/r/binlog_write_error.result +++ /dev/null @@ -1,108 +0,0 @@ -# -# Initialization -# -DROP TABLE IF EXISTS t1, t2; -DROP FUNCTION IF EXISTS f1; -DROP FUNCTION IF EXISTS f2; -DROP PROCEDURE IF EXISTS p1; -DROP PROCEDURE IF EXISTS p2; -DROP TRIGGER IF EXISTS tr1; -DROP TRIGGER IF EXISTS tr2; -DROP VIEW IF EXISTS v1, v2; -# -# Test injecting binlog write error when executing queries -# -SET GLOBAL debug='d,injecting_fault_writing'; -CREATE TABLE t1 (a INT); -CREATE TABLE t1 (a INT); -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -INSERT INTO t1 VALUES (1),(2),(3); -SET GLOBAL debug='d,injecting_fault_writing'; -INSERT INTO t1 VALUES (4),(5),(6); -INSERT INTO t1 VALUES (4),(5),(6); -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -SET GLOBAL debug='d,injecting_fault_writing'; -UPDATE t1 set a=a+1; -UPDATE t1 set a=a+1; -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -SET GLOBAL debug='d,injecting_fault_writing'; -DELETE FROM t1; -DELETE FROM t1; -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -SET GLOBAL debug='d,injecting_fault_writing'; -CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW INSERT INTO t1 VALUES (new.a + 100); -CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW INSERT INTO t1 VALUES (new.a + 100); -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -SET GLOBAL debug='d,injecting_fault_writing'; -DROP TRIGGER tr1; -DROP TRIGGER tr1; -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -SET GLOBAL debug='d,injecting_fault_writing'; -ALTER TABLE t1 ADD (b INT); -ALTER TABLE t1 ADD (b INT); -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -SET GLOBAL debug='d,injecting_fault_writing'; -CREATE VIEW v1 AS SELECT a FROM t1; -CREATE VIEW v1 AS SELECT a FROM t1; -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -SET GLOBAL debug='d,injecting_fault_writing'; -DROP VIEW v1; -DROP VIEW v1; -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -SET GLOBAL debug='d,injecting_fault_writing'; -CREATE PROCEDURE p1(OUT rows INT) SELECT count(*) INTO rows FROM t1; -CREATE PROCEDURE p1(OUT rows INT) SELECT count(*) INTO rows FROM t1; -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -SET GLOBAL debug='d,injecting_fault_writing'; -DROP PROCEDURE p1; -DROP PROCEDURE p1; -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -SET GLOBAL debug='d,injecting_fault_writing'; -DROP TABLE t1; -DROP TABLE t1; -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -SET GLOBAL debug='d,injecting_fault_writing'; -CREATE FUNCTION f1() RETURNS INT return 1; -CREATE FUNCTION f1() RETURNS INT return 1; -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -SET GLOBAL debug='d,injecting_fault_writing'; -DROP FUNCTION f1; -DROP FUNCTION f1; -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -SET GLOBAL debug='d,injecting_fault_writing'; -CREATE USER user1; -CREATE USER user1; -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -SET GLOBAL debug='d,injecting_fault_writing'; -REVOKE ALL PRIVILEGES, GRANT OPTION FROM user1; -REVOKE ALL PRIVILEGES, GRANT OPTION FROM user1; -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -SET GLOBAL debug='d,injecting_fault_writing'; -DROP USER user1; -DROP USER user1; -ERROR HY000: Error writing file 'master-bin' ((errno: #) -SET GLOBAL debug=''; -# -# Cleanup -# -DROP TABLE IF EXISTS t1, t2; -DROP FUNCTION IF EXISTS f1; -DROP PROCEDURE IF EXISTS p1; -DROP TRIGGER IF EXISTS tr1; -DROP VIEW IF EXISTS v1, v2; diff --git a/mysql-test/suite/binlog/t/binlog_row_drop_tbl.test b/mysql-test/suite/binlog/t/binlog_row_drop_tbl.test new file mode 100644 index 00000000000..06854900612 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_row_drop_tbl.test @@ -0,0 +1,5 @@ +# This is a wrapper for drop_table.test so that the same test case can be used +# For both statement and row based bin logs + +-- source include/have_binlog_format_row.inc +-- source extra/binlog_tests/drop_table.test diff --git a/mysql-test/suite/binlog/t/binlog_stm_drop_tbl.test b/mysql-test/suite/binlog/t/binlog_stm_drop_tbl.test new file mode 100644 index 00000000000..f2b07bb69f6 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_stm_drop_tbl.test @@ -0,0 +1,5 @@ +# This is a wrapper for drop_table.test so that the same test case can be used +# For both statement and row based bin logs + +-- source include/have_binlog_format_mixed_or_statement.inc +-- source extra/binlog_tests/drop_table.test diff --git a/mysql-test/suite/binlog/t/binlog_stm_row.test b/mysql-test/suite/binlog/t/binlog_stm_row.test index 29b0a69330d..b1ea9f50e7f 100644 --- a/mysql-test/suite/binlog/t/binlog_stm_row.test +++ b/mysql-test/suite/binlog/t/binlog_stm_row.test @@ -60,7 +60,7 @@ let $wait_condition= --echo # con1 let $wait_condition= SELECT COUNT(*) = 1 FROM information_schema.processlist WHERE - state = "Locked" and info = "INSERT INTO t2 VALUES (3)"; + state = "Table lock" and info = "INSERT INTO t2 VALUES (3)"; --source include/wait_condition.inc SELECT RELEASE_LOCK('Bug#34306'); --connection con2 diff --git a/mysql-test/suite/binlog/t/binlog_write_error.test b/mysql-test/suite/binlog/t/binlog_write_error.test deleted file mode 100644 index 0e57db3d9a0..00000000000 --- a/mysql-test/suite/binlog/t/binlog_write_error.test +++ /dev/null @@ -1,101 +0,0 @@ -# -# === Name === -# -# binlog_write_error.test -# -# === Description === -# -# This test case check if the error of writing binlog file is properly -# reported and handled when executing statements. -# -# === Related Bugs === -# -# BUG#37148 -# - -source include/have_log_bin.inc; -source include/have_debug.inc; - ---echo # ---echo # Initialization ---echo # - -disable_warnings; -DROP TABLE IF EXISTS t1, t2; -DROP FUNCTION IF EXISTS f1; -DROP FUNCTION IF EXISTS f2; -DROP PROCEDURE IF EXISTS p1; -DROP PROCEDURE IF EXISTS p2; -DROP TRIGGER IF EXISTS tr1; -DROP TRIGGER IF EXISTS tr2; -DROP VIEW IF EXISTS v1, v2; -enable_warnings; - ---echo # ---echo # Test injecting binlog write error when executing queries ---echo # - -let $query= CREATE TABLE t1 (a INT); -source include/binlog_inject_error.inc; - -INSERT INTO t1 VALUES (1),(2),(3); - -let $query= INSERT INTO t1 VALUES (4),(5),(6); -source include/binlog_inject_error.inc; - -let $query= UPDATE t1 set a=a+1; -source include/binlog_inject_error.inc; - -let $query= DELETE FROM t1; -source include/binlog_inject_error.inc; - -let $query= CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW INSERT INTO t1 VALUES (new.a + 100); -source include/binlog_inject_error.inc; - -let $query= DROP TRIGGER tr1; -source include/binlog_inject_error.inc; - -let $query= ALTER TABLE t1 ADD (b INT); -source include/binlog_inject_error.inc; - -let $query= CREATE VIEW v1 AS SELECT a FROM t1; -source include/binlog_inject_error.inc; - -let $query= DROP VIEW v1; -source include/binlog_inject_error.inc; - -let $query= CREATE PROCEDURE p1(OUT rows INT) SELECT count(*) INTO rows FROM t1; -source include/binlog_inject_error.inc; - -let $query= DROP PROCEDURE p1; -source include/binlog_inject_error.inc; - -let $query= DROP TABLE t1; -source include/binlog_inject_error.inc; - -let $query= CREATE FUNCTION f1() RETURNS INT return 1; -source include/binlog_inject_error.inc; - -let $query= DROP FUNCTION f1; -source include/binlog_inject_error.inc; - -let $query= CREATE USER user1; -source include/binlog_inject_error.inc; - -let $query= REVOKE ALL PRIVILEGES, GRANT OPTION FROM user1; -source include/binlog_inject_error.inc; - -let $query= DROP USER user1; -source include/binlog_inject_error.inc; - ---echo # ---echo # Cleanup ---echo # - -disable_warnings; -DROP TABLE IF EXISTS t1, t2; -DROP FUNCTION IF EXISTS f1; -DROP PROCEDURE IF EXISTS p1; -DROP TRIGGER IF EXISTS tr1; -DROP VIEW IF EXISTS v1, v2; -enable_warnings; diff --git a/mysql-test/suite/funcs_1/datadict/processlist_val.inc b/mysql-test/suite/funcs_1/datadict/processlist_val.inc index c34fb626bcd..6fcaf45c848 100644 --- a/mysql-test/suite/funcs_1/datadict/processlist_val.inc +++ b/mysql-test/suite/funcs_1/datadict/processlist_val.inc @@ -367,13 +367,13 @@ echo ; connection default; echo -# Poll till INFO is no more NULL and State = 'Locked'. +# Poll till INFO is no more NULL and State = 'Table Lock'. ; let $wait_condition= SELECT COUNT(*) FROM INFORMATION_SCHEMA.PROCESSLIST - WHERE INFO IS NOT NULL AND STATE = 'Locked'; + WHERE INFO IS NOT NULL AND STATE = 'Table Lock'; --source include/wait_condition.inc # -# Expect to see the state 'Locked' for the third connection because the SELECT +# Expect to see the state 'Table Lock' for the third connection because the SELECT # collides with the WRITE TABLE LOCK. --replace_column 1 <ID> 3 <HOST_NAME> 6 <TIME> SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST; @@ -422,10 +422,10 @@ echo ; connection default; echo -# Poll till INFO is no more NULL and State = 'Locked'. +# Poll till INFO is no more NULL and State = 'Table Lock'. ; let $wait_condition= SELECT COUNT(*) FROM INFORMATION_SCHEMA.PROCESSLIST - WHERE INFO IS NOT NULL AND STATE = 'Locked'; + WHERE INFO IS NOT NULL AND STATE = 'Waiting for table'; --source include/wait_condition.inc echo # Expect result: diff --git a/mysql-test/suite/funcs_1/r/processlist_val_no_prot.result b/mysql-test/suite/funcs_1/r/processlist_val_no_prot.result index b0cae801fd6..e8ee784bec4 100644 --- a/mysql-test/suite/funcs_1/r/processlist_val_no_prot.result +++ b/mysql-test/suite/funcs_1/r/processlist_val_no_prot.result @@ -193,11 +193,13 @@ LOCK TABLE test.t1 WRITE; SELECT COUNT(*) FROM test.t1; # ----- switch to connection default (user = root) ----- -# Poll till INFO is no more NULL and State = 'Locked'. +# Poll till INFO is no more NULL and State = 'Table Lock'. +Timeout in wait_condition.inc for SELECT COUNT(*) FROM INFORMATION_SCHEMA.PROCESSLIST +WHERE INFO IS NOT NULL AND STATE = 'Table Lock' SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST; ID USER HOST DB COMMAND TIME STATE INFO -<ID> test_user <HOST_NAME> information_schema Query <TIME> Locked SELECT COUNT(*) FROM test.t1 +<ID> test_user <HOST_NAME> information_schema Query <TIME> Waiting for table SELECT COUNT(*) FROM test.t1 <ID> test_user <HOST_NAME> information_schema Sleep <TIME> NULL <ID> root <HOST_NAME> information_schema Query <TIME> executing SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST UNLOCK TABLES; @@ -221,7 +223,7 @@ LOCK TABLE test.t1 WRITE; SELECT count(*),'BEGIN-This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.-END' AS "Long string" FROM test.t1; # ----- switch to connection default (user = root) ----- -# Poll till INFO is no more NULL and State = 'Locked'. +# Poll till INFO is no more NULL and State = 'Table Lock'. # Expect result: # Statement Content of INFO diff --git a/mysql-test/suite/funcs_1/r/processlist_val_ps.result b/mysql-test/suite/funcs_1/r/processlist_val_ps.result index 4e4cfa57e36..72bd57e836a 100644 --- a/mysql-test/suite/funcs_1/r/processlist_val_ps.result +++ b/mysql-test/suite/funcs_1/r/processlist_val_ps.result @@ -193,11 +193,11 @@ LOCK TABLE test.t1 WRITE; SELECT COUNT(*) FROM test.t1; # ----- switch to connection default (user = root) ----- -# Poll till INFO is no more NULL and State = 'Locked'. +# Poll till INFO is no more NULL and State = 'Table Lock'. SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST; ID USER HOST DB COMMAND TIME STATE INFO -<ID> test_user <HOST_NAME> information_schema Query <TIME> Locked SELECT COUNT(*) FROM test.t1 +<ID> test_user <HOST_NAME> information_schema Query <TIME> Table lock SELECT COUNT(*) FROM test.t1 <ID> test_user <HOST_NAME> information_schema Sleep <TIME> NULL <ID> root <HOST_NAME> information_schema Execute <TIME> executing SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST UNLOCK TABLES; @@ -221,7 +221,7 @@ LOCK TABLE test.t1 WRITE; SELECT count(*),'BEGIN-This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.This is the representative of a very long statement.-END' AS "Long string" FROM test.t1; # ----- switch to connection default (user = root) ----- -# Poll till INFO is no more NULL and State = 'Locked'. +# Poll till INFO is no more NULL and State = 'Table Lock'. # Expect result: # Statement Content of INFO diff --git a/mysql-test/suite/ndb/r/ndb_index_ordered.result b/mysql-test/suite/ndb/r/ndb_index_ordered.result index a29b5343d7c..c99db354314 100644 --- a/mysql-test/suite/ndb/r/ndb_index_ordered.result +++ b/mysql-test/suite/ndb/r/ndb_index_ordered.result @@ -637,21 +637,6 @@ select count(*)- 4 from t1 use index (v) where v > 0000965.00042; count(*)- 4 0 drop table t1; -create table t1(a int primary key, b int not null, index(b)); -insert into t1 values (1,1), (2,2); -set autocommit=0; -begin; -select count(*) from t1; -count(*) -2 -ALTER TABLE t1 ADD COLUMN c int; -select a from t1 where b = 2; -a -2 -show tables; -Tables_in_test -t1 -drop table t1; create table t1 (a int, c varchar(10), primary key using hash (a), index(c)) engine=ndb; insert into t1 (a, c) values (1,'aaa'),(3,'bbb'); diff --git a/mysql-test/suite/ndb/t/disabled.def b/mysql-test/suite/ndb/t/disabled.def index 0fc9a5d3ad6..b2aa3e515be 100644 --- a/mysql-test/suite/ndb/t/disabled.def +++ b/mysql-test/suite/ndb/t/disabled.def @@ -13,3 +13,4 @@ ndb_partition_error2 : Bug#40989 ndb_partition_error2 needs maintenance # the below testcase have been reworked to avoid the bug, test contains comment, keep bug open +ndb_alter_table3 : Bug#45621 2009-06-10 alik A few test files are disabled due to WL#4284 diff --git a/mysql-test/suite/ndb/t/ndb_index_ordered.test b/mysql-test/suite/ndb/t/ndb_index_ordered.test index 782f17ca5b2..c8dfc1de59f 100644 --- a/mysql-test/suite/ndb/t/ndb_index_ordered.test +++ b/mysql-test/suite/ndb/t/ndb_index_ordered.test @@ -333,21 +333,29 @@ select count(*)- 4 from t1 use index (v) where v > 0000965.00042; drop table t1; +# +# Disabled due to WL#4284 +# +# Needs to be reworked. It's not possible anymore to do a non-fast alter table +# on a table that is being used by a pending transaction (transaction holds a +# metadata lock on the table). +# # bug#7798 -create table t1(a int primary key, b int not null, index(b)); -insert into t1 values (1,1), (2,2); -connect (con1,localhost,root,,test); -connect (con2,localhost,root,,test); -connection con1; -set autocommit=0; -begin; -select count(*) from t1; -connection con2; -ALTER TABLE t1 ADD COLUMN c int; -connection con1; -select a from t1 where b = 2; -show tables; -drop table t1; +# create table t1(a int primary key, b int not null, c int, index(b)); +# insert into t1 values (1,1,1), (2,2,2); +# connect (con1,localhost,root,,test); +# connect (con2,localhost,root,,test); +# connection con1; +# set autocommit=0; +# begin; +# select count(*) from t1; +# connection con2; +# ALTER TABLE t1 ADD COLUMN c int +# connection con1; +# select a from t1 where b = 2; +# show tables; +# drop table t1; +# # mysqld 5.0.13 crash, no bug# create table t1 (a int, c varchar(10), diff --git a/mysql-test/suite/parts/r/partition_special_innodb.result b/mysql-test/suite/parts/r/partition_special_innodb.result index 26c1ac9356c..3c64c5c3ca4 100644 --- a/mysql-test/suite/parts/r/partition_special_innodb.result +++ b/mysql-test/suite/parts/r/partition_special_innodb.result @@ -214,9 +214,4 @@ INSERT INTO t1 VALUES (NULL, 'first row t2'); SET autocommit=OFF; ALTER TABLE t1 AUTO_INCREMENT = 10; ERROR HY000: Lock wait timeout exceeded; try restarting transaction -INSERT INTO t1 VALUES (NULL, 'second row t2'); -SELECT a,b FROM t1 ORDER BY a; -a b -1 first row t2 -2 second row t2 DROP TABLE t1; diff --git a/mysql-test/suite/parts/t/partition_special_innodb.test b/mysql-test/suite/parts/t/partition_special_innodb.test index eac19f6d588..7583f953b32 100644 --- a/mysql-test/suite/parts/t/partition_special_innodb.test +++ b/mysql-test/suite/parts/t/partition_special_innodb.test @@ -71,9 +71,6 @@ SET autocommit=OFF; --error ER_LOCK_WAIT_TIMEOUT ALTER TABLE t1 AUTO_INCREMENT = 10; ---connection con1 -INSERT INTO t1 VALUES (NULL, 'second row t2'); -SELECT a,b FROM t1 ORDER BY a; --disconnect con2 --disconnect con1 --connection default diff --git a/mysql-test/suite/rpl/r/rpl_innodb.result b/mysql-test/suite/rpl/r/rpl_innodb.result index bf6c3cb8c86..0d83f29d0fb 100644 --- a/mysql-test/suite/rpl/r/rpl_innodb.result +++ b/mysql-test/suite/rpl/r/rpl_innodb.result @@ -82,3 +82,48 @@ FLUSH LOGS; FLUSH LOGS; DROP DATABASE mysqltest1; End of 5.1 tests +# +# Bug#39675 rename tables on innodb tables with pending +# transactions causes slave data issue. +# +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +DROP TABLE IF EXISTS t3; +CREATE TABLE t1 ( +id INT PRIMARY KEY auto_increment, +b INT DEFAULT NULL +) ENGINE=InnoDB; +CREATE TABLE t2 ( +id INT PRIMARY KEY auto_increment, +b INT DEFAULT NULL +) ENGINE=InnoDB; +INSERT INTO t1 (b) VALUES (1),(2),(3); +BEGIN; +INSERT INTO t1(b) VALUES (4); +-------- switch to master1 -------- +RENAME TABLE t1 TO t3, t2 TO t1;; +-------- switch to master -------- +COMMIT; +-------- switch to master1 -------- +-------- switch to master -------- +SELECT * FROM t1; +id b +SELECT * FROM t3; +id b +1 1 +2 2 +3 3 +4 4 +-------- switch to slave -------- +SELECT * FROM t1; +id b +SELECT * FROM t3; +id b +1 1 +2 2 +3 3 +4 4 +-------- switch to master -------- +DROP TABLE t1; +DROP TABLE t3; +End of 6.0 tests diff --git a/mysql-test/suite/rpl/r/rpl_sp.result b/mysql-test/suite/rpl/r/rpl_sp.result index 33d8267ad91..ba748049ac1 100644 --- a/mysql-test/suite/rpl/r/rpl_sp.result +++ b/mysql-test/suite/rpl/r/rpl_sp.result @@ -977,4 +977,47 @@ drop procedure mysqltestbug36570_p1; drop procedure ` mysqltestbug36570_p2`; drop function mysqltestbug36570_f1; End of 5.0 tests -End of 5.1 tests +# End of 5.1 tests +# +# Test Bug#30977 Concurrent statement using stored +# function and DROP FUNCTION breaks SBR. +# +# Demonstrate that stored function DDL can not go through, +# or, worse yet, make its way into the binary log, while +# the stored function is in use. +# For that, try to insert a result of a stored function +# into a table. Block the insert in the beginning, waiting +# on a table lock. While insert is blocked, attempt to +# drop the routine. Verify that this attempt +# blocks and waits for INSERT to complete. Commit and +# reap the chain of events. Master and slave must contain +# identical data. Statements in the binrary log must be +# consistent with data in the table. +# +# --> connection default +drop table if exists t1, t2; +drop function if exists t1; +create table t1 (a int); +create table t2 (a int) as select 1 as a; +create function f1() returns int deterministic return (select max(a) from t2); +lock table t2 write; +# --> connection master +# Sending 'insert into t1 (a) values (f1())'... +insert into t1 (a) values (f1()); +# Waitng for 'insert into t1 ...' to get blocked on table lock... +# Sending 'drop function f1'. It will abort the table lock wait. +drop function f1; +# --> connection default +# Now let's let 'insert' go through... +unlock tables; +# --> connection con1 +# Reaping 'insert into t1 (a) values (f1())'... +ERROR 42000: FUNCTION test.f1 does not exist +select * from t1; +a +select * from t1; +a +drop table t1, t2; +drop function f1; +ERROR 42000: FUNCTION test.f1 does not exist +# End of 5.5 tests. diff --git a/mysql-test/suite/rpl/r/rpl_trigger.result b/mysql-test/suite/rpl/r/rpl_trigger.result index 86534fa8f7d..83d85e17131 100644 --- a/mysql-test/suite/rpl/r/rpl_trigger.result +++ b/mysql-test/suite/rpl/r/rpl_trigger.result @@ -894,8 +894,6 @@ s @ root@localhost DROP TRIGGER trg1; -Warnings: -Warning 1454 No definer attribute for trigger 'test'.'trg1'. The trigger will be activated under the authorization of the invoker, which may have insufficient privileges. Please recreate the trigger. DROP TABLE t1; DROP TABLE t2; STOP SLAVE; diff --git a/mysql-test/suite/rpl/r/rpl_view_multi.result b/mysql-test/suite/rpl/r/rpl_view_multi.result new file mode 100644 index 00000000000..b3f10584a24 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_view_multi.result @@ -0,0 +1,90 @@ +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +# +# Bug #25144 "replication / binlog with view breaks". +# Statements that used views didn't ensure that view were not modified +# during their execution. Indeed this led to incorrect binary log with +# statement based logging and as result to broken replication. +# +drop tables if exists t1, t2; +drop view if exists v1; +# Syncing slave with master and switching to connection 'slave' +# Switching to connection 'master' +create table t1 (i int); +create table t2 (i int); +create view v1 as select * from t1; +# First we try to concurrently execute statement that uses view +# and statement that drops it. We use "user" locks as means to +# suspend execution of first statement once it opens our view. +select get_lock("lock_bg25144", 1); +get_lock("lock_bg25144", 1) +1 +# Switching to connection 'master1' +insert into v1 values (get_lock("lock_bg25144", 100)); +# Switching to connection 'master2' +drop view v1; +# Switching to connection 'master' +select release_lock("lock_bg25144"); +release_lock("lock_bg25144") +1 +# Switching to connection 'master1' +select release_lock("lock_bg25144"); +release_lock("lock_bg25144") +1 +# Switching to connection 'master2' +# Switching to connection 'master' +# Check that insertion through view did happen. +select * from t1; +i +1 +# Syncing slave with master and switching to connection 'slave' +# Check that slave was able to replicate this sequence +# which means that we got correct binlog order. +select * from t1; +i +1 +# Switching to connection 'master' +# Now we will repeat the test by trying concurrently execute +# statement that uses a view and statement that alters it. +create view v1 as select * from t1; +select get_lock("lock_bg25144", 1); +get_lock("lock_bg25144", 1) +1 +# Switching to connection 'master1' +insert into v1 values (get_lock("lock_bg25144", 100)); +# Switching to connection 'master2' +alter view v1 as select * from t2; +# Switching to connection 'master' +select release_lock("lock_bg25144"); +release_lock("lock_bg25144") +1 +# Switching to connection 'master1' +select release_lock("lock_bg25144"); +release_lock("lock_bg25144") +1 +# Switching to connection 'master2' +# Switching to connection 'master' +# Second insertion should go to t1 as well. +select * from t1; +i +1 +1 +select * from t2; +i +# Syncing slave with master and switching to connection 'slave' +# Now let us check that statements were logged in proper order +# So we have same result on slave. +select * from t1; +i +1 +1 +select * from t2; +i +# Switching to connection 'master' +drop table t1, t2; +drop view v1; +# Syncing slave with master and switching to connection 'slave' diff --git a/mysql-test/suite/rpl/t/disabled.def b/mysql-test/suite/rpl/t/disabled.def index 446c233c8a9..1cf4f47e4e7 100644 --- a/mysql-test/suite/rpl/t/disabled.def +++ b/mysql-test/suite/rpl/t/disabled.def @@ -14,3 +14,8 @@ rpl_get_master_version_and_clock: # Bug#46931 2009-10-17 joro rpl.rpl_get_maste rpl_row_create_table : Bug#45576 2009-12-01 joro rpl_row_create_table fails on PB2 rpl_cross_version : BUG#43913 2009-10-22 luis rpl_cross_version fails with symptom in described in bug report rpl_spec_variables : BUG#47661 2009-10-27 jasonh rpl_spec_variables fails on PB2 hpux +rpl_failed_optimize : WL#4284: Can't optimize table used by a pending transaction (there is metadata lock on the table). +rpl_read_only : WL#4284: Setting Read only won't succeed until all metadata locks are released. +rpl_heartbeat_ssl : SSL certificates have expired. +rpl_ssl : SSL certificates have expired. +rpl_ssl1 : SSL certificates have expired. diff --git a/mysql-test/suite/rpl/t/rpl_innodb.test b/mysql-test/suite/rpl/t/rpl_innodb.test index 64a85d27c88..7ee65027abc 100644 --- a/mysql-test/suite/rpl/t/rpl_innodb.test +++ b/mysql-test/suite/rpl/t/rpl_innodb.test @@ -120,6 +120,71 @@ connection master; FLUSH LOGS; DROP DATABASE mysqltest1; --- source include/master-slave-end.inc --echo End of 5.1 tests + +--echo # +--echo # Bug#39675 rename tables on innodb tables with pending +--echo # transactions causes slave data issue. +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP TABLE IF EXISTS t2; +DROP TABLE IF EXISTS t3; +--enable_warnings + +CREATE TABLE t1 ( + id INT PRIMARY KEY auto_increment, + b INT DEFAULT NULL +) ENGINE=InnoDB; + +CREATE TABLE t2 ( + id INT PRIMARY KEY auto_increment, + b INT DEFAULT NULL +) ENGINE=InnoDB; + +INSERT INTO t1 (b) VALUES (1),(2),(3); + +BEGIN; +INSERT INTO t1(b) VALUES (4); + +--echo -------- switch to master1 -------- +connection master1; +--send RENAME TABLE t1 TO t3, t2 TO t1; + +--echo -------- switch to master -------- +connection master; +# Need to wait until RENAME is received +let $wait_condition= + SELECT COUNT(*) = 1 FROM information_schema.processlist + WHERE info = "RENAME TABLE t1 TO t3, t2 TO t1" and + state = "Waiting for table"; +--source include/wait_condition.inc + +COMMIT; + +--echo -------- switch to master1 -------- +connection master1; +--reap + +--echo -------- switch to master -------- +connection master; +SELECT * FROM t1; +SELECT * FROM t3; + +sync_slave_with_master; + +--echo -------- switch to slave -------- +connection slave; +SELECT * FROM t1; +SELECT * FROM t3; + +--echo -------- switch to master -------- +connection master; +DROP TABLE t1; +DROP TABLE t3; + +--echo End of 6.0 tests + +--source include/master-slave-end.inc diff --git a/mysql-test/suite/rpl/t/rpl_sp.test b/mysql-test/suite/rpl/t/rpl_sp.test index 96a41d9a9ad..3d94415fbb5 100644 --- a/mysql-test/suite/rpl/t/rpl_sp.test +++ b/mysql-test/suite/rpl/t/rpl_sp.test @@ -621,7 +621,64 @@ drop procedure mysqltestbug36570_p1; drop procedure ` mysqltestbug36570_p2`; drop function mysqltestbug36570_f1; --echo End of 5.0 tests ---echo End of 5.1 tests +--echo # End of 5.1 tests +--echo # +--echo # Test Bug#30977 Concurrent statement using stored +--echo # function and DROP FUNCTION breaks SBR. +--echo # +--echo # Demonstrate that stored function DDL can not go through, +--echo # or, worse yet, make its way into the binary log, while +--echo # the stored function is in use. +--echo # For that, try to insert a result of a stored function +--echo # into a table. Block the insert in the beginning, waiting +--echo # on a table lock. While insert is blocked, attempt to +--echo # drop the routine. Verify that this attempt +--echo # blocks and waits for INSERT to complete. Commit and +--echo # reap the chain of events. Master and slave must contain +--echo # identical data. Statements in the binrary log must be +--echo # consistent with data in the table. +--echo # +--echo # --> connection default +connection default; +--disable_warnings +drop table if exists t1, t2; +drop function if exists t1; +--enable_warnings +create table t1 (a int); +create table t2 (a int) as select 1 as a; +create function f1() returns int deterministic return (select max(a) from t2); +lock table t2 write; +--echo # --> connection master +connection master; +--echo # Sending 'insert into t1 (a) values (f1())'... +--send insert into t1 (a) values (f1()) +connection master1; +--echo # Waitng for 'insert into t1 ...' to get blocked on table lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='insert into t1 (a) values (f1())'; +--source include/wait_condition.inc +--echo # Sending 'drop function f1'. It will abort the table lock wait. +drop function f1; +--echo # --> connection default +connection default; +--echo # Now let's let 'insert' go through... +unlock tables; +--echo # --> connection con1 +connection master; +--echo # Reaping 'insert into t1 (a) values (f1())'... +--error ER_SP_DOES_NOT_EXIST +--reap +connection master1; +select * from t1; +connection slave; +select * from t1; +connection master; +drop table t1, t2; +--error ER_SP_DOES_NOT_EXIST +drop function f1; + +--echo # End of 5.5 tests. + # Cleanup sync_slave_with_master; diff --git a/mysql-test/suite/rpl/t/rpl_view_multi.test b/mysql-test/suite/rpl/t/rpl_view_multi.test new file mode 100644 index 00000000000..4e06429db32 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_view_multi.test @@ -0,0 +1,138 @@ +# +# This file contains test cases for bugs which involve views, several +# concurren connections and manifest themselves as wrong binary log +# sequence which results in broken replication. In principle we are +# mostly interested in SBR here but this test will also work with RBR. +# +--source include/master-slave.inc + +--echo # +--echo # Bug #25144 "replication / binlog with view breaks". +--echo # Statements that used views didn't ensure that view were not modified +--echo # during their execution. Indeed this led to incorrect binary log with +--echo # statement based logging and as result to broken replication. +--echo # + +--disable_warnings +drop tables if exists t1, t2; +drop view if exists v1; +--enable_warnings +--echo # Syncing slave with master and switching to connection 'slave' +--sync_slave_with_master + +connect (master2,127.0.0.1,root,,test,$MASTER_MYPORT,); + +--echo # Switching to connection 'master' +connection master; +create table t1 (i int); +create table t2 (i int); +create view v1 as select * from t1; + +--echo # First we try to concurrently execute statement that uses view +--echo # and statement that drops it. We use "user" locks as means to +--echo # suspend execution of first statement once it opens our view. +select get_lock("lock_bg25144", 1); + +--echo # Switching to connection 'master1' +connection master1; +--send insert into v1 values (get_lock("lock_bg25144", 100)) + +--echo # Switching to connection 'master2' +connection master2; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "User lock" and info like "insert into v1 %lock_bg25144%"; +--source include/wait_condition.inc +--send drop view v1 + +--echo # Switching to connection 'master' +connection master; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "drop view v1"; +--source include/wait_condition.inc + +select release_lock("lock_bg25144"); + +--echo # Switching to connection 'master1' +connection master1; +--disable_warnings +--reap +--enable_warnings +select release_lock("lock_bg25144"); + +--echo # Switching to connection 'master2' +connection master2; +--reap + +--echo # Switching to connection 'master' +connection master; +--echo # Check that insertion through view did happen. +select * from t1; +--echo # Syncing slave with master and switching to connection 'slave' +--sync_slave_with_master +--echo # Check that slave was able to replicate this sequence +--echo # which means that we got correct binlog order. +select * from t1; + +--echo # Switching to connection 'master' +connection master; +--echo # Now we will repeat the test by trying concurrently execute +--echo # statement that uses a view and statement that alters it. +create view v1 as select * from t1; + +select get_lock("lock_bg25144", 1); + +--echo # Switching to connection 'master1' +connection master1; +--send insert into v1 values (get_lock("lock_bg25144", 100)) + +--echo # Switching to connection 'master2' +connection master2; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "User lock" and info like "insert into v1 %lock_bg25144%"; +--source include/wait_condition.inc +--send alter view v1 as select * from t2 + +--echo # Switching to connection 'master' +connection master; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "alter view v1 as select * from t2"; +--source include/wait_condition.inc + +select release_lock("lock_bg25144"); + +--echo # Switching to connection 'master1' +connection master1; +--disable_warnings +--reap +--enable_warnings +select release_lock("lock_bg25144"); + +--echo # Switching to connection 'master2' +connection master2; +--reap + +--echo # Switching to connection 'master' +connection master; + +--echo # Second insertion should go to t1 as well. +select * from t1; +select * from t2; + +--echo # Syncing slave with master and switching to connection 'slave' +--sync_slave_with_master +--echo # Now let us check that statements were logged in proper order +--echo # So we have same result on slave. +select * from t1; +select * from t2; + +--echo # Switching to connection 'master' +connection master; +drop table t1, t2; +drop view v1; +--echo # Syncing slave with master and switching to connection 'slave' +--sync_slave_with_master diff --git a/mysql-test/suite/sys_vars/r/autocommit_func.result b/mysql-test/suite/sys_vars/r/autocommit_func.result index 8fe7c0bc90e..13115ca81ef 100644 --- a/mysql-test/suite/sys_vars/r/autocommit_func.result +++ b/mysql-test/suite/sys_vars/r/autocommit_func.result @@ -103,6 +103,8 @@ id name 2 Record_2 4 Record_4 5 Record_5 +## Commit changes +COMMIT; ## Dropping table t1 ## DROP table t1; ## Disconnecting both connections ## diff --git a/mysql-test/suite/sys_vars/r/concurrent_insert_func.result b/mysql-test/suite/sys_vars/r/concurrent_insert_func.result index de4ddbb5a04..0b5b342e134 100644 --- a/mysql-test/suite/sys_vars/r/concurrent_insert_func.result +++ b/mysql-test/suite/sys_vars/r/concurrent_insert_func.result @@ -37,9 +37,9 @@ INSERT INTO t1(name) VALUES('Record_7'); connection default; ## show processlist info and state ## SELECT state,info FROM INFORMATION_SCHEMA.PROCESSLIST -WHERE state= "Locked" AND info LIKE "INSERT INTO t1%"; +WHERE state= "Table lock" AND info LIKE "INSERT INTO t1%"; state info -Locked INSERT INTO t1(name) VALUES('Record_7') +Table lock INSERT INTO t1(name) VALUES('Record_7') ## table contents befor UNLOCK ## SELECT * FROM t1; name diff --git a/mysql-test/suite/sys_vars/r/delayed_insert_limit_func.result b/mysql-test/suite/sys_vars/r/delayed_insert_limit_func.result index 0496efa4296..0f1a16a6911 100644 --- a/mysql-test/suite/sys_vars/r/delayed_insert_limit_func.result +++ b/mysql-test/suite/sys_vars/r/delayed_insert_limit_func.result @@ -4,6 +4,7 @@ Creating connection con0 Creating connection con1 SET @global_delayed_insert_limit = @@GLOBAL.delayed_insert_limit; CREATE TABLE t1 (a VARCHAR(100),b VARCHAR(100),c VARCHAR(100)); +CREATE VIEW v1 as select * from t1; '#--------------------FN_DYNVARS_25_01-------------------------#' SET GLOBAL delayed_insert_limit = 14; INSERT INTO t1 VALUES('1','1','1'); @@ -12,7 +13,7 @@ INSERT INTO t1 VALUES('3','1','1'); INSERT INTO t1 VALUES('4','1','1'); INSERT INTO t1 VALUES('5','1','1'); INSERT INTO t1 VALUES('6','1','1'); -LOCK TABLE t1 WRITE; +LOCK TABLE v1 WRITE; ** Connection con1 ** INSERT DELAYED INTO t1 VALUES('7','1','1'); INSERT DELAYED INTO t1 VALUES('8','1','1'); @@ -70,8 +71,10 @@ SELECT COUNT(*) FROM t1; COUNT(*) 43 DROP TABLE t1; +DROP VIEW v1; '#--------------------FN_DYNVARS_25_02-------------------------#' CREATE TABLE t1 (a VARCHAR(100)); +CREATE VIEW v1 AS SELECT * FROM t1; SET GLOBAL delayed_insert_limit = 20; INSERT INTO t1 VALUES('1'); INSERT INTO t1 VALUES('2'); @@ -79,7 +82,7 @@ INSERT INTO t1 VALUES('3'); INSERT INTO t1 VALUES('4'); INSERT INTO t1 VALUES('5'); INSERT INTO t1 VALUES('6'); -LOCK TABLE t1 WRITE; +LOCK TABLE v1 WRITE; ** Connection con1 ** Asynchronous execute INSERT DELAYED INTO t1 VALUES('7'); @@ -116,5 +119,6 @@ COUNT(*) = 22 1 ** Connection default** DROP TABLE t1; +DROP VIEW v1; SET @@GLOBAL.delayed_insert_limit = @global_delayed_insert_limit; Disconnecting from con1, con0 diff --git a/mysql-test/suite/sys_vars/r/query_cache_wlock_invalidate_func.result b/mysql-test/suite/sys_vars/r/query_cache_wlock_invalidate_func.result index 6b90b61a035..abb8fdcd2a0 100644 --- a/mysql-test/suite/sys_vars/r/query_cache_wlock_invalidate_func.result +++ b/mysql-test/suite/sys_vars/r/query_cache_wlock_invalidate_func.result @@ -11,6 +11,7 @@ CREATE TABLE t1(id int, value varchar(10)); INSERT INTO t1 VALUES(1, 'val1'); INSERT INTO t1 VALUES(2, 'val2'); INSERT INTO t1 VALUES(3, 'val3'); +CREATE VIEW v1 AS SELECT * FROM t1; SET GLOBAL query_cache_size = 131072; FLUSHING CACHE SET GLOBAL query_cache_size = 0; @@ -32,11 +33,11 @@ SHOW STATUS LIKE 'Qcache_queries_in_cache'; Variable_name Value Qcache_queries_in_cache 1 1 Expected -LOCK TABLE t1 WRITE; +LOCK TABLE v1 WRITE; UNLOCK TABLES; SHOW STATUS LIKE 'Qcache_queries_in_cache'; Variable_name Value -Qcache_queries_in_cache 0 +Qcache_queries_in_cache 1 0 Expected '#----------------------------FN_DYNVARS_136_02-----------------------#' SELECT * FROM t1; @@ -48,12 +49,13 @@ id value 2 val2 3 val3 ** Connection con0 ** -LOCK TABLE t1 WRITE; +LOCK TABLE v1 WRITE; ** Connection con1 ** ** Asynchronous Execution ** SELECT * FROM t1; ** Connection con0 ** wait until table is locked +Timeout in wait_condition.inc for SELECT count(*) > 0 FROM information_schema.processlist WHERE state= 'Table lock' UNLOCK TABLES; ** Connection con1 ** ** Asynchronous Result ** @@ -77,7 +79,7 @@ SHOW STATUS LIKE 'Qcache_queries_in_cache'; Variable_name Value Qcache_queries_in_cache 1 1 Expected -LOCK TABLE t1 WRITE; +LOCK TABLE v1 WRITE; UNLOCK TABLES; SHOW STATUS LIKE 'Qcache_queries_in_cache'; Variable_name Value @@ -100,7 +102,7 @@ id value 2 val2 3 val3 ** Connection con0 ** -LOCK TABLE t1 WRITE; +LOCK TABLE v1 WRITE; ** Connection con1 ** ** Should not be blocked ** SELECT * FROM t1; @@ -163,3 +165,4 @@ SET @@GLOBAL.query_cache_type = @old_cache_type; ** Connection default ** Disconnecting Connections con0, con1 DROP TABLE t1; +DROP VIEW v1; diff --git a/mysql-test/suite/sys_vars/r/sql_low_priority_updates_func.result b/mysql-test/suite/sys_vars/r/sql_low_priority_updates_func.result index 584ca4c6f8f..fe76c2c4b96 100644 --- a/mysql-test/suite/sys_vars/r/sql_low_priority_updates_func.result +++ b/mysql-test/suite/sys_vars/r/sql_low_priority_updates_func.result @@ -6,6 +6,7 @@ SET @global_low_priority_updates = @@GLOBAL.low_priority_updates; SET @session_low_priority_updates = @@SESSION.low_priority_updates; CREATE TABLE t1 (a varchar(100)); +create view v1 as select * from t1; '#--------------------FN_DYNVARS_160_01-------------------------#' ** Connection con0 ** SET SESSION low_priority_updates = ON; @@ -19,7 +20,7 @@ INSERT INTO t1 VALUES('3'); INSERT INTO t1 VALUES('4'); INSERT INTO t1 VALUES('5'); INSERT INTO t1 VALUES('6'); -LOCK TABLE t1 WRITE; +LOCK TABLE v1 WRITE; ** Connection con1 ** ** Asynchronous Execution ** UPDATE t1 SET a = CONCAT(a,"-updated");| @@ -55,7 +56,7 @@ INSERT INTO t1 VALUES('3'); INSERT INTO t1 VALUES('4'); INSERT INTO t1 VALUES('5'); INSERT INTO t1 VALUES('6'); -LOCK TABLE t1 WRITE; +LOCK TABLE v1 WRITE; ** Connection con1 ** ** Asynchronous Execution ** UPDATE t1 SET a = CONCAT(a,"-updated");| @@ -107,6 +108,7 @@ SELECT @@SESSION.low_priority_updates; Disconnecting Connections con_int1, con_int2 ** Connection default ** Disconnecting Connections con0, con1 +drop view v1; DROP TABLE t1; SET @@GLOBAL.low_priority_updates = @global_low_priority_updates; SET @@SESSION.low_priority_updates = @session_low_priority_updates; diff --git a/mysql-test/suite/sys_vars/t/autocommit_func.test b/mysql-test/suite/sys_vars/t/autocommit_func.test index 06193448588..10c763ca58e 100644 --- a/mysql-test/suite/sys_vars/t/autocommit_func.test +++ b/mysql-test/suite/sys_vars/t/autocommit_func.test @@ -152,6 +152,10 @@ SELECT * from t1; CONNECTION test_con2; SELECT * from t1; +--echo ## Commit changes +CONNECTION test_con1; +COMMIT; + --echo ## Dropping table t1 ## DROP table t1; diff --git a/mysql-test/suite/sys_vars/t/concurrent_insert_func.test b/mysql-test/suite/sys_vars/t/concurrent_insert_func.test index 1a600ffd7f6..018247df3ff 100644 --- a/mysql-test/suite/sys_vars/t/concurrent_insert_func.test +++ b/mysql-test/suite/sys_vars/t/concurrent_insert_func.test @@ -98,12 +98,12 @@ INSERT INTO t1(name) VALUES('Record_7'); connection default; # wait until INSERT will be locked (low performance) let $wait_condition= SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST - WHERE state= "Locked" AND info LIKE "INSERT INTO t1%"; + WHERE state= "Table lock" AND info LIKE "INSERT INTO t1%"; --source include/wait_condition.inc --echo ## show processlist info and state ## SELECT state,info FROM INFORMATION_SCHEMA.PROCESSLIST -WHERE state= "Locked" AND info LIKE "INSERT INTO t1%"; +WHERE state= "Table lock" AND info LIKE "INSERT INTO t1%"; --echo ## table contents befor UNLOCK ## SELECT * FROM t1; UNLOCK TABLES; diff --git a/mysql-test/suite/sys_vars/t/delayed_insert_limit_func.test b/mysql-test/suite/sys_vars/t/delayed_insert_limit_func.test index 88f247135c8..8ab825ea31d 100644 --- a/mysql-test/suite/sys_vars/t/delayed_insert_limit_func.test +++ b/mysql-test/suite/sys_vars/t/delayed_insert_limit_func.test @@ -46,6 +46,7 @@ SET @global_delayed_insert_limit = @@GLOBAL.delayed_insert_limit; # CREATE TABLE t1 (a VARCHAR(100),b VARCHAR(100),c VARCHAR(100)); +CREATE VIEW v1 as select * from t1; --echo '#--------------------FN_DYNVARS_25_01-------------------------#' @@ -60,7 +61,7 @@ INSERT INTO t1 VALUES('4','1','1'); INSERT INTO t1 VALUES('5','1','1'); INSERT INTO t1 VALUES('6','1','1'); -LOCK TABLE t1 WRITE; +LOCK TABLE v1 WRITE; --echo ** Connection con1 ** connection con1; @@ -122,7 +123,7 @@ connection default; --echo ** Wait till con0 is blocked ** let $wait_condition= SELECT COUNT(*) = 1 FROM information_schema.processlist - WHERE state = 'Locked' AND info = '$my_select'; + WHERE state = 'Table lock' AND info = '$my_select'; --source include/wait_condition.inc UNLOCK TABLES; @@ -153,6 +154,7 @@ let $wait_condition= SELECT count(*) = 43 FROM t1; SELECT COUNT(*) FROM t1; DROP TABLE t1; +DROP VIEW v1; --echo '#--------------------FN_DYNVARS_25_02-------------------------#' @@ -160,6 +162,7 @@ DROP TABLE t1; # delayed_insert_limit is bigger than the number of inserted rows CREATE TABLE t1 (a VARCHAR(100)); +CREATE VIEW v1 AS SELECT * FROM t1; SET GLOBAL delayed_insert_limit = 20; @@ -170,7 +173,7 @@ INSERT INTO t1 VALUES('4'); INSERT INTO t1 VALUES('5'); INSERT INTO t1 VALUES('6'); -LOCK TABLE t1 WRITE; +LOCK TABLE v1 WRITE; --echo ** Connection con1 ** connection con1; @@ -214,7 +217,7 @@ connection default; --echo ** Wait till con0 is blocked ** let $wait_condition= SELECT COUNT(*) = 1 FROM information_schema.processlist - WHERE state = 'Locked' AND info = '$my_select'; + WHERE state = 'Table lock' AND info = '$my_select'; --source include/wait_condition.inc UNLOCK TABLES; @@ -240,6 +243,7 @@ eval $my_select; --echo ** Connection default** connection default; DROP TABLE t1; +DROP VIEW v1; SET @@GLOBAL.delayed_insert_limit = @global_delayed_insert_limit; --echo Disconnecting from con1, con0 diff --git a/mysql-test/suite/sys_vars/t/query_cache_wlock_invalidate_func.test b/mysql-test/suite/sys_vars/t/query_cache_wlock_invalidate_func.test index e5ced59d175..a72d73105a6 100644 --- a/mysql-test/suite/sys_vars/t/query_cache_wlock_invalidate_func.test +++ b/mysql-test/suite/sys_vars/t/query_cache_wlock_invalidate_func.test @@ -60,6 +60,8 @@ INSERT INTO t1 VALUES(1, 'val1'); INSERT INTO t1 VALUES(2, 'val2'); INSERT INTO t1 VALUES(3, 'val3'); +CREATE VIEW v1 AS SELECT * FROM t1; + # # Clearing the query cache and setting up cache size # @@ -99,7 +101,7 @@ SELECT * FROM t1; SHOW STATUS LIKE 'Qcache_queries_in_cache'; --echo 1 Expected -LOCK TABLE t1 WRITE; +LOCK TABLE v1 WRITE; UNLOCK TABLES; @@ -127,7 +129,7 @@ SELECT * FROM t1; --echo ** Connection con0 ** connection con0; -LOCK TABLE t1 WRITE; +LOCK TABLE v1 WRITE; --echo ** Connection con1 ** connection con1; @@ -139,7 +141,7 @@ send SELECT * FROM t1; connection con0; --echo wait until table is locked -let $wait_condition= SELECT count(*) > 0 FROM information_schema.processlist WHERE state= 'Locked'; +let $wait_condition= SELECT count(*) > 0 FROM information_schema.processlist WHERE state= 'Table lock'; --source include/wait_condition.inc UNLOCK TABLES; @@ -175,7 +177,7 @@ SELECT * FROM t1; SHOW STATUS LIKE 'Qcache_queries_in_cache'; --echo 1 Expected -LOCK TABLE t1 WRITE; +LOCK TABLE v1 WRITE; UNLOCK TABLES; @@ -199,7 +201,7 @@ SELECT * FROM t1; --echo ** Connection con0 ** connection con0; -LOCK TABLE t1 WRITE; +LOCK TABLE v1 WRITE; --echo ** Connection con1 ** connection con1; @@ -281,6 +283,7 @@ disconnect con0; disconnect con1; DROP TABLE t1; +DROP VIEW v1; --enable_ps_protocol diff --git a/mysql-test/suite/sys_vars/t/sql_low_priority_updates_func.test b/mysql-test/suite/sys_vars/t/sql_low_priority_updates_func.test index 2ef6e34b0b3..87813a958e0 100644 --- a/mysql-test/suite/sys_vars/t/sql_low_priority_updates_func.test +++ b/mysql-test/suite/sys_vars/t/sql_low_priority_updates_func.test @@ -46,6 +46,7 @@ SET @session_low_priority_updates = @@SESSION.low_priority_updates; # CREATE TABLE t1 (a varchar(100)); +create view v1 as select * from t1; --echo '#--------------------FN_DYNVARS_160_01-------------------------#' # @@ -69,7 +70,7 @@ INSERT INTO t1 VALUES('4'); INSERT INTO t1 VALUES('5'); INSERT INTO t1 VALUES('6'); -LOCK TABLE t1 WRITE; +LOCK TABLE v1 WRITE; --echo ** Connection con1 ** connection con1; @@ -85,7 +86,7 @@ delimiter ;| --echo ** Connection con0 ** connection con0; -let $wait_condition = SELECT COUNT(*) > 0 FROM information_schema.processlist WHERE state='Locked' AND info LIKE 'UPDATE t1 SET a = CONCAT(a,"-updated")'; +let $wait_condition = SELECT COUNT(*) > 0 FROM information_schema.processlist WHERE state='Table lock' AND info LIKE 'UPDATE t1 SET a = CONCAT(a,"-updated")'; --source include/wait_condition.inc --echo ** Asynchronous Execution ** @@ -101,7 +102,7 @@ delimiter ;| --echo ** Connection default ** connection default; -let $wait_condition= SELECT count(*) = 2 FROM information_schema.processlist WHERE state LIKE 'Locked'; +let $wait_condition= SELECT count(*) = 2 FROM information_schema.processlist WHERE state LIKE 'Table lock'; --source include/wait_condition.inc UNLOCK TABLES; @@ -139,7 +140,7 @@ INSERT INTO t1 VALUES('4'); INSERT INTO t1 VALUES('5'); INSERT INTO t1 VALUES('6'); -LOCK TABLE t1 WRITE; +LOCK TABLE v1 WRITE; --echo ** Connection con1 ** connection con1; @@ -155,7 +156,7 @@ delimiter ;| --echo ** Connection con0 ** connection con0; -let $wait_condition = SELECT COUNT(*) > 0 FROM information_schema.processlist WHERE state='Locked' AND info LIKE 'UPDATE t1 SET a = CONCAT(a,"-updated")'; +let $wait_condition = SELECT COUNT(*) > 0 FROM information_schema.processlist WHERE state='Table lock' AND info LIKE 'UPDATE t1 SET a = CONCAT(a,"-updated")'; --source include/wait_condition.inc --echo ** Asynchronous Execution ** @@ -171,7 +172,7 @@ delimiter ;| --echo ** Connection default ** connection default; -let $wait_condition= SELECT count(*) = 2 FROM information_schema.processlist WHERE state LIKE 'Locked'; +let $wait_condition= SELECT count(*) = 2 FROM information_schema.processlist WHERE state LIKE 'Table lock'; --source include/wait_condition.inc UNLOCK TABLES; @@ -238,6 +239,7 @@ connection default; disconnect con0; disconnect con1; +drop view v1; DROP TABLE t1; SET @@GLOBAL.low_priority_updates = @global_low_priority_updates; diff --git a/mysql-test/t/create.test b/mysql-test/t/create.test index 9f3c3a88151..21778e00ab9 100644 --- a/mysql-test/t/create.test +++ b/mysql-test/t/create.test @@ -683,8 +683,8 @@ drop table t1; # Error during open_and_lock_tables() of tables --error ER_NO_SUCH_TABLE create table t1 select * from t2; -# Rather special error which also caught during open tables pahse ---error ER_UPDATE_TABLE_USED +# A special case which is also caught during open tables pahse +--error ER_NO_SUCH_TABLE create table t1 select * from t1; # Error which happens before select_create::prepare() --error ER_CANT_AGGREGATE_2COLLATIONS @@ -706,6 +706,10 @@ create table t1 (i int); create table t1 select 1 as i; create table if not exists t1 select 1 as i; select * from t1; +# Error which is detected after successfull table open. +--error ER_UPDATE_TABLE_USED +create table if not exists t1 select * from t1; +select * from t1; # Error before select_create::prepare() --error ER_CANT_AGGREGATE_2COLLATIONS create table t1 select coalesce('a' collate latin1_swedish_ci,'b' collate latin1_bin); @@ -1635,3 +1639,33 @@ END ;| DROP TABLE t1; DROP TABLE B; + + +--echo # +--echo # Bug #47107 assert in notify_shared_lock on incorrect +--echo # CREATE TABLE , HANDLER +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1(f1 integer); + +--echo # The following CREATE TABLEs before gave an assert. + +HANDLER t1 OPEN AS A; +--error ER_TABLE_EXISTS_ERROR +CREATE TABLE t1 SELECT 1 AS f2; + +HANDLER t1 OPEN AS A; +--error ER_TABLE_EXISTS_ERROR +CREATE TABLE t1(f1 integer); + +CREATE TABLE t2(f1 integer); +HANDLER t1 OPEN AS A; +--error ER_TABLE_EXISTS_ERROR +CREATE TABLE t1 LIKE t2; + +DROP TABLE t2; +DROP TABLE t1; diff --git a/mysql-test/t/debug_sync.test b/mysql-test/t/debug_sync.test index 514e471b603..ebeeec61632 100644 --- a/mysql-test/t/debug_sync.test +++ b/mysql-test/t/debug_sync.test @@ -390,7 +390,7 @@ DROP TABLE IF EXISTS t1; # # Test. CREATE TABLE t1 (c1 INT); -LOCK TABLE t1 WRITE; +LOCK TABLE t1 READ; --echo connection con1 connect (con1,localhost,root,,); # Retain action after use. First used by general_log. diff --git a/mysql-test/t/delayed.test b/mysql-test/t/delayed.test index d1e9bc154f5..142f1b241d6 100644 --- a/mysql-test/t/delayed.test +++ b/mysql-test/t/delayed.test @@ -307,7 +307,7 @@ connection update; connection default; let $wait_condition= select count(*) = 1 from information_schema.processlist - where command = "Delayed insert" and state = "upgrading lock"; + where command = "Delayed insert" and state = "Table lock"; --source include/wait_condition.inc connect (select,localhost,root,,); --echo connection: select diff --git a/mysql-test/t/disabled.def b/mysql-test/t/disabled.def index 97237d5da73..165988ab89e 100644 --- a/mysql-test/t/disabled.def +++ b/mysql-test/t/disabled.def @@ -10,7 +10,12 @@ # ############################################################################## kill : Bug#37780 2008-12-03 HHunger need some changes to be robust enough for pushbuild. -query_cache_28249 : Bug#43861 2009-03-25 main.query_cache_28249 fails sporadicallyr +query_cache_28249 : Bug#43861 2009-03-25 main.query_cache_28249 fails sporadically +openssl_1 : SSL certificates expired +ssl : SSL certificates expired +ssl_8k_key : SSL certificates expired +ssl_compress : SSL certificates expired +ssl_connect : SSL certificates expired innodb-autoinc : Bug#49267 2009-12-02 test fails on windows because of different case mode innodb : Bug#49396 2009-12-03 test fails in embedded mode plugin_load : Bug#42144 2009-12-21 alik plugin_load fails diff --git a/mysql-test/t/drop.test b/mysql-test/t/drop.test index 4aeb7165bcb..5ef4a28b202 100644 --- a/mysql-test/t/drop.test +++ b/mysql-test/t/drop.test @@ -100,8 +100,8 @@ drop database if exists mysqltest; drop table if exists t1; --enable_warnings create table t1 (i int); -lock tables t1 read; create database mysqltest; +lock tables t1 read; connect (addconroot1, localhost, root,,); --send drop table t1 connect (addconroot2, localhost, root,,); diff --git a/mysql-test/t/drop_debug.test b/mysql-test/t/drop_debug.test index 97ee5847d0a..63c85d9246b 100644 --- a/mysql-test/t/drop_debug.test +++ b/mysql-test/t/drop_debug.test @@ -17,11 +17,14 @@ DROP DATABASE IF EXISTS mysql_test; --echo CREATE DATABASE mysql_test; CREATE TABLE mysql_test.t1(a INT); +CREATE TABLE mysql_test.t2(b INT); +CREATE TABLE mysql_test.t3(c INT); --echo SET SESSION DEBUG = "+d,bug43138"; --echo +--sorted_result DROP DATABASE mysql_test; --echo diff --git a/mysql-test/t/flush.test b/mysql-test/t/flush.test index f27d4cf2fad..4172230a54d 100644 --- a/mysql-test/t/flush.test +++ b/mysql-test/t/flush.test @@ -68,10 +68,13 @@ drop table t1; create table t1 (c1 int); lock table t1 write; # Cannot get the global read lock with write locked tables. ---error 1192 +--error ER_LOCK_OR_ACTIVE_TRANSACTION flush tables with read lock; lock table t1 read; -# Can get the global read lock with read locked tables. +# Cannot get the global read lock with read locked tables. +--error ER_LOCK_OR_ACTIVE_TRANSACTION +flush tables with read lock; +unlock tables; flush tables with read lock; --error 1223 lock table t1 write; @@ -84,12 +87,12 @@ create table t2 (c1 int); create table t3 (c1 int); lock table t1 read, t2 read, t3 write; # Cannot get the global read lock with write locked tables. ---error 1192 +--error ER_LOCK_OR_ACTIVE_TRANSACTION flush tables with read lock; lock table t1 read, t2 read, t3 read; -# Can get the global read lock with read locked tables. +# Cannot get the global read lock with read locked tables. +--error ER_LOCK_OR_ACTIVE_TRANSACTION flush tables with read lock; -# Release all table locks and the global read lock. unlock tables; drop table t1, t2, t3; @@ -157,6 +160,7 @@ flush tables with read lock; unlock tables; lock tables t1 read; +--error ER_LOCK_OR_ACTIVE_TRANSACTION flush tables with read lock; unlock tables; diff --git a/mysql-test/t/flush_block_commit.test b/mysql-test/t/flush_block_commit.test index 74892def63f..0b3bede1684 100644 --- a/mysql-test/t/flush_block_commit.test +++ b/mysql-test/t/flush_block_commit.test @@ -6,7 +6,7 @@ # And it requires InnoDB --source include/have_innodb.inc -# Save the initial number of concurrent sessions +--echo # Save the initial number of concurrent sessions --source include/count_sessions.inc --echo # Establish connection con1 (user=root) @@ -30,18 +30,24 @@ INSERT INTO t1 VALUES(1); --echo # Switch to connection con2 connection con2; FLUSH TABLES WITH READ LOCK; -SELECT * FROM t1; --echo # Switch to connection con1 connection con1; -send COMMIT; # blocked by con2 -sleep 1; +--echo # Sending: +--send COMMIT --echo # Switch to connection con2 connection con2; -SELECT * FROM t1; # verify con1 was blocked and data did not move +--echo # Wait until COMMIT gets blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for release of readlock" and info = "COMMIT"; +--source include/wait_condition.inc +--echo # Verify that 'con1' was blocked and data did not move. +SELECT * FROM t1; UNLOCK TABLES; --echo # Switch to connection con1 connection con1; -reap; +--echo # Reaping COMMIT +--reap # No deadlock ? @@ -63,6 +69,7 @@ COMMIT; # should not be blocked by con3 --echo # Switch to connection con2 connection con2; reap; +COMMIT; --echo # Switch to connection con3 connection con3; reap; @@ -79,8 +86,6 @@ connection con1; BEGIN; INSERT INTO t1 VALUES(10); FLUSH TABLES WITH READ LOCK; -COMMIT; -UNLOCK TABLES; --echo # Switch to connection con2 connection con2; FLUSH TABLES WITH READ LOCK; # bug caused hang here @@ -91,19 +96,21 @@ UNLOCK TABLES; BEGIN; SELECT * FROM t1; SHOW CREATE DATABASE test; - -DROP TABLE t1; +COMMIT; -# Cleanup +--echo # Cleanup --echo # Switch to connection default and close connections con1, con2, con3 connection default; disconnect con1; disconnect con2; disconnect con3; -# End of 4.1 tests +--echo # We commit open transactions when we disconnect: only then we can +--echo # drop the table. +DROP TABLE t1; +--echo # End of 4.1 tests -# Wait till all disconnects are completed +--echo # Wait till all disconnects are completed --source include/wait_until_count_sessions.inc diff --git a/mysql-test/t/flush_block_commit_notembedded.test b/mysql-test/t/flush_block_commit_notembedded.test index aea38250218..d7ffbd475b4 100644 --- a/mysql-test/t/flush_block_commit_notembedded.test +++ b/mysql-test/t/flush_block_commit_notembedded.test @@ -9,7 +9,7 @@ --source include/have_log_bin.inc --source include/have_innodb.inc -# Save the initial number of concurrent sessions +--echo # Save the initial number of concurrent sessions --source include/count_sessions.inc @@ -24,14 +24,14 @@ connection con1; CREATE TABLE t1 (a INT) ENGINE=innodb; RESET MASTER; SET AUTOCOMMIT=0; -INSERT t1 VALUES (1); +SELECT 1; --echo # Switch to connection con2 connection con2; FLUSH TABLES WITH READ LOCK; SHOW MASTER STATUS; --echo # Switch to connection con1 connection con1; -send COMMIT; +send INSERT INTO t1 VALUES (1); --echo # Switch to connection con2 connection con2; sleep 1; @@ -43,11 +43,30 @@ reap; DROP TABLE t1; SET AUTOCOMMIT=1; +# GLR blocks new transactions +create table t1 (a int) engine=innodb; +connection con1; +flush tables with read lock; +connection con2; +begin; +--send insert into t1 values (1); +connection con1; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for release of readlock" and + info = "insert into t1 values (1)"; +--source include/wait_condition.inc +unlock tables; +connection con2; +--reap +commit; +drop table t1; + --echo # Switch to connection default and close connections con1 and con2 connection default; disconnect con1; disconnect con2; -# Wait till all disconnects are completed +--echo # Wait till all disconnects are completed --source include/wait_until_count_sessions.inc diff --git a/mysql-test/t/flush_table.test b/mysql-test/t/flush_table.test index 50e7e91419a..e4fc1b0c39f 100644 --- a/mysql-test/t/flush_table.test +++ b/mysql-test/t/flush_table.test @@ -15,30 +15,21 @@ insert into t1 values(0); # Test for with read lock + flush lock table t1 read; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE flush table t1; -check table t1; unlock tables; -# Test for with 2 read lock in different thread + flush +# Test for with write lock + flush -lock table t1 read; -connect (locker,localhost,root,,test); -connection locker; -lock table t1 read; -connection default; -send flush table t1; -connection locker; ---sleep 2 -select * from t1; -unlock tables; -connection default; -reap; -select * from t1; +lock table t1 write; +flush table t1; +check table t1; unlock tables; # Test for with a write lock and a waiting read lock + flush lock table t1 write; +connect (locker,localhost,root,,test); connection locker; send lock table t1 read; connection default; @@ -51,9 +42,9 @@ reap; unlock tables; connection default; -# Test for with a read lock and a waiting write lock + flush +# Test for with a write lock and a waiting write lock + flush -lock table t1 read; +lock table t1 write; connection locker; send lock table t1 write; connection default; diff --git a/mysql-test/t/implicit_commit.test b/mysql-test/t/implicit_commit.test new file mode 100644 index 00000000000..d8ffd6e9452 --- /dev/null +++ b/mysql-test/t/implicit_commit.test @@ -0,0 +1,1162 @@ +source include/have_innodb.inc; +source include/not_embedded.inc; + +SET GLOBAL EVENT_SCHEDULER = OFF; +SET BINLOG_FORMAT = STATEMENT; + +LET $OLD_DB= `SELECT DATABASE()`; + +CREATE DATABASE db1; +USE db1; +CREATE TABLE t1 (a INT, KEY a(a)) ENGINE=INNODB; +INSERT INTO t1 VALUES (1),(2),(3),(4),(5); +CREATE TABLE t3 (a INT) ENGINE=MyISAM; +INSERT INTO t3 SELECT * FROM t1; +CREATE TABLE trans (a INT) ENGINE=INNODB; + +DELIMITER |; + +CREATE PROCEDURE test_if_commit() +BEGIN + ROLLBACK; + SELECT IF (COUNT(*) > 0, "YES", "NO") AS "IMPLICIT COMMIT" FROM trans; + DELETE FROM trans; + COMMIT; +END| + +DELIMITER ;| + +SET AUTOCOMMIT = FALSE; + +--echo # +--echo # SQLCOM_SELECT +--echo # + +let $statement= + select 1 as res from t1 where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_TABLE LIKE +--echo # + +let $statement= + create table t2 like t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_CREATE +--echo # + +let $statement= + show create table t2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DROP_TABLE +--echo # + +let $statement= + drop table t2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_TABLE TEMPORARY +--echo # + +let $statement= + create temporary table t2 as select * from t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DROP_TABLE TEMPORARY +--echo # + +let $statement= + drop temporary table t2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_TABLE +--echo # + +let $statement= + create table t2 as select * from t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_UPDATE +--echo # + +let $statement= + update t2 set a=a+1 where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_INSERT +--echo # + +let $statement= + insert into t2 set a=((1) in (select * from t1)); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_INSERT_SELECT +--echo # + +let $statement= + insert into t2 select * from t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_REPLACE +--echo # + +let $statement= + replace t2 set a=((1) in (select * from t1)); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_REPLACE_SELECT +--echo # + +let $statement= + replace t2 select * from t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DELETE +--echo # + +let $statement= + delete from t2 where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DELETE_MULTI +--echo # + +let $statement= + delete t2, t3 from t2, t3 where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_UPDATE_MULTI +--echo # + +select * from t2; +let $statement= + update t2, t3 set t3.a=t2.a, t2.a=null where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_LOAD +--echo # + +create table t4 (a varchar(100)); + +let $statement= + load data infile '../../std_data/words.dat' into table t4; +source include/implicit_commit_helper.inc; + +drop table t4; + +--echo # +--echo # SQLCOM_SHOW_DATABASES +--echo # + +let $statement= + show databases where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_TABLES +--echo # + +let $statement= + show tables where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_FIELDS +--echo # + +let $statement= + show fields from t1 where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_KEYS +--echo # + +let $statement= + show keys from t1 where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_VARIABLES +--echo # + +let $statement= + show variables where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_STATUS +--echo # + +let $statement= + show status where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_ENGINE_MUTEX +--echo # + +let $statement= + show engine all mutex; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_PROCESSLIST +--echo # + +let $statement= + show processlist; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_ENGINE_LOGS +--echo # + +let $statement= + show engine all logs; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_ENGINE_STATUS +--echo # + +let $statement= + show engine all status; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_CHARSETS +--echo # + +let $statement= + show charset where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_COLLATIONS +--echo # + +let $statement= + show collation where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_TABLE_STATUS +--echo # + +let $statement= + show table status where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_TRIGGERS +--echo # + +let $statement= + show triggers where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_OPEN_TABLES +--echo # + +let $statement= + show open tables where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_STATUS_PROC +--echo # + +let $statement= + show procedure status where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_STATUS_FUNC +--echo # + +let $statement= + show function status where (1) in (select * from t1); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SET_OPTION +--echo # + +let $statement= + set @a=((1) in (select * from t1)); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DO +--echo # + +let $statement= + do ((1) in (select * from t1)); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CALL +--echo # + +create procedure p1(a int) begin end; + +let $statement= + call p1((1) in (select * from t1)); +source include/implicit_commit_helper.inc; + +drop procedure p1; + +--echo # +--echo # SQLCOM_CREATE_VIEW +--echo # + +let $statement= + create view v1 as select * from t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ALTER_VIEW +--echo # + +let $statement= + alter view v1 as select 2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DROP_VIEW +--echo # + +let $statement= + drop view v1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_INDEX +--echo # + +let $statement= + create index idx1 on t1(a); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DROP_INDEX +--echo # + +let $statement= + drop index idx1 on t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ALTER_TABLE +--echo # + +let $statement= + alter table t1 add column b int; +source include/implicit_commit_helper.inc; + +let $statement= + alter table t1 change b c int; +source include/implicit_commit_helper.inc; + +let $statement= + alter table t1 drop column c; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ALTER_TABLE TEMPORARY +--echo # + +create temporary table t4 (a int); + +let $statement= + alter table t1 add column b int; +source include/implicit_commit_helper.inc; + +let $statement= + alter table t1 change b c int; +source include/implicit_commit_helper.inc; + +let $statement= + alter table t1 drop column c; +source include/implicit_commit_helper.inc; + +drop table t4; + +--echo # +--echo # SQLCOM_TRUNCATE +--echo # + +insert into t2 select * from t1; +let $statement= + truncate table t2; +source include/implicit_commit_helper.inc; +insert into t2 select * from t1; + +--echo # +--echo # SQLCOM_TRUNCATE TEMPORARY +--echo # + +create temporary table t4 as select * from t1; +let $statement= + truncate table t4; +source include/implicit_commit_helper.inc; +drop temporary table t4; + +--echo # +--echo # SQLCOM_SHOW_MASTER_STAT +--echo # + +let $statement= + show master status; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_SLAVE_STAT +--echo # + +let $statement= + show slave status; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_GRANT +--echo # + +let $statement= + grant all on test.t1 to mysqltest_2@localhost with grant option; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_REVOKE +--echo # +let $statement= + revoke select on test.t1 from mysqltest_2@localhost; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_REVOKE_ALL +--echo # + +let $statement= + revoke all on test.t1 from mysqltest_2@localhost; +source include/implicit_commit_helper.inc; + +drop user mysqltest_2@localhost; + +--echo # +--echo # SQLCOM_SHOW_GRANTS +--echo # + +let $statement= + show grants; +source include/implicit_commit_helper.inc; + +let $statement= + show grants for current_user(); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_LOCK_TABLES +--echo # + +let $statement= + lock tables t1 write, trans write; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_UNLOCK_TABLES +--echo # + +let $statement= + unlock tables; +source include/implicit_commit_helper.inc; + +# +# Missing test for lock tables transactional. +# + +--echo # +--echo # SQLCOM_CREATE_DB +--echo # + +let $statement= + create database db2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CHANGE_DB +--echo # + +create table db2.t1 (a int); +insert into db2.t1 values (1); +commit; + +let $statement= + use db2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_CREATE_DB +--echo # + +let $statement= + show create database db2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ALTER_DB +--echo # + +#let $statement= +# alter database db2 character set koi8r; +#source include/implicit_commit_helper.inc; + +#let $statement= +# alter database db2 collate cp1251_general_cs; +#source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ALTER_DB_UPGRADE +--echo # + +#let $statement= +# alter database `#mysql50#db3` upgrade data directory name; +#source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DROP_DB +--echo # + +use db1; + +let $statement= + drop database db2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_REPAIR +--echo # + +let $statement= + repair table t2; +source include/implicit_commit_helper.inc; + +let $statement= + repair table t2 use_frm; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_OPTIMIZE +--echo # + +let $statement= + optimize table t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CHECK +--echo # + +let $statement= + check table t1; +source include/implicit_commit_helper.inc; + +let $statement= + check table t1 extended; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ASSIGN_TO_KEYCACHE +--echo # + +set global keycache.key_buffer_size=128*1024; + +let $statement= + cache index t3 in keycache; +source include/implicit_commit_helper.inc; + +set global keycache.key_buffer_size=0; + +--echo # +--echo # SQLCOM_PRELOAD_KEYS +--echo # + +let $statement= + load index into cache t3; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_FLUSH +--echo # + +let $statement= + flush local privileges; +source include/implicit_commit_helper.inc; + +let $statement= + flush privileges; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_KILL +--echo # + +--echo # +--echo # SQLCOM_ANALYZE +--echo # + +let $statement= + analyze table t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ROLLBACK +--echo # + +let $statement= + rollback; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ROLLBACK_TO_SAVEPOINT +--echo # + + +--echo # +--echo # SQLCOM_COMMIT +--echo # + +let $statement= + commit; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SAVEPOINT +--echo # + +let $statement= + savepoint sp1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_RELEASE_SAVEPOINT +--echo # + +--echo # +--echo # SQLCOM_SLAVE_START +--echo # + +--echo # +--echo # SQLCOM_SLAVE_STOP +--echo # + +--echo # +--echo # SQLCOM_BEGIN +--echo # + +let $statement= + begin; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CHANGE_MASTER +--echo # + +--echo # +--echo # SQLCOM_RENAME_TABLE +--echo # + +let $statement= + rename table t3 to t4; +source include/implicit_commit_helper.inc; + +let $statement= + rename table t4 to t3; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_RESET +--echo # + +--echo # +--echo # SQLCOM_PURGE +--echo # + +--echo # +--echo # SQLCOM_PURGE_BEFORE +--echo # + +--echo # +--echo # SQLCOM_SHOW_BINLOGS +--echo # + +--echo # +--echo # SQLCOM_HA_OPEN +--echo # + +let $statement= + handler t1 open as ha1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_HA_READ +--echo # + +let $statement= + handler ha1 read a first; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_HA_CLOSE +--echo # + +let $statement= + handler ha1 close; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_SLAVE_HOSTS +--echo # + +let $statement= + show slave hosts; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_BINLOG_EVENTS +--echo # + +let $statement= + show binlog events; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_NEW_MASTER +--echo # + +--echo # +--echo # SQLCOM_SHOW_WARNS +--echo # + +let $statement= + show warnings; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_EMPTY_QUERY +--echo # + +--echo # +--echo # SQLCOM_SHOW_ERRORS +--echo # + +let $statement= + show errors; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_STORAGE_ENGINES +--echo # + +let $statement= + show engines; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_PRIVILEGES +--echo # + +let $statement= + show privileges; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_HELP +--echo # + +let $statement= + help 'foo'; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_USER +--echo # + +let $statement= + create user trxusr1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_RENAME_USER +--echo # + +let $statement= + rename user 'trxusr1' to 'trxusr2'; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DROP_USER +--echo # + +let $statement= + drop user trxusr2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CHECKSUM +--echo # + +let $statement= + checksum table t1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_PROCEDURE +--echo # + +let $statement= + create procedure p1(a int) begin end; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ALTER_PROCEDURE +--echo # + +let $statement= + alter procedure p1 comment 'foobar'; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_CREATE_PROC +--echo # + +let $statement= + show create procedure p1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_STATUS_PROC +--echo # + +let $statement= + show procedure status; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_PROC_CODE +--echo # + +# +# Available only on servers with debugging support. +# + +--disable_abort_on_error +let $statement= + show procedure code p1; +source include/implicit_commit_helper.inc; +--enable_abort_on_error + +--echo # +--echo # SQLCOM_DROP_PROCEDURE +--echo # + +let $statement= + drop procedure p1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_FUNCTION +--echo # + +--echo # +--echo # SQLCOM_DROP_FUNCTION +--echo # + +--echo # +--echo # SQLCOM_CREATE_SPFUNCTION +--echo # + +let $statement= + create function f1() returns int return 69; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ALTER_FUNCTION +--echo # + +let $statement= + alter function f1 comment 'comment'; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_CREATE_FUNC +--echo # + +let $statement= + show create function f1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_STATUS_FUNC +--echo # + +let $statement= + show function status like '%f%'; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_FUNC_CODE +--echo # + +# +# Available only on servers with debugging support. +# + +--disable_abort_on_error +let $statement= + show function code f1; +source include/implicit_commit_helper.inc; +--enable_abort_on_error + +--echo # +--echo # SQLCOM_PREPARE +--echo # + +let $statement= + prepare stmt1 from "insert into t1 values (5)"; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_EXECUTE +--echo # + +let $statement= + execute stmt1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DEALLOCATE_PREPARE +--echo # + +let $statement= + deallocate prepare stmt1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_TRIGGER +--echo # + +let $statement= + create trigger trg1 before insert on t1 for each row set @a:=1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_CREATE_TRIGGER +--echo # + +let $statement= + show create trigger trg1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DROP_TRIGGER +--echo # + +let $statement= + drop trigger trg1; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_XA_START +--echo # + +--echo # +--echo # SQLCOM_XA_END +--echo # + +--echo # +--echo # SQLCOM_XA_PREPARE +--echo # + +--echo # +--echo # SQLCOM_XA_COMMIT +--echo # + +--echo # +--echo # SQLCOM_XA_ROLLBACK +--echo # + +--echo # +--echo # SQLCOM_XA_RECOVER +--echo # + +--echo # +--echo # SQLCOM_ALTER_TABLESPACE +--echo # + +--echo # +--echo # SQLCOM_INSTALL_PLUGIN +--echo # + +--echo # +--echo # SQLCOM_SHOW_PLUGINS +--echo # + +--echo # +--echo # SQLCOM_UNINSTALL_PLUGIN +--echo # + +--echo # +--echo # SQLCOM_SHOW_AUTHORS +--echo # + +let $statement= + show authors; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_BINLOG_BASE64_EVENT +--echo # + +--echo # +--echo # SQLCOM_SHOW_CONTRIBUTORS +--echo # + +let $statement= + show contributors; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_CREATE_SERVER +--echo # + +--echo # +--echo # SQLCOM_ALTER_SERVER +--echo # + +--echo # +--echo # SQLCOM_DROP_SERVER +--echo # + +--echo # +--echo # SQLCOM_CREATE_EVENT +--echo # + +let $statement= + create event ev1 on schedule every 1 second do insert into t1 values (6); +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_ALTER_EVENT +--echo # + +let $statement= + alter event ev1 rename to ev2 disable; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_CREATE_EVENT +--echo # + +let $statement= + show create event ev2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_EVENTS +--echo # + +let $statement= + show events; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_DROP_EVENT +--echo # + +let $statement= + drop event ev2; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_BACKUP +--echo # + +#create database backup_db; +# +#let $statement= +# backup database db1 to 'backup_db1.ba'; +#source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_ARCHIVE +--echo # + +# +# --error ER_NOT_ALLOWED_COMMAND +# +#let $statement= +# show backup 'backup_db1.ba'; +#source include/implicit_commit_helper.inc; +# + +--echo # +--echo # SQLCOM_RESTORE +--echo # + +#let $statement= +# restore from 'backup_db1.ba'; +#source include/implicit_commit_helper.inc; + +#--remove_file $MYSQLTEST_VARDIR/master-data/backup_db1.ba +# +#drop database backup_db; + +--echo # +--echo # SQLCOM_BACKUP_TEST +--echo # + +# BACKUP_TEST + +--echo # +--echo # SQLCOM_SHOW_PROFILE +--echo # + +let $statement= + show profile memory; +source include/implicit_commit_helper.inc; + +--echo # +--echo # SQLCOM_SHOW_PROFILES +--echo # + +let $statement= + show profiles; +source include/implicit_commit_helper.inc; + +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; +eval USE $OLD_DB; +DROP DATABASE db1; + +--echo End of tests diff --git a/mysql-test/t/information_schema.test b/mysql-test/t/information_schema.test index 9da7cc1042d..c6cb28a4a30 100644 --- a/mysql-test/t/information_schema.test +++ b/mysql-test/t/information_schema.test @@ -544,6 +544,7 @@ AND table_name not like 'ndb%' AND table_name not like 'innodb_%' GROUP BY TABLE_SCHEMA; + # # TRIGGERS table test # @@ -914,8 +915,8 @@ DROP PROCEDURE p1; DROP USER mysql_bug20230@localhost; # -# Bug#2123 query with a simple non-correlated subquery over -# INFORMARTION_SCHEMA.TABLES +# Bug#21231 query with a simple non-correlated subquery over +# INFORMARTION_SCHEMA.TABLES # SELECT MAX(table_name) FROM information_schema.tables WHERE table_schema IN ('mysql', 'INFORMATION_SCHEMA', 'test'); @@ -1391,9 +1392,66 @@ SET TIMESTAMP=DEFAULT; --echo End of 5.1 tests. +--echo # +--echo # Additional test for WL#3726 "DDL locking for all metadata objects" +--echo # To avoid possible deadlocks process of filling of I_S tables should +--echo # use high-priority metadata lock requests when opening tables. +--echo # Below we just test that we really use high-priority lock request +--echo # since reproducing a deadlock will require much more complex test. +--echo # +--disable_warnings +drop tables if exists t1, t2, t3; +--enable_warnings +create table t1 (i int); +create table t2 (j int primary key auto_increment); +connect (con3726_1,localhost,root,,test); +--echo # Switching to connection 'con3726_1' +connection con3726_1; +lock table t2 read; +connect (con3726_2,localhost,root,,test); +--echo # Switching to connection 'con3726_2' +connection con3726_2; +--echo # RENAME below will be blocked by 'lock table t2 read' above but +--echo # will add two pending requests for exclusive metadata locks. +--send rename table t2 to t3 +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info like "rename table t2 to t3"; +--source include/wait_condition.inc +--echo # These statements should not be blocked by pending lock requests +select table_name, column_name, data_type from information_schema.columns + where table_schema = 'test' and table_name in ('t1', 't2'); +select table_name, auto_increment from information_schema.tables + where table_schema = 'test' and table_name in ('t1', 't2'); +--echo # Switching to connection 'con3726_1' +connection con3726_1; +unlock tables; +--echo # Switching to connection 'con3726_2' +connection con3726_2; +--reap +--echo # Switching to connection 'default' +connection default; +disconnect con3726_1; +disconnect con3726_2; +drop tables t1, t3; + +# +# Bug#39270 I_S optimization algorithm does not work properly in some cases +# +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE; +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME='t1'; +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS + WHERE CONSTRAINT_SCHEMA='test'; +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS + WHERE TABLE_NAME='t1' and TABLE_SCHEMA='test'; +EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TRIGGERS + WHERE EVENT_OBJECT_SCHEMA='test'; + # # Bug#24062 Incorrect error msg after execute DROP TABLE IF EXISTS on information_schema -# +# --error ER_DBACCESS_DENIED_ERROR create table information_schema.t1 (f1 INT); --error ER_DBACCESS_DENIED_ERROR @@ -1429,27 +1487,18 @@ DROP TABLE t1, information_schema.tables; LOCK TABLES t1 READ, information_schema.tables READ; DROP TABLE t1; -# -# Bug#39270 I_S optimization algorithm does not work properly in some cases -# -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE; -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME='t1'; -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS - WHERE CONSTRAINT_SCHEMA='test'; -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS - WHERE TABLE_NAME='t1' and TABLE_SCHEMA='test'; -EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TRIGGERS - WHERE EVENT_OBJECT_SCHEMA='test'; +# Wait till all disconnects are completed +--source include/wait_until_count_sessions.inc # -# Bug #43834 Assertion in Natural_join_column::db_name() on an I_S query +# Bug #43834 Assertion in Natural_join_column::db_name() on an I_S query # + SELECT * -FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE -LEFT JOIN INFORMATION_SCHEMA.COLUMNS -USING (TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME) -WHERE COLUMNS.TABLE_SCHEMA = 'test' -AND COLUMNS.TABLE_NAME = 't1'; +FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE +LEFT JOIN INFORMATION_SCHEMA.COLUMNS +USING (TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME) +WHERE COLUMNS.TABLE_SCHEMA = 'test' +AND COLUMNS.TABLE_NAME = 't1'; + -# Wait till all disconnects are completed ---source include/wait_until_count_sessions.inc diff --git a/mysql-test/t/innodb-lock.test b/mysql-test/t/innodb-lock.test index eacf7e562be..d2f630ccaba 100644 --- a/mysql-test/t/innodb-lock.test +++ b/mysql-test/t/innodb-lock.test @@ -56,9 +56,12 @@ commit; drop table t1; -# -# Try with old lock method (where LOCK TABLE is ignored by InnoDB) -# +--echo # +--echo # Old lock method (where LOCK TABLE was ignored by InnoDB) no longer +--echo # works due to fix for bugs #46272 "MySQL 5.4.4, new MDL: unnecessary +--echo # deadlock" and bug #37346 "innodb does not detect deadlock between +--echo # update and alter table". +--echo # set @@innodb_table_locks=0; @@ -67,36 +70,38 @@ insert into t1 values(0, 0),(1,1),(2,2); commit; SELECT * from t1 where id = 0 FOR UPDATE; +--echo # Connection 'con2'. connection con2; set autocommit=0; set @@innodb_table_locks=0; -# The following statement should work becase innodb doesn't check table locks -lock table t1 write; +--echo # The following statement should block because SQL-level lock +--echo # is taken on t1 which will wait until concurrent transaction +--echo # is commited. +--echo # Sending: +--send lock table t1 write; +--echo # Connection 'con1'. connection con1; +--echo # Wait until LOCK TABLE is blocked on SQL-level lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "lock table t1 write"; +--source include/wait_condition.inc +--echo # We should be able to do UPDATEs and SELECTs within transaction. +update t1 set x=1 where id = 0; +select * from t1; +--echo # Unblock LOCK TABLE. +commit; -# This will be locked by MySQL ---send -update t1 set x=10 where id = 2; ---sleep 2 - +--echo # Connection 'con2'. connection con2; - -# Note that we will get a deadlock if we try to select any rows marked -# for update by con1 ! - -SELECT * from t1 where id = 2; -UPDATE t1 set x=3 where id = 2; -commit; -SELECT * from t1; -commit; +--echo # Reap LOCK TABLE. +--reap unlock tables; +--echo # Connection 'con1'. connection con1; -reap; -commit; -select * from t1; drop table t1; # End of 4.1 tests diff --git a/mysql-test/t/innodb.test b/mysql-test/t/innodb.test index 075058d42c2..c4380ff8f43 100644 --- a/mysql-test/t/innodb.test +++ b/mysql-test/t/innodb.test @@ -1854,16 +1854,15 @@ connect (b,localhost,root,,); connection a; create table t1(a int not null, b int, c int, d int, primary key(a)) engine=innodb; insert into t1(a) values (1),(2),(3); +delimiter |; +create trigger t1t before insert on t1 for each row begin set NEW.b = NEW.a * 10 + 5, NEW.c = NEW.a / 10; end | +delimiter ;| commit; connection b; set autocommit = 0; update t1 set b = 5 where a = 2; connection a; -delimiter |; -create trigger t1t before insert on t1 for each row begin set NEW.b = NEW.a * 10 + 5, NEW.c = NEW.a / 10; end | -delimiter ;| set autocommit = 0; -connection a; insert into t1(a) values (10),(20),(30),(40),(50),(60),(70),(80),(90),(100), (11),(21),(31),(41),(51),(61),(71),(81),(91),(101), (12),(22),(32),(42),(52),(62),(72),(82),(92),(102), @@ -1927,6 +1926,9 @@ insert into t2(a) values(8); delete from t2 where a = 3; update t4 set b = b + 1 where a = 3; commit; +connection a; +commit; +connection b; drop trigger t1t; drop trigger t2t; drop trigger t3t; diff --git a/mysql-test/t/innodb_mysql_lock-master.opt b/mysql-test/t/innodb_mysql_lock-master.opt new file mode 100644 index 00000000000..0041949b829 --- /dev/null +++ b/mysql-test/t/innodb_mysql_lock-master.opt @@ -0,0 +1 @@ +--innodb_lock_wait_timeout=300 diff --git a/mysql-test/t/innodb_mysql_lock.test b/mysql-test/t/innodb_mysql_lock.test new file mode 100644 index 00000000000..6469ef2d229 --- /dev/null +++ b/mysql-test/t/innodb_mysql_lock.test @@ -0,0 +1,175 @@ +-- source include/have_innodb.inc + +# Save the initial number of concurrent sessions. +--source include/count_sessions.inc + +--echo # +--echo # Bug #22876 Four-way deadlock +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +connect (con1,localhost,root,,); +connect (con2,localhost,root,,); +connect (con3,localhost,root,,); + +--echo # Connection 1 +connection con1; +set @@autocommit=0; +CREATE TABLE t1(s1 INT UNIQUE) ENGINE=innodb; +INSERT INTO t1 VALUES (1); + +--echo # Connection 2 +connection con2; +set @@autocommit=0; +INSERT INTO t1 VALUES (2); +--send INSERT INTO t1 VALUES (1) + +--echo # Connection 3 +connection con3; +set @@autocommit=0; +--send DROP TABLE t1 + +--echo # Connection 1 +connection con1; +let $wait_condition= + SELECT COUNT(*) = 1 FROM information_schema.processlist + WHERE info = "INSERT INTO t1 VALUES (1)" and + state = "update"; +--source include/wait_condition.inc +let $wait_condition= + SELECT COUNT(*) = 1 FROM information_schema.processlist + WHERE info = "DROP TABLE t1" and + state = "Waiting for table"; +--source include/wait_condition.inc +--echo # Connection 1 is now holding the lock. +--echo # Issuing insert from connection 1 while connection 2&3 +--echo # is waiting for the lock should give a deadlock error. +--error ER_LOCK_DEADLOCK +INSERT INTO t1 VALUES (2); + +--echo # Cleanup +connection con2; +--reap +commit; +set @@autocommit=1; +connection con1; +commit; +set @@autocommit=1; +connection con3; +--reap +set @@autocommit=1; +connection default; + +disconnect con1; +disconnect con3; + + +--echo # +--echo # Test for bug #37346 "innodb does not detect deadlock between update +--echo # and alter table". +--echo # +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (c1 int primary key, c2 int, c3 int) engine=InnoDB; +insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0); +begin; +--echo # Run statement which acquires X-lock on one of table's rows. +update t1 set c3=c3+1 where c2=3; + +--echo # +--echo # Switching to connection 'con37346'. +connect (con37346,localhost,root,,test,,); +connection con37346; +--echo # The below ALTER TABLE statement should wait till transaction +--echo # in connection 'default' is complete and then succeed. +--echo # It should not deadlock or fail with ER_LOCK_DEADLOCK error. +--echo # Sending: +--send alter table t1 add column c4 int; + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until the above ALTER TABLE gets blocked because this +--echo # connection holds SW metadata lock on table to be altered. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add column c4 int"; +--source include/wait_condition.inc + +--echo # The below statement should succeed. It should not +--echo # deadlock or end with ER_LOCK_DEADLOCK error. +update t1 set c3=c3+1 where c2=4; + +--echo # Unblock ALTER TABLE by committing transaction. +commit; + +--echo # +--echo # Switching to connection 'con37346'. +connection con37346; +--echo # Reaping ALTER TABLE. +--reap + +--echo # +--echo # Switching to connection 'default'. +connection default; +disconnect con37346; +drop table t1; + + +--echo # +--echo # Bug #42147 Concurrent DML and LOCK TABLE ... READ for InnoDB +--echo # table cause warnings in errlog +--echo # + +--echo # +--echo # Note that this test for now relies on a global suppression of +--echo # the warning "Found lock of type 6 that is write and read locked" +--echo # This suppression rule can be removed once Bug#42147 is properly +--echo # fixed. See bug page for more info. +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (i INT) engine= innodb; + +--echo # Connection 2 +--echo # Get user-level lock +connection con2; +SELECT get_lock('bug42147_lock', 60); + +--echo # Connection 1 +connection default; +--send INSERT INTO t1 SELECT get_lock('bug42147_lock', 60) + +--echo # Connection 2 +connection con2; +let $wait_condition= + SELECT COUNT(*) > 0 FROM information_schema.processlist + WHERE state = 'User lock' + AND info = 'INSERT INTO t1 SELECT get_lock(\'bug42147_lock\', 60)'; +--source include/wait_condition.inc +LOCK TABLES t1 READ; +SELECT release_lock('bug42147_lock'); + +--echo # Connection 1 +connection default; +--reap + +--echo # Connection 2 +connection con2; +UNLOCK TABLES; + +--echo # Connection 1 +connection default; +disconnect con2; +DROP TABLE t1; + +# Check that all connections opened by test cases in this file are really +# gone so execution of other tests won't be affected by their presence. +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/t/innodb_mysql_sync.test b/mysql-test/t/innodb_mysql_sync.test new file mode 100644 index 00000000000..3f061e30293 --- /dev/null +++ b/mysql-test/t/innodb_mysql_sync.test @@ -0,0 +1,48 @@ +# +# Test file for InnoDB tests that require the debug sync facility +# +--source include/have_innodb.inc +--source include/have_debug_sync.inc +# Save the initial number of concurrent sessions. +--source include/count_sessions.inc + + +--echo # +--echo # Bug 42074 concurrent optimize table and +--echo # alter table = Assertion failed: thd->is_error() +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +--echo # Create InnoDB table +CREATE TABLE t1 (id INT) engine=innodb; +connect (con2, localhost, root); + +--echo # Connection 1 +--echo # Start optimizing table +connection default; +SET DEBUG_SYNC='ha_admin_try_alter SIGNAL optimize_started WAIT_FOR table_altered'; +--send OPTIMIZE TABLE t1 + +--echo # Connection 2 +--echo # Change table to engine=memory +connection con2; +SET DEBUG_SYNC='now WAIT_FOR optimize_started'; +ALTER TABLE t1 engine=memory; +SET DEBUG_SYNC='now SIGNAL table_altered'; + +--echo # Connection 1 +--echo # Complete optimization +connection default; +--reap + +disconnect con2; +DROP TABLE t1; +SET DEBUG_SYNC='RESET'; + + +# Check that all connections opened by test cases in this file are really +# gone so execution of other tests won't be affected by their presence. +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/t/insert_notembedded.test b/mysql-test/t/insert_notembedded.test index 2950acff3cc..510dc56e8f7 100644 --- a/mysql-test/t/insert_notembedded.test +++ b/mysql-test/t/insert_notembedded.test @@ -174,7 +174,7 @@ connection default; # we must wait till the insert opens and locks the table let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and id = $ID; + where state = "Table lock" and id = $ID; --source include/wait_condition.inc connect (select,localhost,root,,); --echo connection: select diff --git a/mysql-test/t/kill.test b/mysql-test/t/kill.test index 02b033df2e5..b91feb3a1d5 100644 --- a/mysql-test/t/kill.test +++ b/mysql-test/t/kill.test @@ -329,6 +329,243 @@ KILL CONNECTION_ID(); SELECT 1; --connection default +--echo # +--echo # Additional test for WL#3726 "DDL locking for all metadata objects" +--echo # Check that DDL and DML statements waiting for metadata locks can +--echo # be killed. Note that we don't cover all situations here since it +--echo # can be tricky to write test case for some of them (e.g. REPAIR or +--echo # ALTER and other statements under LOCK TABLES). +--echo # +--disable_warnings +drop tables if exists t1, t2, t3; +--enable_warnings + +create table t1 (i int primary key); +connect (blocker, localhost, root, , ); +connect (dml, localhost, root, , ); +connect (ddl, localhost, root, , ); + +--echo # Test for RENAME TABLE +--echo # Switching to connection 'blocker' +connection blocker; +lock table t1 read; +--echo # Switching to connection 'ddl' +connection ddl; +let $ID= `select connection_id()`; +--send rename table t1 to t2 +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--replace_result $ID ID +eval kill query $ID; +--echo # Switching to connection 'ddl' +connection ddl; +--error ER_QUERY_INTERRUPTED +--reap + +--echo # Test for DROP TABLE +--send drop table t1 +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "drop table t1"; +--source include/wait_condition.inc +--replace_result $ID ID +eval kill query $ID; +--echo # Switching to connection 'ddl' +connection ddl; +--error ER_QUERY_INTERRUPTED +--reap + +--echo # Test for CREATE TRIGGER +--send create trigger t1_bi before insert on t1 for each row set @a:=1 +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "create trigger t1_bi before insert on t1 for each row set @a:=1"; +--source include/wait_condition.inc +--replace_result $ID ID +eval kill query $ID; +--echo # Switching to connection 'ddl' +connection ddl; +--error ER_QUERY_INTERRUPTED +--reap + +--echo # +--echo # Tests for various kinds of ALTER TABLE +--echo # +--echo # Full-blown ALTER which should copy table +--send alter table t1 add column j int +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "alter table t1 add column j int"; +--source include/wait_condition.inc +--replace_result $ID ID +eval kill query $ID; +--echo # Switching to connection 'ddl' +connection ddl; +--error ER_QUERY_INTERRUPTED +--reap + +--echo # Two kinds of simple ALTER +--send alter table t1 rename to t2 +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "alter table t1 rename to t2"; +--source include/wait_condition.inc +--replace_result $ID ID +eval kill query $ID; +--echo # Switching to connection 'ddl' +connection ddl; +--error ER_QUERY_INTERRUPTED +--reap +--send alter table t1 disable keys +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "alter table t1 disable keys"; +--source include/wait_condition.inc +--replace_result $ID ID +eval kill query $ID; +--echo # Switching to connection 'ddl' +connection ddl; +--error ER_QUERY_INTERRUPTED +--reap +--echo # Fast ALTER +--send alter table t1 alter column i set default 100 +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "alter table t1 alter column i set default 100"; +--source include/wait_condition.inc +--replace_result $ID ID +eval kill query $ID; +--echo # Switching to connection 'ddl' +connection ddl; +--error ER_QUERY_INTERRUPTED +--reap +--echo # Special case which is triggered only for MERGE tables. +--echo # Switching to connection 'blocker' +connection blocker; +unlock tables; +create table t2 (i int primary key) engine=merge union=(t1); +lock tables t2 read; +--echo # Switching to connection 'ddl' +connection ddl; +--send alter table t2 alter column i set default 100 +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "alter table t2 alter column i set default 100"; +--source include/wait_condition.inc +--replace_result $ID ID +eval kill query $ID; +--echo # Switching to connection 'ddl' +connection ddl; +--error ER_QUERY_INTERRUPTED +--reap + +--echo # Test for DML waiting for meta-data lock +--echo # Switching to connection 'blocker' +connection blocker; +unlock tables; +drop table t2; +create table t2 (k int); +lock tables t1 read; +--echo # Switching to connection 'ddl' +connection ddl; +# Let us add pending exclusive metadata lock on t2 +--send rename tables t1 to t3, t2 to t1 +--echo # Switching to connection 'dml' +connection dml; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "rename tables t1 to t3, t2 to t1"; +--source include/wait_condition.inc +let $ID2= `select connection_id()`; +--send insert into t2 values (1) +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "insert into t2 values (1)"; +--source include/wait_condition.inc +--replace_result $ID2 ID2 +eval kill query $ID2; +--echo # Switching to connection 'dml' +connection dml; +--error ER_QUERY_INTERRUPTED +--reap +--echo # Switching to connection 'blocker' +connection blocker; +unlock tables; +--echo # Switching to connection 'ddl' +connection ddl; +--reap + +--echo # Test for DML waiting for tables to be flushed +--echo # Switching to connection 'blocker' +connection blocker; +lock tables t1 read; +--echo # Switching to connection 'ddl' +connection ddl; +--echo # Let us mark locked table t1 as old +--send flush tables +--echo # Switching to connection 'dml' +connection dml; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Flushing tables" and + info = "flush tables"; +--source include/wait_condition.inc +--send select * from t1 +--echo # Switching to connection 'default' +connection default; +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info = "select * from t1"; +--source include/wait_condition.inc +--replace_result $ID2 ID2 +eval kill query $ID2; +--echo # Switching to connection 'dml' +connection dml; +--error ER_QUERY_INTERRUPTED +--reap +--echo # Switching to connection 'blocker' +connection blocker; +unlock tables; +--echo # Switching to connection 'ddl' +connection ddl; +--reap + +--echo # Cleanup. +--echo # Switching to connection 'default' +connection default; +drop table t3; +drop table t1; + ########################################################################### # Restore global concurrent_insert value. Keep in the end of the test file. diff --git a/mysql-test/t/lock.test b/mysql-test/t/lock.test index 04994e3e48f..eda3e8451dd 100644 --- a/mysql-test/t/lock.test +++ b/mysql-test/t/lock.test @@ -2,8 +2,11 @@ # Testing of table locking # +# Save the initial number of concurrent sessions. +--source include/count_sessions.inc + --disable_warnings -drop table if exists t1,t2; +drop table if exists t1,t2,t3; --enable_warnings CREATE TABLE t1 ( `id` int(11) NOT NULL default '0', `id2` int(11) NOT NULL default '0', `id3` int(11) NOT NULL default '0', `dummy1` char(30) default NULL, PRIMARY KEY (`id`,`id2`), KEY `index_id3` (`id3`)) ENGINE=MyISAM; insert into t1 (id,id2) values (1,1),(1,2),(1,3); @@ -178,6 +181,7 @@ select * from t2; --error ER_TABLE_NOT_LOCKED select * from t3; select * from v_bug5719; +--error ER_LOCK_OR_ACTIVE_TRANSACTION drop view v_bug5719; --echo --echo sic: did not left LOCK TABLES mode automatically @@ -185,7 +189,7 @@ drop view v_bug5719; --error ER_TABLE_NOT_LOCKED select * from t1; unlock tables; -create view v_bug5719 as select * from t1; +create or replace view v_bug5719 as select * from t1; lock tables v_bug5719 write; select * from v_bug5719; --echo @@ -198,6 +202,12 @@ select * from t1; select * from t2; --error ER_TABLE_NOT_LOCKED select * from t3; +--echo Dropping of implicitly locked table is disallowed. +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +drop table t1; +unlock tables; +--echo Now let us also lock table explicitly and drop it. +lock tables t1 write, v_bug5719 write; drop table t1; --echo --echo sic: left LOCK TABLES mode @@ -251,3 +261,324 @@ UNLOCK TABLES; DROP TABLE t1,t2; --echo End of 5.1 tests. + +--echo # +--echo # Ensure that FLUSH TABLES doesn't substitute a base locked table +--echo # with a temporary one. +--echo # + +--disable_warnings +drop table if exists t1, t2; +--enable_warnings +create table t1 (a int); +create table t2 (a int); +lock table t1 write, t2 write; +create temporary table t1 (a int); +flush table t1; +drop temporary table t1; +select * from t1; +unlock tables; +drop table t1, t2; + +--echo # +--echo # Ensure that REPAIR .. USE_FRM works under LOCK TABLES. +--echo # + +--disable_warnings +drop table if exists t1, t2; +--enable_warnings +create table t1 (a int); +create table t2 (a int); +lock table t1 write, t2 write; +repair table t1 use_frm; +repair table t1 use_frm; +select * from t1; +select * from t2; +repair table t2 use_frm; +repair table t2 use_frm; +select * from t1; +unlock tables; +drop table t1, t2; + +--echo # +--echo # Ensure that mi_copy_status is called for two instances +--echo # of the same table when it is reopened after a flush. +--echo # +--disable_warnings +drop table if exists t1; +drop view if exists v1; +--enable_warnings +create table t1 (c1 int); +create view v1 as select * from t1; +lock tables t1 write, v1 write; +flush table t1; +insert into t1 values (33); +flush table t1; +select * from t1; +unlock tables; +drop table t1; +drop view v1; + +--echo # +--echo # WL#4284: Transactional DDL locking +--echo # + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +connect(con1,localhost,root,,); +set autocommit= 0; +insert into t1 values (1); +lock table t1 write; +--echo # Disconnect +--echo # Ensure that metadata locks will be released if there is an open +--echo # transaction (autocommit=off) in conjunction with lock tables. +disconnect con1; +connection default; +drop table t1; + +--echo # Same problem but now for BEGIN + +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (a int); +connect(con1,localhost,root,,); +begin; +insert into t1 values (1); +--echo # Disconnect +--echo # Ensure that metadata locks held by the transaction are released. +disconnect con1; +connection default; +drop table t1; + + +--echo # +--echo # Coverage for situations when we try to execute DDL on tables +--echo # which are locked by LOCK TABLES only implicitly. +--echo # +--disable_warnings +drop tables if exists t1, t2; +drop view if exists v1; +drop function if exists f1; +--enable_warnings +create table t1 (i int); +create table t2 (j int); +--echo # +--echo # Try to perform DDL on table which is locked through view. +create view v1 as select * from t2; +lock tables t1 write, v1 write; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +flush table t2; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +drop table t2; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +alter table t2 add column k int; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +create trigger t2_bi before insert on t2 for each row set @a:=1; +--echo # Repair produces error as part of its result set. +repair table t2; +unlock tables; +drop view v1; +--echo # +--echo # Now, try DDL on table which is locked through routine. +delimiter |; +create function f1 () returns int +begin + insert into t2 values (1); + return 0; +end| +delimiter ;| +create view v1 as select f1() from t1; +lock tables v1 read; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +flush table t2; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +drop table t2; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +alter table t2 add column k int; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +create trigger t2_bi before insert on t2 for each row set @a:=1; +--echo # Repair produces error as part of its result set. +repair table t2; +unlock tables; +drop view v1; +drop function f1; +--echo # +--echo # Finally, try DDL on table which is locked thanks to trigger. +create trigger t1_ai after insert on t1 for each row insert into t2 values (1); +lock tables t1 write; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +flush table t2; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +drop table t2; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +alter table t2 add column k int; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +create trigger t2_bi before insert on t2 for each row set @a:=1; +--echo # Repair produces error as part of its result set. +repair table t2; +unlock tables; +drop trigger t1_ai; +drop tables t1, t2; + + +--echo # +--echo # Bug#45035 " Altering table under LOCK TABLES results in +--echo # "Error 1213 Deadlock found..." +--echo # +--echo # When reopening tables under LOCK TABLES after ALTER TABLE, +--echo # 6.0 used to be taking thr_lock locks one by one, and +--echo # that would lead to a lock conflict. +--echo # Check that taking all locks at once works. +--echo # +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (i int); +lock tables t1 write, t1 as a read, t1 as b read; +alter table t1 add column j int; +unlock tables; +drop table t1; +create temporary table t1 (i int); +--echo # +--echo # This is just for test coverage purposes, +--echo # when this is allowed, remove the --error. +--echo # +--error ER_CANT_REOPEN_TABLE +lock tables t1 write, t1 as a read, t1 as b read; +alter table t1 add column j int; +unlock tables; +drop table t1; +--echo # +--echo # Separate case for partitioned tables is important +--echo # because each partition has an own thr_lock object. +--echo # +create table t1 (i int) partition by list (i) + (partition p0 values in (1), + partition p1 values in (2,3), + partition p2 values in (4,5)); +lock tables t1 write, t1 as a read, t1 as b read; +alter table t1 add column j int; +unlock tables; +drop table t1; + + +--echo # +--echo # Bug #43272 HANDLER SQL command does not work under LOCK TABLES +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (a INT); +LOCK TABLE t1 WRITE; + +--echo # HANDLER commands are not allowed in LOCK TABLES mode +--error ER_LOCK_OR_ACTIVE_TRANSACTION +HANDLER t1 OPEN; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +HANDLER t1 READ FIRST; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +HANDLER t1 CLOSE; + +UNLOCK TABLES; +DROP TABLE t1; + + +--echo # +--echo # Bug#45066 FLUSH TABLES WITH READ LOCK deadlocks against +--echo # LOCK TABLE +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1(a INT); + +LOCK TABLE t1 READ; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +FLUSH TABLES; + +LOCK TABLE t1 WRITE; +FLUSH TABLES; + +--echo # +--echo # If you allow the next combination, you reintroduce bug Bug#45066 +--echo # +LOCK TABLE t1 READ; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +FLUSH TABLES WITH READ LOCK; + +LOCK TABLE t1 WRITE; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +FLUSH TABLES WITH READ LOCK; + +UNLOCK TABLES; +DROP TABLE t1; + + +--echo # +--echo # Simplified test for bug #48538 "Assertion in thr_lock() on LOAD DATA +--echo # CONCURRENT INFILE". +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 (f1 INT, f2 INT) ENGINE = MEMORY; +CREATE TRIGGER t1_ai AFTER INSERT ON t1 FOR EACH ROW + UPDATE LOW_PRIORITY t1 SET f2 = 7; + +--echo # Statement below should fail with ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +--echo # error instead of failing on assertion in table-level locking subsystem. +--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +INSERT INTO t1(f1) VALUES(0); + +DROP TABLE t1; + + +--echo # +--echo # Bug#43685 Lock table affects other non-related tables +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1, t2; +--enable_warnings + +connect (con2, localhost, root); +CREATE TABLE t1 (id INT); +CREATE TABLE t2 (id INT); + +--echo # Connection default +connection default; +LOCK TABLE t1 WRITE; +ANALYZE TABLE t1; + +--echo # Connection con2 +connection con2; +LOCK TABLE t2 WRITE; +--echo # This used to hang until the first connection +--echo # unlocked t1. +FLUSH TABLE t2; + +UNLOCK TABLES; + +--echo # Connection default +connection default; +UNLOCK TABLES; +DROP TABLE t1, t2; +disconnect con2; + + +--echo # +--echo # End of 6.0 tests. +--echo # + +# Check that all connections opened by test cases in this file are really +# gone so execution of other tests won't be affected by their presence. +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/t/lock_multi.test b/mysql-test/t/lock_multi.test index 5670276ee2e..df473876c9a 100644 --- a/mysql-test/t/lock_multi.test +++ b/mysql-test/t/lock_multi.test @@ -8,31 +8,44 @@ drop table if exists t1,t2; # Test to see if select will get the lock ahead of low priority update connect (locker,localhost,root,,); +connect (locker2,localhost,root,,); connect (reader,localhost,root,,); connect (writer,localhost,root,,); connection locker; create table t1(n int); insert into t1 values (1); -lock tables t1 write; +connection locker2; +select get_lock("mysqltest_lock", 100); +connection locker; +send +update t1 set n = 2 and get_lock('mysqltest_lock', 100); connection writer; +# Wait till above update gets blocked on a user lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "User lock" and info = "update t1 set n = 2 and get_lock('mysqltest_lock', 100)"; +--source include/wait_condition.inc send update low_priority t1 set n = 4; connection reader; # Sleep a bit till the update of connection writer is in work and hangs let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "update low_priority t1 set n = 4"; + where state = "Table lock" and info = "update low_priority t1 set n = 4"; --source include/wait_condition.inc send select n from t1; -connection locker; +connection locker2; # Sleep a bit till the select of connection reader is in work and hangs let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "select n from t1"; + where state = "Table lock" and info = "select n from t1"; --source include/wait_condition.inc -unlock tables; +select release_lock("mysqltest_lock"); +connection locker; +reap; +select release_lock("mysqltest_lock"); connection writer; reap; connection reader; @@ -42,19 +55,31 @@ drop table t1; connection locker; create table t1(n int); insert into t1 values (1); -lock tables t1 read; +connection locker2; +select get_lock("mysqltest_lock", 100); +connection locker; +send +select n from t1 where get_lock('mysqltest_lock', 100); connection writer; +# Wait till above select gets blocked on a user lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "User lock" and info = "select n from t1 where get_lock('mysqltest_lock', 100)"; +--source include/wait_condition.inc send update low_priority t1 set n = 4; connection reader; # Sleep a bit till the update of connection writer is in work and hangs let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "update low_priority t1 set n = 4"; + where state = "Table lock" and info = "update low_priority t1 set n = 4"; --source include/wait_condition.inc select n from t1; +connection locker2; +select release_lock("mysqltest_lock"); connection locker; -unlock tables; +reap; +select release_lock("mysqltest_lock"); connection writer; reap; drop table t1; @@ -95,9 +120,10 @@ insert t1 select * from t2; connection locker; let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "insert t1 select * from t2"; + where state = "Waiting for table" and info = "insert t1 select * from t2"; --source include/wait_condition.inc drop table t2; +unlock tables; connection reader; --error ER_NO_SUCH_TABLE reap; @@ -119,9 +145,10 @@ connection locker; # Sleep a bit till the insert of connection reader is in work and hangs let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "insert t1 select * from t2"; + where state = "Waiting for table" and info = "insert t1 select * from t2"; --source include/wait_condition.inc drop table t2; +unlock tables; connection reader; --error ER_NO_SUCH_TABLE reap; @@ -163,8 +190,8 @@ SELECT user.Select_priv FROM user, db WHERE user.user = db.user LIMIT 1; connection locker; # Sleep a bit till the select of connection reader is in work and hangs let $wait_condition= - select count(*) = 1 from information_schema.processlist - where state = "Waiting for table" and info = + SELECT COUNT(*) = 1 FROM information_schema.processlist + WHERE state = "Waiting for table" AND info = "SELECT user.Select_priv FROM user, db WHERE user.user = db.user LIMIT 1"; --source include/wait_condition.inc # Make test case independent from earlier grants. @@ -196,9 +223,10 @@ connection writer; # Sleep a bit till the flush of connection locker is in work and hangs let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables" and info = "FLUSH TABLES WITH READ LOCK"; + where state = "Waiting for table" and info = "FLUSH TABLES WITH READ LOCK"; --source include/wait_condition.inc # This must not block. +--error ER_TABLE_NOT_LOCKED CREATE TABLE t2 (c1 int); UNLOCK TABLES; # @@ -208,7 +236,7 @@ reap; UNLOCK TABLES; # connection default; -DROP TABLE t1, t2; +DROP TABLE t1; # # Test if CREATE TABLE SELECT with LOCK TABLE deadlocks. # @@ -226,7 +254,7 @@ connection writer; # Sleep a bit till the flush of connection locker is in work and hangs let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables" and info = "FLUSH TABLES WITH READ LOCK"; + where state = "Waiting for table" and info = "FLUSH TABLES WITH READ LOCK"; --source include/wait_condition.inc --error ER_TABLE_NOT_LOCKED CREATE TABLE t2 AS SELECT * FROM t1; @@ -298,7 +326,7 @@ connection reader; # Wait till connection writer is blocked let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "alter table t1 auto_increment=0"; + where state = "Waiting for table" and info = "alter table t1 auto_increment=0"; --source include/wait_condition.inc send alter table t1 auto_increment=0; @@ -306,7 +334,7 @@ connection locker; # Wait till connection reader is blocked let $wait_condition= select count(*) = 2 from information_schema.processlist - where state = "Locked" and info = "alter table t1 auto_increment=0"; + where state = "Waiting for table" and info = "alter table t1 auto_increment=0"; --source include/wait_condition.inc unlock tables; connection writer; @@ -337,10 +365,10 @@ connection con2; send flush tables with read lock; connection con5; --echo # con5 -let $show_statement= SHOW PROCESSLIST; -let $field= State; -let $condition= = 'Flushing tables'; ---source include/wait_show_condition.inc +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "flush tables with read lock"; +--source include/wait_condition.inc --echo # global read lock is taken connection con3; --echo # con3 @@ -461,16 +489,16 @@ update t1 set i= 10; connection reader; let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "update t1 set i= 10"; + where state = "Table lock" and info = "update t1 set i= 10"; --source include/wait_condition.inc send select * from t1; connection default; let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "select * from t1"; + where state = "Table lock" and info = "select * from t1"; --source include/wait_condition.inc -let $ID= `select id from information_schema.processlist where state = "Locked" and info = "update t1 set i= 10"`; +let $ID= `select id from information_schema.processlist where state = "Table lock" and info = "update t1 set i= 10"`; --replace_result $ID ID eval kill query $ID; connection reader; @@ -501,6 +529,7 @@ drop table t1; # Disconnect sessions used in many subtests above disconnect locker; +disconnect locker2; disconnect reader; disconnect writer; @@ -528,7 +557,7 @@ connection default; --echo connection: default let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables"; + where state = "Waiting for table"; --source include/wait_condition.inc alter table t1 add column j int; connect (insert,localhost,root,,test,,); @@ -536,7 +565,7 @@ connection insert; --echo connection: insert let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables"; + where state = "Waiting for table"; --source include/wait_condition.inc --send insert into t1 values (1,2); --echo connection: default @@ -586,18 +615,14 @@ connection default; --echo connection: default let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables"; + where state = "Waiting for table"; --source include/wait_condition.inc flush tables; let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables"; + where state = "Waiting for table"; --source include/wait_condition.inc unlock tables; -let $wait_condition= - select count(*) = 0 from information_schema.processlist - where state = "Flushing tables"; ---source include/wait_condition.inc connection flush; --reap connection default; @@ -617,11 +642,11 @@ lock tables t1 read; let $tlwa= `show status like 'Table_locks_waited'`; connect (waiter,localhost,root,,); connection waiter; ---send insert into t1 values(1); +send insert into t1 values(1); connection default; let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and info = "insert into t1 values(1)"; + where state = "Table lock" and info = "insert into t1 values(1)"; --source include/wait_condition.inc let $tlwb= `show status like 'Table_locks_waited'`; unlock tables; @@ -658,21 +683,122 @@ connection default; --echo connection: default let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables"; + where state = "Waiting for table"; --source include/wait_condition.inc flush tables; let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables"; + where state = "Waiting for table"; --source include/wait_condition.inc drop table t1; -let $wait_condition= - select count(*) = 0 from information_schema.processlist - where state = "Flushing tables"; ---source include/wait_condition.inc connection flush; --reap connection default; disconnect flush; + + +--echo # +--echo # Test for bug #46272 "MySQL 5.4.4, new MDL: unnecessary deadlock". +--echo # +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (c1 int primary key, c2 int, c3 int); +insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0); +begin; +update t1 set c3=c3+1 where c2=3; + +--echo # +--echo # Switching to connection 'con46272'. +connect (con46272,localhost,root,,test,,); +connection con46272; +--echo # The below ALTER TABLE statement should wait till transaction +--echo # in connection 'default' is complete and then succeed. +--echo # It should not deadlock or fail with ER_LOCK_DEADLOCK error. +--echo # Sending: +--send alter table t1 add column c4 int; + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until the above ALTER TABLE gets blocked because this +--echo # connection holds SW metadata lock on table to be altered. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add column c4 int"; +--source include/wait_condition.inc + +--echo # The below statement should succeed. It should not +--echo # deadlock or end with ER_LOCK_DEADLOCK error. +update t1 set c3=c3+1 where c2=4; + +--echo # Unblock ALTER TABLE by committing transaction. +commit; + +--echo # +--echo # Switching to connection 'con46272'. +connection con46272; +--echo # Reaping ALTER TABLE. +--reap + +--echo # +--echo # Switching to connection 'default'. +connection default; +disconnect con46272; +drop table t1; + + +--echo # +--echo # Bug#47249 assert in MDL_global_lock::is_lock_type_compatible +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS v1; +--enable_warnings + +--echo # +--echo # Test 1: LOCK TABLES v1 WRITE, t1 READ; +--echo # +--echo # Thanks to the fact that we no longer allow DDL on tables +--echo # which are locked for write implicitly, the exact scenario +--echo # in which assert was failing is no longer repeatable. + +CREATE TABLE t1 ( f1 integer ); +CREATE VIEW v1 AS SELECT f1 FROM t1 ; + +LOCK TABLES v1 WRITE, t1 READ; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +FLUSH TABLE t1; +UNLOCK TABLES; + +# Cleanup +DROP TABLE t1; +DROP VIEW v1; + +--echo # +--echo # Test 2: LOCK TABLES t1 WRITE, v1 READ; +--echo # + +CREATE TABLE t1 ( f1 integer ); +CREATE VIEW v1 AS SELECT f1 FROM t1 ; + +--echo # Connection 2 +connect (con2,localhost,root); +LOCK TABLES t1 WRITE, v1 READ; +FLUSH TABLE t1; +disconnect con2; +--source include/wait_until_disconnected.inc + +--echo # Connection 1 +connection default; +LOCK TABLES t1 WRITE; +FLUSH TABLE t1; # Assertion happened here + +# Cleanup +DROP TABLE t1; +DROP VIEW v1; + + # Wait till all disconnects are completed --source include/wait_until_count_sessions.inc diff --git a/mysql-test/t/lock_sync.test b/mysql-test/t/lock_sync.test index 73289685114..460c0175808 100644 --- a/mysql-test/t/lock_sync.test +++ b/mysql-test/t/lock_sync.test @@ -21,6 +21,7 @@ --echo # TL_WRITE_ALLOW_READ) on this table might have led to deadlock. --disable_warnings drop table if exists t1; +drop view if exists v1; --enable_warnings --echo # Create auxiliary connections used through the test. connect (con_bug45143_1,localhost,root,,test,,); @@ -35,6 +36,9 @@ set @old_general_log = @@global.general_log; set @@global.general_log= OFF; create table t1 (i int) engine=InnoDB; +--echo # We have to use view in order to make LOCK TABLES avoid +--echo # acquiring SNRW metadata lock on table. +create view v1 as select * from t1; insert into t1 values (1); --echo # Prepare user lock which will be used for resuming execution of --echo # the first statement after it acquires TL_WRITE_ALLOW_WRITE lock. @@ -65,14 +69,14 @@ connection con_bug45143_3; --echo # acquiring lock for the the first instance of 't1'. set debug_sync= 'now WAIT_FOR parked'; --echo # Send LOCK TABLE statement which will try to get TL_WRITE lock on 't1': ---send lock table t1 write; +--send lock table v1 write; --echo # Switch to connection 'default'. connection default; --echo # Wait until this LOCK TABLES statement starts waiting for table lock. let $wait_condition= select count(*)= 1 from information_schema.processlist - where state= 'Locked' and - info='lock table t1 write'; + where state= 'Table lock' and + info='lock table v1 write'; --source include/wait_condition.inc --echo # Allow SELECT ... FOR UPDATE to resume. --echo # Since it already has TL_WRITE_ALLOW_WRITE lock on the first instance @@ -110,6 +114,7 @@ disconnect con_bug45143_2; disconnect con_bug45143_3; set debug_sync= 'RESET'; set @@global.general_log= @old_general_log; +drop view v1; drop table t1; diff --git a/mysql-test/t/lowercase_table2.test b/mysql-test/t/lowercase_table2.test index 92add60616a..b8c7f532cde 100644 --- a/mysql-test/t/lowercase_table2.test +++ b/mysql-test/t/lowercase_table2.test @@ -201,10 +201,9 @@ create table t_bug44738_UPPERCASE (i int); drop table t_bug44738_UPPERCASE; --echo # Finally, let us check that another issue which was exposed by ---echo # the original test case is solved. I.e. that fuse in CREATE TABLE ---echo # which ensures that table is not created if there is an entry for ---echo # it in TDC even though it was removed from disk uses normalized ---echo # version of the table name. +--echo # the original test case is solved. I.e. that the table is not +--echo # created if there is an entry for it in TDC even though it was +--echo # removed from disk. create table t_bug44738_UPPERCASE (i int) engine = myisam; --echo # Load table definition in TDC. select table_schema, table_name, table_comment from information_schema.tables @@ -214,9 +213,10 @@ let $MYSQLD_DATADIR= `select @@datadir`; --remove_file $MYSQLD_DATADIR/test/t_bug44738_UPPERCASE.frm --remove_file $MYSQLD_DATADIR/test/t_bug44738_UPPERCASE.MYD --remove_file $MYSQLD_DATADIR/test/t_bug44738_UPPERCASE.MYI ---echo # After manual removal of table still there should be an entry for table ---echo # in TDC so attempt to create table with the same name should fail. ---error ER_TABLE_EXISTS_ERROR +--echo # Check that still there is an entry for table in TDC. +show open tables like 't_bug44738_%'; +--echo # So attempt to create table with the same name should fail. +--error ER_FILE_NOT_FOUND create table t_bug44738_UPPERCASE (i int); --echo # And should succeed after FLUSH TABLES. flush tables; diff --git a/mysql-test/t/mdl_sync.test b/mysql-test/t/mdl_sync.test new file mode 100644 index 00000000000..0f3091794f1 --- /dev/null +++ b/mysql-test/t/mdl_sync.test @@ -0,0 +1,3217 @@ +# +# We need the Debug Sync Facility. +# +--source include/have_debug_sync.inc + +# Save the initial number of concurrent sessions. +--source include/count_sessions.inc + + +# Clean up resources used in this test case. +--disable_warnings +SET DEBUG_SYNC= 'RESET'; +--enable_warnings + +# +# Test the case of when a exclusive lock request waits for a +# shared lock being upgraded to a exclusive lock. +# + +connect (con1,localhost,root,,test,,); +connect (con2,localhost,root,,test,,); +connect (con3,localhost,root,,test,,); + +connection default; + +--disable_warnings +drop table if exists t1,t2,t3; +--enable_warnings + +create table t1 (i int); +create table t2 (i int); + +--echo connection: default +lock tables t2 read; + +connection con1; +--echo connection: con1 +set debug_sync='mdl_upgrade_shared_lock_to_exclusive SIGNAL parked WAIT_FOR go'; +--send alter table t1 rename t3 + +connection default; +--echo connection: default +set debug_sync= 'now WAIT_FOR parked'; + +connection con2; +--echo connection: con2 +set debug_sync='mdl_acquire_lock_wait SIGNAL go'; +--send drop table t1,t2 + +connection con1; +--echo connection: con1 +--reap + +connection default; +--echo connection: default +unlock tables; + +connection con2; +--echo connection: con2 +--error ER_BAD_TABLE_ERROR +--reap + +connection default; +drop table t3; + +disconnect con1; +disconnect con2; +disconnect con3; + +# Clean up resources used in this test case. +--disable_warnings +SET DEBUG_SYNC= 'RESET'; +--enable_warnings + + +--echo # +--echo # Basic test coverage for type-of-operation aware metadata locks. +--echo # +--disable_warnings +drop table if exists t1, t2, t3; +--enable_warnings +connect(mdl_con1,localhost,root,,); +connect(mdl_con2,localhost,root,,); +connect(mdl_con3,localhost,root,,); +connection default; +set debug_sync= 'RESET'; +create table t1 (c1 int); + +--echo # +--echo # A) First let us check compatibility rules between differend kinds of +--echo # type-of-operation aware metadata locks. +--echo # Of course, these rules are already covered by the tests scattered +--echo # across the test suite. But it still makes sense to have one place +--echo # which covers all of them. +--echo # + +--echo # 1) Acquire S (simple shared) lock on the table (by using HANDLER): +--echo # +handler t1 open; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that S, SH, SR and SW locks are compatible with it. +handler t1 open t; +handler t close; +select column_name from information_schema.columns where + table_schema='test' and table_name='t1'; +select count(*) from t1; +insert into t1 values (1), (1); +--echo # Check that SNW lock is compatible with it. To do this use ALTER TABLE +--echo # which will fail after opening the table and thus obtaining SNW metadata +--echo # lock. +--error ER_DUP_ENTRY +alter table t1 add primary key (c1); +--echo # Check that SNRW lock is compatible with S lock. +lock table t1 write; +insert into t1 values (1); +unlock tables; +--echo # Check that X lock is incompatible with S lock. +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above RENAME is blocked because of S lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Unblock RENAME TABLE. +handler t1 close; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping RENAME TABLE. +--reap +--echo # Restore the original state of the things. +rename table t2 to t1; +--echo # +--echo # Switching to connection 'default'. +connection default; +handler t1 open; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that upgrade from SNW to X is blocked by presence of S lock. +--echo # Sending: +--send alter table t1 add column c2 int; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above ALTER TABLE is blocked because of S lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add column c2 int"; +--source include/wait_condition.inc +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Unblock ALTER TABLE. +handler t1 close; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping ALTER TABLE. +--reap +--echo # Restore the original state of the things. +alter table t1 drop column c2; +--echo # +--echo # Switching to connection 'default'. +connection default; +handler t1 open; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that upgrade from SNRW to X is blocked by presence of S lock. +lock table t1 write; +--echo # Sending: +--send alter table t1 add column c2 int; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above upgrade of SNRW to X in ALTER TABLE is blocked +--echo # because of S lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add column c2 int"; +--source include/wait_condition.inc +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Unblock ALTER TABLE. +handler t1 close; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping ALTER TABLE. +--reap +--echo # Restore the original state of the things. +alter table t1 drop column c2; +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # +--echo # 2) Acquire SH (shared high-priority) lock on the table. +--echo # We have to involve DEBUG_SYNC facility for this as usually +--echo # such kind of locks are short-lived. +--echo # +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +--echo # Sending: +--send select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t1'; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +set debug_sync= 'now WAIT_FOR locked'; +--echo # Check that S, SH, SR and SW locks are compatible with it. +handler t1 open; +handler t1 close; +select column_name from information_schema.columns where + table_schema='test' and table_name='t1'; +select count(*) from t1; +insert into t1 values (1); +--echo # Check that SNW lock is compatible with it. To do this use ALTER TABLE +--echo # which will fail after opening the table and thus obtaining SNW metadata +--echo # lock. +--error ER_DUP_ENTRY +alter table t1 add primary key (c1); +--echo # Check that SNRW lock is compatible with SH lock. +lock table t1 write; +delete from t1 limit 1; +unlock tables; +--echo # Check that X lock is incompatible with SH lock. +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above RENAME is blocked because of SH lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE. +set debug_sync= 'now SIGNAL finish'; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping SELECT ... FROM I_S. +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping RENAME TABLE. +--reap +--echo # Restore the original state of the things. +rename table t2 to t1; +--echo # +--echo # Switching to connection 'default'. +connection default; +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +--echo # Sending: +--send select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t1'; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +set debug_sync= 'now WAIT_FOR locked'; +--echo # Check that upgrade from SNW to X is blocked by presence of SH lock. +--echo # Sending: +--send alter table t1 add column c2 int; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above ALTER TABLE is blocked because of SH lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add column c2 int"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE. +set debug_sync= 'now SIGNAL finish'; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping SELECT ... FROM I_S. +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping ALTER TABLE. +--reap +--echo # Restore the original state of the things. +alter table t1 drop column c2; +--echo # +--echo # Switching to connection 'default'. +connection default; +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +--send select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t1'; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +set debug_sync= 'now WAIT_FOR locked'; +--echo # Check that upgrade from SNRW to X is blocked by presence of S lock. +lock table t1 write; +--echo # Sending: +--send alter table t1 add column c2 int; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above upgrade of SNRW to X in ALTER TABLE is blocked +--echo # because of S lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add column c2 int"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE. +set debug_sync= 'now SIGNAL finish'; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping SELECT ... FROM I_S. +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping ALTER TABLE. +--reap +--echo # Restore the original state of the things. +alter table t1 drop column c2; +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # +--echo # +--echo # 3) Acquire SR lock on the table. +--echo # +--echo # +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that S, SH, SR and SW locks are compatible with it. +handler t1 open; +handler t1 close; +select column_name from information_schema.columns where + table_schema='test' and table_name='t1'; +select count(*) from t1; +insert into t1 values (1); +--echo # Check that SNW lock is compatible with it. To do this use ALTER TABLE +--echo # which will fail after opening the table and thus obtaining SNW metadata +--echo # lock. +--error ER_DUP_ENTRY +alter table t1 add primary key (c1); +--echo # Check that SNRW lock is not compatible with SR lock. +--echo # Sending: +--send lock table t1 write; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Check that the above LOCK TABLES is blocked because of SR lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "lock table t1 write"; +--source include/wait_condition.inc +--echo # Unblock LOCK TABLES. +commit; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping LOCK TABLES. +--reap +delete from t1 limit 1; +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that X lock is incompatible with SR lock. +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above RENAME is blocked because of SR lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Unblock RENAME TABLE. +commit; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping RENAME TABLE. +--reap +--echo # Restore the original state of the things. +rename table t2 to t1; +--echo # +--echo # Switching to connection 'default'. +connection default; +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that upgrade from SNW to X is blocked by presence of SR lock. +--echo # Sending: +--send alter table t1 add column c2 int; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above ALTER TABLE is blocked because of SR lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add column c2 int"; +--source include/wait_condition.inc +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Unblock ALTER TABLE. +commit; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping ALTER TABLE. +--reap +--echo # Restore the original state of the things. +alter table t1 drop column c2; +--echo # +--echo # There is no need to check that upgrade from SNRW to X is blocked +--echo # by presence of SR lock because SNRW is incompatible with SR anyway. +--echo # +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # +--echo # +--echo # 4) Acquire SW lock on the table. +--echo # +--echo # +begin; +insert into t1 values (1); +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that S, SH, SR and SW locks are compatible with it. +handler t1 open; +handler t1 close; +select column_name from information_schema.columns where + table_schema='test' and table_name='t1'; +select count(*) from t1; +insert into t1 values (1); +--echo # Check that SNW lock is not compatible with SW lock. +--echo # Again we use ALTER TABLE which fails after opening +--echo # the table to avoid upgrade of SNW -> X. +--echo # Sending: +--send alter table t1 add primary key (c1); +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Check that the above ALTER TABLE is blocked because of SW lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add primary key (c1)"; +--source include/wait_condition.inc +--echo # Unblock ALTER TABLE. +commit; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping ALTER TABLE. +--error ER_DUP_ENTRY +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +begin; +insert into t1 values (1); +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that SNRW lock is not compatible with SW lock. +--echo # Sending: +--send lock table t1 write; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Check that the above LOCK TABLES is blocked because of SW lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "lock table t1 write"; +--source include/wait_condition.inc +--echo # Unblock LOCK TABLES. +commit; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping LOCK TABLES. +--reap +delete from t1 limit 2; +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +begin; +insert into t1 values (1); +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that X lock is incompatible with SW lock. +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above RENAME is blocked because of SW lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Unblock RENAME TABLE. +commit; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping RENAME TABLE. +--reap +--echo # Restore the original state of the things. +rename table t2 to t1; +--echo # +--echo # There is no need to check that upgrade from SNW/SNRW to X is +--echo # blocked by presence of SW lock because SNW/SNRW is incompatible +--echo # with SW anyway. +--echo # +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # +--echo # +--echo # 5) Acquire SNW lock on the table. We have to use DEBUG_SYNC for +--echo # this, to prevent SNW from being immediately upgraded to X. +--echo # +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +--echo # Sending: +--send alter table t1 add primary key (c1); +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +set debug_sync= 'now WAIT_FOR locked'; +--echo # Check that S, SH and SR locks are compatible with it. +handler t1 open; +handler t1 close; +select column_name from information_schema.columns where + table_schema='test' and table_name='t1'; +select count(*) from t1; +--echo # Check that SW lock is incompatible with SNW lock. +--echo # Sending: +--send delete from t1 limit 2; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above DELETE is blocked because of SNW lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "delete from t1 limit 2"; +--source include/wait_condition.inc +--echo # Unblock ALTER and thus DELETE. +set debug_sync= 'now SIGNAL finish'; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping ALTER TABLE. +--error ER_DUP_ENTRY +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping DELETE. +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +--echo # Sending: +--send alter table t1 add primary key (c1); +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +set debug_sync= 'now WAIT_FOR locked'; +--echo # Check that SNW lock is incompatible with SNW lock. +--echo # Sending: +--send alter table t1 add primary key (c1); +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above ALTER is blocked because of SNW lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add primary key (c1)"; +--source include/wait_condition.inc +--echo # Unblock ALTERs. +set debug_sync= 'now SIGNAL finish'; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping first ALTER TABLE. +--error ER_DUP_ENTRY +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping another ALTER TABLE. +--error ER_DUP_ENTRY +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +--echo # Sending: +--send alter table t1 add primary key (c1); +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +set debug_sync= 'now WAIT_FOR locked'; +--echo # Check that SNRW lock is incompatible with SNW lock. +--echo # Sending: +--send lock table t1 write; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above LOCK TABLES is blocked because of SNW lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "lock table t1 write"; +--source include/wait_condition.inc +--echo # Unblock ALTER and thus LOCK TABLES. +set debug_sync= 'now SIGNAL finish'; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping ALTER TABLE. +--error ER_DUP_ENTRY +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping LOCK TABLES +--reap +insert into t1 values (1); +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +--echo # Sending: +--send alter table t1 add primary key (c1); +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +set debug_sync= 'now WAIT_FOR locked'; +--echo # Check that X lock is incompatible with SNW lock. +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above RENAME is blocked because of SNW lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Unblock ALTER and thus RENAME TABLE. +set debug_sync= 'now SIGNAL finish'; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping ALTER TABLE. +--error ER_DUP_ENTRY +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping RENAME TABLE +--reap +--echo # Revert back to original state of things. +rename table t2 to t1; +--echo # +--echo # There is no need to check that upgrade from SNW/SNRW to X is +--echo # blocked by presence of another SNW lock because SNW/SNRW is +--echo # incompatible with SNW anyway. +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # +--echo # +--echo # 6) Acquire SNRW lock on the table. +--echo # +--echo # +lock table t1 write; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that S and SH locks are compatible with it. +handler t1 open; +handler t1 close; +select column_name from information_schema.columns where + table_schema='test' and table_name='t1'; +--echo # Check that SR lock is incompatible with SNRW lock. +--echo # Sending: +--send select count(*) from t1; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Check that the above SELECT is blocked because of SNRW lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "select count(*) from t1"; +--source include/wait_condition.inc +--echo # Unblock SELECT. +unlock tables; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping SELECT. +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +lock table t1 write; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that SW lock is incompatible with SNRW lock. +--echo # Sending: +--send delete from t1 limit 1; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Check that the above DELETE is blocked because of SNRW lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "delete from t1 limit 1"; +--source include/wait_condition.inc +--echo # Unblock DELETE. +unlock tables; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping DELETE. +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +lock table t1 write; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that SNW lock is incompatible with SNRW lock. +--echo # Sending: +--send alter table t1 add primary key (c1); +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Check that the above ALTER is blocked because of UNWR lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add primary key (c1)"; +--source include/wait_condition.inc +--echo # Unblock ALTER. +unlock tables; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping ALTER TABLE. +--error ER_DUP_ENTRY +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +lock table t1 write; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that SNRW lock is incompatible with SNRW lock. +--echo # Sending: +--send lock table t1 write; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Check that the above LOCK TABLES is blocked because of SNRW lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "lock table t1 write"; +--source include/wait_condition.inc +--echo # Unblock waiting LOCK TABLES. +unlock tables; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping LOCK TABLES +--reap +insert into t1 values (1); +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +lock table t1 write; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that X lock is incompatible with SNRW lock. +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Check that the above RENAME is blocked because of SNRW lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE +unlock tables; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping RENAME TABLE +--reap +--echo # Revert back to original state of things. +rename table t2 to t1; +--echo # +--echo # There is no need to check that upgrade from SNW/SNRW to X is +--echo # blocked by presence of another SNRW lock because SNW/SNRW is +--echo # incompatible with SNRW anyway. +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # +--echo # +--echo # 7) Now do the same round of tests for X lock. We use additional +--echo # table to get long-lived lock of this type. +--echo # +create table t2 (c1 int); +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Take a lock on t2, so RENAME TABLE t1 TO t2 will get blocked +--echo # after acquiring X lock on t1. +lock tables t2 read; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that RENAME has acquired X lock on t1 and is waiting for t2. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Check that S lock in incompatible with X lock. +--echo # Sending: +--send handler t1 open; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above HANDLER statement is blocked because of X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "handler t1 open"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping RENAME TABLE. +--error ER_TABLE_EXISTS_ERROR +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping HANDLER. +--reap +handler t1 close; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Prepare for blocking RENAME TABLE. +lock tables t2 read; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that RENAME has acquired X lock on t1 and is waiting for t2. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Check that SH lock in incompatible with X lock. +--echo # Sending: +--send select column_name from information_schema.columns where table_schema='test' and table_name='t1'; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above SELECT ... FROM I_S ... statement is blocked +--echo # because of X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info like "select column_name from information_schema.columns%"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping RENAME TABLE. +--error ER_TABLE_EXISTS_ERROR +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping SELECT ... FROM I_S. +--reap +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Prepare for blocking RENAME TABLE. +lock tables t2 read; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that RENAME has acquired X lock on t1 and is waiting for t2. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Check that SR lock in incompatible with X lock. +--echo # Sending: +--send select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above SELECT statement is blocked +--echo # because of X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "select count(*) from t1"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping RENAME TABLE. +--error ER_TABLE_EXISTS_ERROR +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping SELECT. +--reap +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Prepare for blocking RENAME TABLE. +lock tables t2 read; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that RENAME has acquired X lock on t1 and is waiting for t2. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Check that SW lock in incompatible with X lock. +--echo # Sending: +--send delete from t1 limit 1; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above DELETE statement is blocked +--echo # because of X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "delete from t1 limit 1"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping RENAME TABLE. +--error ER_TABLE_EXISTS_ERROR +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping DELETE. +--reap +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Prepare for blocking RENAME TABLE. +lock tables t2 read; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that RENAME has acquired X lock on t1 and is waiting for t2. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Check that SNW lock is incompatible with X lock. +--echo # Sending: +--send alter table t1 add primary key (c1); +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above ALTER statement is blocked +--echo # because of X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "alter table t1 add primary key (c1)"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping RENAME TABLE +--error ER_TABLE_EXISTS_ERROR +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping ALTER. +--error ER_DUP_ENTRY +--reap +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Prepare for blocking RENAME TABLE. +lock tables t2 read; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that RENAME has acquired X lock on t1 and is waiting for t2. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Check that SNRW lock is incompatible with X lock. +--echo # Sending: +--send lock table t1 write; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above LOCK TABLE statement is blocked +--echo # because of X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "lock table t1 write"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping RENAME TABLE +--error ER_TABLE_EXISTS_ERROR +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping LOCK TABLE. +--reap +unlock tables; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Prepare for blocking RENAME TABLE. +lock tables t2 read; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that RENAME has acquired X lock on t1 and is waiting for t2. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Check that X lock is incompatible with X lock. +--echo # Sending: +--send rename table t1 to t3; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above RENAME statement is blocked +--echo # because of X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t1 to t3"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping RENAME TABLE +--error ER_TABLE_EXISTS_ERROR +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping RENAME. +--reap +rename table t3 to t1; + +--echo # +--echo # B) Now let us test compatibility in cases when both locks +--echo # are pending. I.e. let us test rules for priorities between +--echo # different types of metadata locks. +--echo # + +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # +--echo # 1) Check compatibility for pending SNW lock. +--echo # +--echo # Acquire SW lock in order to create pending SNW lock later. +begin; +insert into t1 values (1); +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Add pending SNW lock. +--echo # Sending: +--send alter table t1 add primary key (c1); +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that ALTER TABLE is waiting with pending SNW lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "alter table t1 add primary key (c1)"; +--source include/wait_condition.inc +--echo # Check that S, SH and SR locks are compatible with pending SNW +handler t1 open t; +handler t close; +select column_name from information_schema.columns where + table_schema='test' and table_name='t1'; +select count(*) from t1; +--echo # Check that SW is incompatible with pending SNW +--echo # Sending: +--send delete from t1 limit 1; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above DELETE is blocked because of pending SNW lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "delete from t1 limit 1"; +--source include/wait_condition.inc +--echo # Unblock ALTER TABLE. +commit; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping ALTER. +--error ER_DUP_ENTRY +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping DELETE. +--reap +--echo # +--echo # We can't do similar check for SNW, SNRW and X locks because +--echo # they will also be blocked by active SW lock. +--echo # +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # +--echo # 2) Check compatibility for pending SNRW lock. +--echo # +--echo # Acquire SR lock in order to create pending SNRW lock. +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Add pending SNRW lock. +--echo # Sending: +--send lock table t1 write; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that LOCK TABLE is waiting with pending SNRW lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "lock table t1 write"; +--source include/wait_condition.inc +--echo # Check that S and SH locks are compatible with pending SNRW +handler t1 open t; +handler t close; +select column_name from information_schema.columns where + table_schema='test' and table_name='t1'; +--echo # Check that SR is incompatible with pending SNRW +--echo # Sending: +--send select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above SELECT is blocked because of pending SNRW lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "select count(*) from t1"; +--source include/wait_condition.inc +--echo # Unblock LOCK TABLE. +commit; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping LOCK TABLE. +--reap +unlock tables; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping SELECT. +--reap +--echo # Restore pending SNRW lock. +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Sending: +--send lock table t1 write; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that LOCK TABLE is waiting with pending SNRW lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "lock table t1 write"; +--source include/wait_condition.inc +--echo # Check that SW is incompatible with pending SNRW +--echo # Sending: +--send insert into t1 values (1); +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above INSERT is blocked because of pending SNRW lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "insert into t1 values (1)"; +--source include/wait_condition.inc +--echo # Unblock LOCK TABLE. +commit; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping LOCK TABLE. +--reap +unlock tables; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping INSERT. +--reap +--echo # Restore pending SNRW lock. +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Sending: +--send lock table t1 write; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that LOCK TABLE is waiting with pending SNRW lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "lock table t1 write"; +--source include/wait_condition.inc +--echo # Check that SNW is compatible with pending SNRW +--echo # So ALTER TABLE statements are not starved by LOCK TABLEs. +--error ER_DUP_ENTRY +alter table t1 add primary key (c1); +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Unblock LOCK TABLE. +commit; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping LOCK TABLE. +--reap +unlock tables; +--echo # +--echo # We can't do similar check for SNRW and X locks because +--echo # they will also be blocked by active SR lock. +--echo # +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # +--echo # 3) Check compatibility for pending X lock. +--echo # +--echo # Acquire SR lock in order to create pending X lock. +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Add pending X lock. +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that RENAME TABLE is waiting with pending X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Check that SH locks are compatible with pending X +select column_name from information_schema.columns where + table_schema='test' and table_name='t1'; +--echo # Check that S is incompatible with pending X +--echo # Sending: +--send handler t1 open; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above HANDLER OPEN is blocked because of pending X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "handler t1 open"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE. +commit; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping RENAME TABLE. +--error ER_TABLE_EXISTS_ERROR +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping HANDLER t1 OPEN. +--reap +handler t1 close; +--echo # Restore pending X lock. +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Add pending X lock. +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that RENAME TABLE is waiting with pending X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Check that SR is incompatible with pending X +--echo # Sending: +--send select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above SELECT is blocked because of pending X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "select count(*) from t1"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE. +commit; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping RENAME TABLE. +--error ER_TABLE_EXISTS_ERROR +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping SELECT. +--reap +--echo # Restore pending X lock. +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Add pending X lock. +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that RENAME TABLE is waiting with pending X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Check that SW is incompatible with pending X +--echo # Sending: +--send delete from t1 limit 1; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above DELETE is blocked because of pending X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "delete from t1 limit 1"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE. +commit; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping RENAME TABLE. +--error ER_TABLE_EXISTS_ERROR +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping DELETE. +--reap +--echo # Restore pending X lock. +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Add pending X lock. +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that RENAME TABLE is waiting with pending X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Check that SNW is incompatible with pending X +--echo # Sending: +--send alter table t1 add primary key (c1); +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above ALTER TABLE is blocked because of pending X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "alter table t1 add primary key (c1)"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE. +commit; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping RENAME TABLE. +--error ER_TABLE_EXISTS_ERROR +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping ALTER TABLE. +--error ER_DUP_ENTRY +--reap +--echo # Restore pending X lock. +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +handler t1 open; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Add pending X lock. +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that RENAME TABLE is waiting with pending X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Check that SNRW is incompatible with pending X +--echo # Sending: +--send lock table t1 write; +--echo # +--echo # Switching to connection 'mdl_con3'. +connection mdl_con3; +--echo # Check that the above LOCK TABLES is blocked because of pending X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "lock table t1 write"; +--source include/wait_condition.inc +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Unblock RENAME TABLE. +handler t1 close; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping RENAME TABLE. +--error ER_TABLE_EXISTS_ERROR +--reap +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reaping LOCK TABLES. +--reap +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; + +--echo # +--echo # +--echo # C) Now let us test how type-of-operation locks are handled in +--echo # transactional context. Obviously we are mostly interested +--echo # in conflicting types of locks. +--echo # + +--echo # +--echo # 1) Let us check how various locks used within transactional +--echo # context interact with active/pending SNW lock. +--echo # +--echo # We start with case when we are acquiring lock on the table +--echo # which was not used in the transaction before. +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Create an active SNW lock on t2. +--echo # We have to use DEBUG_SYNC facility as otherwise SNW lock +--echo # will be immediately released (or upgraded to X lock). +insert into t2 values (1), (1); +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +--echo # Sending: +--send alter table t2 add primary key (c1); +--echo # +--echo # Switching to connection 'default'. +connection default; +set debug_sync= 'now WAIT_FOR locked'; +--echo # SR lock should be acquired without any waiting. +select count(*) from t2; +commit; +--echo # Now let us check that we will wait in case of SW lock. +begin; +select count(*) from t1; +--echo # Sending: +--send insert into t2 values (1); +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above INSERT is blocked. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "insert into t2 values (1)"; +--source include/wait_condition.inc +--echo # Unblock ALTER TABLE and thus INSERT. +set debug_sync= 'now SIGNAL finish'; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reap ALTER TABLE. +--error ER_DUP_ENTRY +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap INSERT. +--reap +commit; +--echo # +--echo # Now let us see what happens when we are acquiring lock on the table +--echo # which is already used in transaction. +--echo # +--echo # *) First, case when transaction which has SR lock on the table also +--echo # locked in SNW mode acquires yet another SR lock and then tries +--echo # to acquire SW lock. +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Create an active SNW lock on t1. +set debug_sync= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +--echo # Sending: +--send alter table t1 add primary key (c1); +--echo # +--echo # Switching to connection 'default'. +connection default; +set debug_sync= 'now WAIT_FOR locked'; +--echo # We should still be able to get SR lock without waiting. +select count(*) from t1; +--echo # Since the above ALTER TABLE is not upgrading SNW lock to X by waiting +--echo # for SW lock we won't create deadlock. +--echo # So the below INSERT should not end-up with ER_LOCK_DEADLOCK error. +--echo # Sending: +--send insert into t1 values (1); +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above INSERT is blocked. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "insert into t1 values (1)"; +--source include/wait_condition.inc +--echo # Unblock ALTER TABLE and thus INSERT. +set debug_sync= 'now SIGNAL finish'; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reap ALTER TABLE. +--error ER_DUP_ENTRY +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap INSERT. +--reap +commit; +--echo # +--echo # **) Now test in which transaction that has SW lock on the table +--echo # against which there is pending SNW lock acquires SR and SW +--echo # locks on this table. +--echo # +begin; +insert into t1 values (1); +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Create pending SNW lock on t1. +--echo # Sending: +--send alter table t1 add primary key (c1); +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until ALTER TABLE starts waiting for SNW lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "alter table t1 add primary key (c1)"; +--source include/wait_condition.inc +--echo # We should still be able to get both SW and SR locks without waiting. +select count(*) from t1; +delete from t1 limit 1; +--echo # Unblock ALTER TABLE. +commit; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reap ALTER TABLE. +--error ER_DUP_ENTRY +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # +--echo # 2) Now similar tests for active SNW lock which is being upgraded +--echo # to X lock. +--echo # +--echo # Again we start with case when we are acquiring lock on the +--echo # table which was not used in the transaction before. +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Start transaction which will prevent SNW -> X upgrade from +--echo # completing immediately. +begin; +select count(*) from t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Create SNW lock pending upgrade to X on t2. +--echo # Sending: +--send alter table t2 add column c2 int; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until ALTER TABLE starts waiting X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "alter table t2 add column c2 int"; +--source include/wait_condition.inc +--echo # Check that attempt to acquire SR lock on t2 causes waiting. +--echo # Sending: +--send select count(*) from t2; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above SELECT is blocked. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "select count(*) from t2"; +--source include/wait_condition.inc +--echo # Unblock ALTER TABLE. +commit; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reap ALTER TABLE. +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap SELECT. +--reap +commit; +--echo # Do similar check for SW lock. +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Start transaction which will prevent SNW -> X upgrade from +--echo # completing immediately. +begin; +select count(*) from t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Create SNW lock pending upgrade to X on t2. +--echo # Sending: +--send alter table t2 drop column c2; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until ALTER TABLE starts waiting X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "alter table t2 drop column c2"; +--source include/wait_condition.inc +--echo # Check that attempt to acquire SW lock on t2 causes waiting. +--echo # Sending: +--send insert into t2 values (1); +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above INSERT is blocked. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "insert into t2 values (1)"; +--source include/wait_condition.inc +--echo # Unblock ALTER TABLE. +commit; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reap ALTER TABLE. +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap INSERT. +--reap +commit; +--echo # +--echo # Test for the case in which we are acquiring lock on the table +--echo # which is already used in transaction. +--echo # +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Create SNW lock pending upgrade to X. +--echo # Sending: +--send alter table t1 add column c2 int; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until ALTER TABLE starts waiting X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "alter table t1 add column c2 int"; +--source include/wait_condition.inc +--echo # Check that transaction is still able to acquire SR lock. +select count(*) from t1; +--echo # Waiting trying to acquire SW lock will cause deadlock and +--echo # therefore should cause an error. +--error ER_LOCK_DEADLOCK +delete from t1 limit 1; +--echo # Unblock ALTER TABLE. +commit; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reap ALTER TABLE. +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # +--echo # 3) Check how various locks used within transactional context +--echo # interact with active/pending SNRW lock. +--echo # +--echo # Once again we start with case when we are acquiring lock on +--echo # the table which was not used in the transaction before. +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +lock table t2 write; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Attempt to acquire SR should be blocked. It should +--echo # not cause errors as it does not creates deadlock. +--echo # Sending: +--send select count(*) from t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that the above SELECT is blocked +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "select count(*) from t2"; +--source include/wait_condition.inc +--echo # Unblock SELECT. +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap SELECT. +--reap +commit; +--echo # Repeat the same test for SW lock. +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +lock table t2 write; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Again attempt to acquire SW should be blocked and should +--echo # not cause any errors. +--echo # Sending: +--send delete from t2 limit 1; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Check that the above DELETE is blocked +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "delete from t2 limit 1"; +--source include/wait_condition.inc +--echo # Unblock DELETE. +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap DELETE. +--reap +commit; +--echo # +--echo # Now coverage for the case in which we are acquiring lock on +--echo # the table which is already used in transaction and against +--echo # which there is a pending SNRW lock request. +--echo # +--echo # *) Let us start with case when transaction has only a SR lock. +--echo # +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Sending: +--send lock table t1 write; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until LOCK TABLE is blocked creating pending request for X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "lock table t1 write"; +--source include/wait_condition.inc +--echo # Check that another instance of SR lock is granted without waiting. +select count(*) from t1; +--echo # Attempt to wait for SW lock will lead to deadlock, thus +--echo # the below statement should end with ER_LOCK_DEADLOCK error. +--error ER_LOCK_DEADLOCK +delete from t1 limit 1; +--echo # Unblock LOCK TABLES. +commit; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reap LOCK TABLES. +--reap +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # +--echo # **) Now case when transaction has a SW lock. +--echo # +begin; +delete from t1 limit 1; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Sending: +--send lock table t1 write; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until LOCK TABLE is blocked creating pending request for X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "lock table t1 write"; +--source include/wait_condition.inc +--echo # Check that both SR and SW locks are granted without waiting +--echo # and errors. +select count(*) from t1; +insert into t1 values (1, 1); +--echo # Unblock LOCK TABLES. +commit; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reap LOCK TABLES. +--reap +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # +--echo # 4) Check how various locks used within transactional context +--echo # interact with active/pending X lock. +--echo # +--echo # As usual we start with case when we are acquiring lock on +--echo # the table which was not used in the transaction before. +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Start transaction which will prevent X lock from going away +--echo # immediately. +begin; +select count(*) from t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Create pending X lock on t2. +--echo # Sending: +--send rename table t2 to t3; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until RENAME TABLE starts waiting with pending X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t2 to t3"; +--source include/wait_condition.inc +--echo # Check that attempt to acquire SR lock on t2 causes waiting. +--echo # Sending: +--send select count(*) from t2; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above SELECT is blocked. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "select count(*) from t2"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE. +commit; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reap RENAME TABLE. +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap SELECT. +--error ER_NO_SUCH_TABLE +--reap +commit; +rename table t3 to t2; +--echo # The same test for SW lock. +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Start transaction which will prevent X lock from going away +--echo # immediately. +begin; +select count(*) from t2; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Create pending X lock on t2. +--echo # Sending: +--send rename table t2 to t3; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until RENAME TABLE starts waiting with pending X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t2 to t3"; +--source include/wait_condition.inc +--echo # Check that attempt to acquire SW lock on t2 causes waiting. +--echo # Sending: +--send delete from t2 limit 1; +--echo # +--echo # Switching to connection 'mdl_con2'. +connection mdl_con2; +--echo # Check that the above DELETE is blocked. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "delete from t2 limit 1"; +--source include/wait_condition.inc +--echo # Unblock RENAME TABLE. +commit; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reap RENAME TABLE. +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap DELETE. +--error ER_NO_SUCH_TABLE +--reap +commit; +rename table t3 to t2; +--echo # +--echo # Coverage for the case in which we are acquiring lock on +--echo # the table which is already used in transaction and against +--echo # which there is a pending X lock request. +--echo # +--echo # *) The first case is when transaction has only a SR lock. +--echo # +begin; +select count(*) from t1; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until RENAME TABLE is blocked creating pending request for X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Check that another instance of SR lock is granted without waiting. +select count(*) from t1; +--echo # Attempt to wait for SW lock will lead to deadlock, thus +--echo # the below statement should end with ER_LOCK_DEADLOCK error. +--error ER_LOCK_DEADLOCK +delete from t1 limit 1; +--echo # Unblock RENAME TABLE. +commit; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reap RENAME TABLE. +--error ER_TABLE_EXISTS_ERROR +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # +--echo # **) The second case is when transaction has a SW lock. +--echo # +begin; +delete from t1 limit 1; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Sending: +--send rename table t1 to t2; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until RENAME TABLE is blocked creating pending request for X lock. +let $wait_condition= +select count(*) = 1 from information_schema.processlist +where state = "Waiting for table" and info = "rename table t1 to t2"; +--source include/wait_condition.inc +--echo # Check that both SR and SW locks are granted without waiting +--echo # and errors. +select count(*) from t1; +insert into t1 values (1, 1); +--echo # Unblock RENAME TABLE. +commit; +--echo # +--echo # Switching to connection 'mdl_con1'. +connection mdl_con1; +--echo # Reap RENAME TABLE. +--error ER_TABLE_EXISTS_ERROR +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; + +--echo # Clean-up. +disconnect mdl_con1; +disconnect mdl_con2; +disconnect mdl_con3; +set debug_sync= 'RESET'; +drop table t1, t2; + + +--echo # +--echo # Additional coverage for some scenarios in which not quite +--echo # correct use of S metadata locks by HANDLER statement might +--echo # have caused deadlocks. +--echo # +--disable_warnings +drop table if exists t1, t2; +--enable_warnings +connect(handler_con1,localhost,root,,); +connect(handler_con2,localhost,root,,); +connection default; +create table t1 (i int); +create table t2 (j int); +insert into t1 values (1); + +--echo # +--echo # First, check scenario in which we upgrade SNRW lock to X lock +--echo # on a table while having HANDLER READ trying to acquire TL_READ +--echo # on the same table. +--echo # +handler t1 open; +--echo # +--echo # Switching to connection 'handler_con1'. +connection handler_con1; +lock table t1 write; +--echo # Upgrade SNRW to X lock. +--echo # Sending: +--send alter table t1 add column j int; +--echo # +--echo # Switching to connection 'handler_con2'. +connection handler_con2; +--echo # Wait until ALTER is blocked during upgrade. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add column j int"; +--source include/wait_condition.inc +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # The below statement should not cause deadlock. +--send handler t1 read first; +--echo # +--echo # Switching to connection 'handler_con1'. +connection handler_con1; +--echo # Reap ALTER TABLE. +--reap +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap HANDLER READ. +--reap +handler t1 close; + +--echo # +--echo # Now, check scenario in which upgrade of SNRW lock to X lock +--echo # can be blocked by HANDLER which is open in connection currently +--echo # waiting to get table-lock owned by connection doing upgrade. +--echo # +handler t1 open; +--echo # +--echo # Switching to connection 'handler_con1'. +connection handler_con1; +lock table t1 write, t2 read; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Execute statement which will be blocked on table-level lock +--echo # owned by connection 'handler_con1'. +--echo # Sending: +--send insert into t2 values (1); +--echo # +--echo # Switching to connection 'handler_con1'. +connection handler_con1; +--echo # Wait until INSERT is blocked on table-level lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Table lock" and info = "insert into t2 values (1)"; +--source include/wait_condition.inc +--echo # The below statement should not cause deadlock. +alter table t1 drop column j; +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap INSERT. +--reap +handler t1 close; + +--echo # +--echo # Then, check the scenario in which upgrade of SNRW lock to X +--echo # lock is blocked by HANDLER which is open in connection currently +--echo # waiting to get SW lock on the same table. +--echo # +handler t1 open; +--echo # +--echo # Switching to connection 'handler_con1'. +connection handler_con1; +lock table t1 write; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # The below insert should be blocked because active SNRW lock on 't1'. +--echo # Sending: +--send insert into t1 values (1); +--echo # +--echo # Switching to connection 'handler_con1'. +connection handler_con1; +--echo # Wait until INSERT is blocked because of SNRW lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "insert into t1 values (1)"; +--source include/wait_condition.inc +--echo # The below ALTER TABLE will be blocked because of presence of HANDLER. +--echo # Sending: +--send alter table t1 add column j int; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # INSERT should be chosen as victim for resolving deadlock. +--echo # Reaping INSERT. +--error ER_LOCK_DEADLOCK +--reap +--echo # Close HANDLER to unblock ALTER TABLE. +handler t1 close; +--echo # +--echo # Switching to connection 'handler_con1'. +connection handler_con1; +--echo # Reaping ALTER TABLE. +--reap +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; + +--echo # +--echo # Finally, test in which upgrade of SNRW lock to X lock is blocked +--echo # by HANDLER which is open in connection currently waiting to get +--echo # SR lock on the table on which lock is upgraded. +--echo # +handler t1 open; +--echo # +--echo # Switching to connection 'handler_con1'. +connection handler_con1; +lock table t1 write, t2 write; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # The below insert should be blocked because active SNRW lock on 't1'. +--echo # Sending: +--send insert into t2 values (1); +--echo # +--echo # Switching to connection 'handler_con1'. +connection handler_con1; +--echo # Wait until INSERT is blocked because of SNRW lock. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "insert into t2 values (1)"; +--source include/wait_condition.inc +--echo # The below ALTER TABLE will be blocked because of presence of HANDLER. +--echo # Sending: +--send alter table t1 drop column j; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # INSERT should be chosen as victim for resolving deadlock. +--echo # Reaping INSERT. +--error ER_LOCK_DEADLOCK +--reap +--echo # Close HANDLER to unblock ALTER TABLE. +handler t1 close; +--echo # +--echo # Switching to connection 'handler_con1'. +connection handler_con1; +--echo # Reaping ALTER TABLE. +--reap +unlock tables; +--echo # +--echo # Switching to connection 'default'. +connection default; + +--echo # Clean-up. +disconnect handler_con1; +disconnect handler_con2; +drop tables t1, t2; + + +--echo # +--echo # Test coverage for basic deadlock detection in metadata +--echo # locking subsystem. +--echo # +--disable_warnings +drop tables if exists t0, t1, t2, t3, t4, t5; +--enable_warnings + +connect(deadlock_con1,localhost,root,,); +connect(deadlock_con2,localhost,root,,); +connect(deadlock_con3,localhost,root,,); +connection default; +create table t1 (i int); +create table t2 (j int); +create table t3 (k int); +create table t4 (k int); + +--echo # +--echo # Test for the case in which no deadlock occurs. +--echo # + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +begin; +insert into t1 values (1); + +--echo # +--echo # Switching to connection 'deadlock_con2'. +connection deadlock_con2; +begin; +insert into t2 values (1); + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Send: +--send rename table t2 to t0, t3 to t2, t0 to t3; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Wait until the above RENAME TABLE is blocked because it has to wait +--echo # for 'deadlock_con2' which holds shared metadata lock on 't2'. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t2 to t0, t3 to t2, t0 to t3"; +--source include/wait_condition.inc +--echo # The below statement should wait for exclusive metadata lock +--echo # on 't2' to go away and should not produce ER_LOCK_DEADLOCK +--echo # as no deadlock is possible in this situation. +--echo # Send: +--send select * from t2; + +--echo # +--echo # Switching to connection 'deadlock_con2'. +connection deadlock_con2; +--echo # Wait until the above SELECT * FROM t2 is starts waiting +--echo # for an exclusive metadata lock to go away. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "select * from t2"; +--source include/wait_condition.inc +--echo # +--echo # Unblock RENAME TABLE by releasing shared metadata lock on t2. +commit; + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap RENAME TABLE. +--reap + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Reap SELECT. +--reap + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # +--echo # Let us check that in the process of waiting for conflicting lock +--echo # on table 't2' to go away transaction in connection 'deadlock_con1' +--echo # has not released metadata lock on table 't1'. +--echo # Send: +--send rename table t1 to t0, t3 to t1, t0 to t3; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Wait until the above RENAME TABLE is blocked because it has to wait +--echo # for 'deadlock_con1' which should still hold shared metadata lock on +--echo # table 't1'. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t1 to t0, t3 to t1, t0 to t3"; +--source include/wait_condition.inc +--echo # Commit transaction to unblock RENAME TABLE. +commit; + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap RENAME TABLE. +--reap + +--echo # +--echo # Test for case when deadlock occurs and should be detected immediately. +--echo # + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +begin; +insert into t2 values (2); + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Send: +--send rename table t2 to t0, t1 to t2, t0 to t1; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Wait until the above RENAME TABLE is blocked because it has to wait +--echo # for 'deadlock_con1' which holds shared metadata lock on 't2'. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t2 to t0, t1 to t2, t0 to t1"; +--source include/wait_condition.inc +--echo # +--echo # The below statement should not wait as doing so will cause deadlock. +--echo # Instead it should fail and emit ER_LOCK_DEADLOCK statement. +--error ER_LOCK_DEADLOCK +select * from t1; + +--echo # +--echo # Let us check that failure of the above statement has not released +--echo # metadata lock on table 't1', i.e. that RENAME TABLE is still blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t2 to t0, t1 to t2, t0 to t1"; +--source include/wait_condition.inc +--echo # Commit transaction to unblock RENAME TABLE. +commit; + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap RENAME TABLE. +--reap + +--echo # +--echo # Test for the case in which deadlock also occurs but not immediately. +--echo # + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +begin; +insert into t2 values (1); + +--echo # +--echo # Switching to connection 'default'. +connection default; +lock table t1 write; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # The below SELECT statement should wait for metadata lock +--echo # on table 't1' and should not produce ER_LOCK_DEADLOCK +--echo # immediately as no deadlock is possible at the moment. +--send select * from t1; + +--echo # +--echo # Switching to connection 'deadlock_con2'. +connection deadlock_con2; +--echo # Wait until the above SELECT * FROM t1 is starts waiting +--echo # for an UNRW metadata lock to go away. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "select * from t1"; +--source include/wait_condition.inc + +--echo # Send RENAME TABLE statement that will deadlock with the +--echo # SELECT statement and thus should abort the latter. +--send rename table t1 to t0, t2 to t1, t0 to t2; + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Wait till above RENAME TABLE is blocked while holding +--echo # pending X lock on t1. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t1 to t0, t2 to t1, t0 to t2"; +--source include/wait_condition.inc +--echo # Allow the above RENAME TABLE to acquire lock on t1 and +--echo # create pending lock on t2 thus creating deadlock. +unlock tables; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Since the latest RENAME TABLE entered in deadlock with SELECT +--echo # statement the latter should be aborted and emit ER_LOCK_DEADLOCK +--echo # error. +--echo # Reap SELECT * FROM t1. +--error ER_LOCK_DEADLOCK +--reap + +--echo # +--echo # Again let us check that failure of the SELECT statement has not +--echo # released metadata lock on table 't2', i.e. that the latest RENAME +--echo # is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "rename table t1 to t0, t2 to t1, t0 to t2"; +--source include/wait_condition.inc +--echo # Commit transaction to unblock this RENAME TABLE. +commit; + +--echo # +--echo # Switching to connection 'deadlock_con2'. +connection deadlock_con2; +--echo # Reap RENAME TABLE ... . +--reap; + +--echo # +--echo # Switching to connection 'default'. +connection default; + +drop tables t1, t2, t3, t4; + +--echo # +--echo # Now, test case which shows that deadlock detection empiric +--echo # also takes into account requests for metadata lock upgrade. +--echo # +create table t1 (i int); +insert into t1 values (1); +--echo # Avoid race which occurs when SELECT in 'deadlock_con1' connection +--echo # accesses table before the above INSERT unlocks the table and thus +--echo # its result becomes visible to other connections. +select * from t1; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +begin; +select * from t1; + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Send: +--send alter table t1 add column j int, rename to t2; + +--echo # +--echo # Switching to connection 'deadlock_con1'. +connection deadlock_con1; +--echo # Wait until the above ALTER TABLE ... RENAME acquires exclusive +--echo # metadata lock on 't2' and starts waiting for connection +--echo # 'deadlock_con1' which holds shared lock on 't1'. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add column j int, rename to t2"; +--source include/wait_condition.inc + +--echo # The below statement should not wait as it will cause deadlock. +--echo # An appropriate error should be reported instead. +--error ER_LOCK_DEADLOCK +select * from t2; + +--echo # Again let us check that failure of the above statement has not +--echo # released all metadata locks in connection 'deadlock_con1' and +--echo # so ALTER TABLE ... RENAME is still blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add column j int, rename to t2"; +--source include/wait_condition.inc + +--echo # Commit transaction to unblock ALTER TABLE ... RENAME. +commit; + +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap ALTER TABLE ... RENAME. +--reap + +drop table t2; + +disconnect deadlock_con1; +disconnect deadlock_con2; +disconnect deadlock_con3; + + +--echo # +--echo # Test for bug #46748 "Assertion in MDL_context::wait_for_locks() +--echo # on INSERT + CREATE TRIGGER". +--echo # +--disable_warnings +drop tables if exists t1, t2, t3, t4, t5; +--enable_warnings +--echo # Let us simulate scenario in which we open some tables from extended +--echo # part of prelocking set but then encounter conflicting metadata lock, +--echo # so have to back-off and wait for it to go away. +connect (con1root,localhost,root,,test,,); +connect (con2root,localhost,root,,test,,); +connection default; +create table t1 (i int); +create table t2 (j int); +create table t3 (k int); +create table t4 (l int); +create trigger t1_bi before insert on t1 for each row + insert into t2 values (new.i); +create trigger t2_bi before insert on t2 for each row + insert into t3 values (new.j); +--echo # +--echo # Switching to connection 'con1root'. +connection con1root; +lock tables t4 read; +--echo # +--echo # Switching to connection 'con2root'. +connection con2root; +--echo # Send : +--send rename table t3 to t5, t4 to t3; +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Wait until the above RENAME TABLE adds pending requests for exclusive +--echo # metadata lock on its tables and blocks due to 't4' being used by LOCK +--echo # TABLES. +let $wait_condition= select count(*)= 1 from information_schema.processlist + where state= 'Waiting for table' and + info='rename table t3 to t5, t4 to t3'; +--source include/wait_condition.inc +--echo # Send : +--send insert into t1 values (1); +--echo # +--echo # Switching to connection 'con1root'. +connection con1root; +--echo # Wait until INSERT statement waits due to encountering pending +--echo # exclusive metadata lock on 't3'. +let $wait_condition= select count(*)= 1 from information_schema.processlist + where state= 'Waiting for table' and + info='insert into t1 values (1)'; +--source include/wait_condition.inc +unlock tables; +--echo # +--echo # Switching to connection 'con2root'. +connection con2root; +--echo # Reap RENAME TABLE. +--reap +--echo # +--echo # Switching to connection 'default'. +connection default; +--echo # Reap INSERT. +--reap +--echo # Clean-up. +disconnect con1root; +disconnect con2root; +drop tables t1, t2, t3, t5; + + +--echo # +--echo # Bug#42546 - Backup: RESTORE fails, thinking it finds an existing table +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings +set @save_log_output=@@global.log_output; +set global log_output=file; + +connect(con2, localhost, root,,); + +--echo # +--echo # Test 1: CREATE TABLE +--echo # + +--echo # Connection 2 +connection con2; +--echo # Start insert on the not-yet existing table +--echo # Wait after taking the MDL lock +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +--send INSERT INTO t1 VALUES(1,"def") + +--echo # Connection 1 +connection default; +SET DEBUG_SYNC= 'now WAIT_FOR locked'; +--echo # Now INSERT has a MDL on the non-existent table t1. + +--echo # +--echo # Continue the INSERT once CREATE waits for exclusive lock +SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL finish'; +--echo # Try to create that table. +--send CREATE TABLE t1 (c1 INT, c2 VARCHAR(100), KEY(c1)) + +--echo # Connection 2 +--echo # Insert fails +connection con2; +--error ER_NO_SUCH_TABLE +--reap + +--echo # Connection 1 +connection default; +--reap; +SET DEBUG_SYNC= 'RESET'; +SHOW TABLES; + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +--echo # +--echo # Test 2: CREATE TABLE LIKE +--echo # + +CREATE TABLE t2 (c1 INT, c2 VARCHAR(100), KEY(c1)); + +--echo # Connection 2 +connection con2; +--echo # Start insert on the not-yet existing table +--echo # Wait after taking the MDL +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL locked WAIT_FOR finish'; +--send INSERT INTO t1 VALUES(1,"def") + +--echo # Connection 1 +connection default; +SET DEBUG_SYNC= 'now WAIT_FOR locked'; +--echo # Now INSERT has a MDL on the non-existent table t1. + +--echo # +--echo # Continue the INSERT once CREATE waits for exclusive lock +SET DEBUG_SYNC= 'mdl_acquire_lock_wait SIGNAL finish'; +--echo # Try to create that table. +--send CREATE TABLE t1 LIKE t2 + +--echo # Connection 2 +--echo # Insert fails +connection con2; +--error ER_NO_SUCH_TABLE +--reap + +--echo # Connection 1 +connection default; +--reap +SET DEBUG_SYNC= 'RESET'; +SHOW TABLES; + +DROP TABLE t2; +disconnect con2; +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +set global log_output=@save_log_output; + + +--echo # +--echo # Bug #46044 "MDL deadlock on LOCK TABLE + CREATE TABLE HIGH_PRIORITY +--echo # FOR UPDATE" +--echo # +--disable_warnings +drop tables if exists t1, t2; +--enable_warnings +connect (con46044, localhost, root,,); +connect (con46044_2, localhost, root,,); +connection default; +create table t1 (i int); + +--echo # Let us check that we won't deadlock if during filling +--echo # of I_S table we encounter conflicting metadata lock +--echo # which owner is in its turn waiting for our connection. +lock tables t1 read; + +--echo # Switching to connection 'con46044'. +connection con46044; +--echo # Sending: +--send create table t2 select * from t1 for update; + +--echo # Switching to connection 'default'. +connection default; +--echo # Waiting until CREATE TABLE ... SELECT ... is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Table lock" and info = "create table t2 select * from t1 for update"; +--source include/wait_condition.inc + +--echo # First let us check that SHOW FIELDS/DESCRIBE doesn't +--echo # gets blocked and emits and error. +--error ER_WARN_I_S_SKIPPED_TABLE +show fields from t2; + +--echo # Now test for I_S query which reads only .FRMs. +--echo # +--echo # Query below should only emit a warning. +select column_name from information_schema.columns + where table_schema='test' and table_name='t2'; + +--echo # Finally, test for I_S query which does full-blown table open. +--echo # +--echo # Query below should not be blocked. Warning message should be +--echo # stored in the 'table_comment' column. +select table_name, table_type, auto_increment, table_comment + from information_schema.tables where table_schema='test' and table_name='t2'; + +--echo # Switching to connection 'default'. +connection default; +unlock tables; + +--echo # Switching to connection 'con46044'. +connection con46044; +--echo # Reaping CREATE TABLE ... SELECT ... . +--reap +drop table t2; + +--echo # +--echo # Let us also check that queries to I_S wait for conflicting metadata +--echo # locks to go away instead of skipping table with a warning in cases +--echo # when deadlock is not possible. This is a nice thing from compatibility +--echo # and ease of use points of view. +--echo # +--echo # We check same three queries to I_S in this new situation. + +--echo # Switching to connection 'con46044_2'. +connection con46044_2; +lock tables t1 read; + +--echo # Switching to connection 'con46044'. +connection con46044; +--echo # Sending: +--send create table t2 select * from t1 for update; + +--echo # Switching to connection 'default'. +connection default; +--echo # Waiting until CREATE TABLE ... SELECT ... is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Table lock" and info = "create table t2 select * from t1 for update"; +--source include/wait_condition.inc + +--echo # Let us check that SHOW FIELDS/DESCRIBE gets blocked. +--echo # Sending: +--send show fields from t2; + +--echo # Switching to connection 'con46044_2'. +connection con46044_2; +--echo # Wait until SHOW FIELDS gets blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "show fields from t2"; +--source include/wait_condition.inc + +unlock tables; + +--echo # Switching to connection 'con46044'. +connection con46044; +--echo # Reaping CREATE TABLE ... SELECT ... . +--reap + +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping SHOW FIELDS ... +--reap +drop table t2; + +--echo # Switching to connection 'con46044_2'. +connection con46044_2; +lock tables t1 read; + +--echo # Switching to connection 'con46044'. +connection con46044; +--echo # Sending: +--send create table t2 select * from t1 for update; + +--echo # Switching to connection 'default'. +connection default; +--echo # Waiting until CREATE TABLE ... SELECT ... is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Table lock" and info = "create table t2 select * from t1 for update"; +--source include/wait_condition.inc + +--echo # Check that I_S query which reads only .FRMs gets blocked. +--echo # Sending: +--send select column_name from information_schema.columns where table_schema='test' and table_name='t2'; + +--echo # Switching to connection 'con46044_2'. +connection con46044_2; +--echo # Wait until SELECT COLUMN_NAME FROM I_S.COLUMNS gets blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info like "select column_name from information_schema.columns%"; +--source include/wait_condition.inc + +unlock tables; + +--echo # Switching to connection 'con46044'. +connection con46044; +--echo # Reaping CREATE TABLE ... SELECT ... . +--reap + +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping SELECT COLUMN_NAME FROM I_S.COLUMNS +--reap +drop table t2; + +--echo # Switching to connection 'con46044_2'. +connection con46044_2; +lock tables t1 read; + +--echo # Switching to connection 'con46044'. +connection con46044; +--echo # Sending: +--send create table t2 select * from t1 for update; + +--echo # Switching to connection 'default'. +connection default; +--echo # Waiting until CREATE TABLE ... SELECT ... is blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Table lock" and info = "create table t2 select * from t1 for update"; +--source include/wait_condition.inc + +--echo # Finally, check that I_S query which does full-blown table open +--echo # also gets blocked. +--echo # Sending: +--send select table_name, table_type, auto_increment, table_comment from information_schema.tables where table_schema='test' and table_name='t2'; + +--echo # Switching to connection 'con46044_2'. +connection con46044_2; +--echo # Wait until SELECT ... FROM I_S.TABLES gets blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and + info like "select table_name, table_type, auto_increment, table_comment from information_schema.tables%"; +--source include/wait_condition.inc + +unlock tables; + +--echo # Switching to connection 'con46044'. +connection con46044; +--echo # Reaping CREATE TABLE ... SELECT ... . +--reap + +--echo # Switching to connection 'default'. +connection default; +--echo # Reaping SELECT ... FROM I_S.TABLES +--reap +drop table t2; + +--echo # Switching to connection 'default'. +connection default; +--echo # Clean-up. +disconnect con46044; +disconnect con46044_2; +drop table t1; + + +--echo # +--echo # Test for bug #46273 "MySQL 5.4.4 new MDL: Bug#989 is not fully fixed +--echo # in case of ALTER". +--echo # +--disable_warnings +drop table if exists t1; +--enable_warnings +set debug_sync= 'RESET'; +connect (con46273,localhost,root,,test,,); +connection default; +create table t1 (c1 int primary key, c2 int, c3 int); +insert into t1 values (1,1,0),(2,2,0),(3,3,0),(4,4,0),(5,5,0); + +begin; +select * from t1 where c2 = 3; + +--echo # +--echo # Switching to connection 'con46273'. +connection con46273; +set debug_sync='after_lock_tables_takes_lock SIGNAL alter_table_locked WAIT_FOR alter_go'; +--send alter table t1 add column e int, rename to t2; + +--echo # +--echo # Switching to connection 'default'. +connection default; +set debug_sync='now WAIT_FOR alter_table_locked'; +set debug_sync='before_open_table_wait_refresh SIGNAL alter_go'; +--echo # The below statement should get ER_LOCK_DEADLOCK error +--echo # (i.e. it should not allow ALTER to proceed, and then +--echo # fail due to 't1' changing its name to 't2'). +--error ER_LOCK_DEADLOCK +update t1 set c3=c3+1 where c2 = 3; + +--echo # +--echo # Let us check that failure of the above statement has not released +--echo # metadata lock on table 't1', i.e. that ALTER TABLE is still blocked. +let $wait_condition= + select count(*) = 1 from information_schema.processlist + where state = "Waiting for table" and info = "alter table t1 add column e int, rename to t2"; +--source include/wait_condition.inc + +--echo # Unblock ALTER TABLE by commiting transaction and thus releasing +--echo # metadata lock on 't1'. +commit; + +--echo # +--echo # Switching to connection 'con46273'. +connection con46273; +--echo # Reap ALTER TABLE. +--reap + +--echo # +--echo # Switching to connection 'default'. +connection default; +disconnect con46273; +--echo # Clean-up. +set debug_sync= 'RESET'; +drop table t2; + + +--echo # +--echo # Test for bug #46673 "Deadlock between FLUSH TABLES WITH READ LOCK +--echo # and DML". +--echo # +--disable_warnings +drop tables if exists t1; +--enable_warnings +connect (con46673, localhost, root,,); +connection default; +create table t1 (i int); + +--echo # Switching to connection 'con46673'. +connection con46673; +begin; +insert into t1 values (1); + +--echo # Switching to connection 'default'. +connection default; +--echo # Statement below should not get blocked. And if after some +--echo # changes to code it is there should not be a deadlock between +--echo # it and transaction from connection 'con46673'. +flush tables with read lock; +unlock tables; + +--echo # Switching to connection 'con46673'. +connection con46673; +delete from t1 where i = 1; +commit; + +--echo # Switching to connection 'default'. +connection default; +--echo # Clean-up +disconnect con46673; +drop table t1; + + +--echo # +--echo # Bug#48210 FLUSH TABLES WITH READ LOCK deadlocks +--echo # against concurrent CREATE PROCEDURE +--echo # + +connect (con2, localhost, root); + +--echo # Test 1: CREATE PROCEDURE + +--echo # Connection 1 +connection default; +--echo # Start CREATE PROCEDURE and open mysql.proc +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL table_opened WAIT_FOR grlwait'; +--send CREATE PROCEDURE p1() SELECT 1 + +--echo # Connection 2 +connection con2; +SET DEBUG_SYNC= 'now WAIT_FOR table_opened'; +--echo # Check that FLUSH must wait to get the GRL +--echo # and let CREATE PROCEDURE continue +SET DEBUG_SYNC= 'wait_lock_global_read_lock SIGNAL grlwait'; +--send FLUSH TABLES WITH READ LOCK + +--echo # Connection 1 +connection default; +--reap + +--echo # Connection 2 +connection con2; +--reap +UNLOCK TABLES; + +--echo # Connection 1 +connection default; +SET DEBUG_SYNC= 'RESET'; + +--echo # Test 2: DROP PROCEDURE + +connection default; +--echo # Start DROP PROCEDURE and open tables +SET DEBUG_SYNC= 'after_open_table_mdl_shared SIGNAL table_opened WAIT_FOR grlwait'; +--send DROP PROCEDURE p1 + +--echo # Connection 2 +connection con2; +SET DEBUG_SYNC= 'now WAIT_FOR table_opened'; +--echo # Check that FLUSH must wait to get the GRL +--echo # and let DROP PROCEDURE continue +SET DEBUG_SYNC= 'wait_lock_global_read_lock SIGNAL grlwait'; +--send FLUSH TABLES WITH READ LOCK + +--echo # Connection 1 +connection default; +--reap + +--echo # Connection 2 +connection con2; +--reap +UNLOCK TABLES; + +--echo # Connection 1 +connection default; +SET DEBUG_SYNC= 'RESET'; + +disconnect con2; + + +# Check that all connections opened by test cases in this file are really +# gone so execution of other tests won't be affected by their presence. +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/t/merge-big.test b/mysql-test/t/merge-big.test index b687973c9d1..33bd93791f1 100644 --- a/mysql-test/t/merge-big.test +++ b/mysql-test/t/merge-big.test @@ -51,7 +51,7 @@ connection default; #--sleep 8 #SELECT ID,STATE,INFO FROM INFORMATION_SCHEMA.PROCESSLIST; let $wait_condition= SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST - WHERE ID = $con1_id AND STATE = 'Locked'; + WHERE ID = $con1_id AND STATE = 'Table lock'; --source include/wait_condition.inc #SELECT NOW(); --echo # Kick INSERT out of thr_multi_lock(). @@ -61,7 +61,7 @@ FLUSH TABLES; #--sleep 8 #SELECT ID,STATE,INFO FROM INFORMATION_SCHEMA.PROCESSLIST; let $wait_condition= SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST - WHERE ID = $con1_id AND STATE = 'Waiting for table'; + WHERE ID = $con1_id AND STATE = 'Table lock'; --source include/wait_condition.inc #SELECT NOW(); --echo # Unlock and close table and wait for con1 to close too. diff --git a/mysql-test/t/merge.test b/mysql-test/t/merge.test index 63ad5a1e97c..c3499037e97 100644 --- a/mysql-test/t/merge.test +++ b/mysql-test/t/merge.test @@ -216,20 +216,20 @@ drop table t3,t1,t2; # # temporary merge tables # -create table t1 (a int not null); -create table t2 (a int not null); -insert into t1 values (1); -insert into t2 values (2); -create temporary table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2); +CREATE TABLE t1 (c1 INT NOT NULL); +CREATE TABLE t2 (c1 INT NOT NULL); +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (2); +CREATE TEMPORARY TABLE t3 (c1 INT NOT NULL) ENGINE=MRG_MYISAM UNION=(t1,t2); --error ER_WRONG_MRG_TABLE -select * from t3; -create temporary table t4 (a int not null); -create temporary table t5 (a int not null); -insert into t4 values (1); -insert into t5 values (2); -create temporary table t6 (a int not null) ENGINE=MERGE UNION=(t4,t5); -select * from t6; -drop table t6, t3, t1, t2, t4, t5; +SELECT * FROM t3; +CREATE TEMPORARY TABLE t4 (c1 INT NOT NULL); +CREATE TEMPORARY TABLE t5 (c1 INT NOT NULL); +INSERT INTO t4 VALUES (4); +INSERT INTO t5 VALUES (5); +CREATE TEMPORARY TABLE t6 (c1 INT NOT NULL) ENGINE=MRG_MYISAM UNION=(t4,t5); +SELECT * FROM t6; +DROP TABLE t6, t3, t1, t2, t4, t5; # # Bug#19627 - temporary merge table locking # MERGE table and its children must match in temporary type. @@ -556,7 +556,7 @@ CREATE TABLE t1(a INT); SELECT * FROM tm1; CHECK TABLE tm1; CREATE TABLE t2(a BLOB); ---error 1168 +--error ER_WRONG_MRG_TABLE SELECT * FROM tm1; CHECK TABLE tm1; ALTER TABLE t2 MODIFY a INT; @@ -675,12 +675,16 @@ SELECT * FROM t3; --echo # Truncate MERGE table under locked tables. LOCK TABLE t1 WRITE, t2 WRITE, t3 WRITE; INSERT INTO t1 VALUES (1); ---error ER_LOCK_OR_ACTIVE_TRANSACTION TRUNCATE TABLE t3; SELECT * FROM t3; +UNLOCK TABLES; +SELECT * FROM t1; +SELECT * FROM t2; --echo # --echo # Truncate child table under locked tables. ---error ER_LOCK_OR_ACTIVE_TRANSACTION +LOCK TABLE t1 WRITE, t2 WRITE, t3 WRITE; +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (2); TRUNCATE TABLE t1; SELECT * FROM t3; UNLOCK TABLES; @@ -706,14 +710,18 @@ SELECT * FROM t3; INSERT INTO t1 VALUES (1); CREATE TABLE t4 (c1 INT, INDEX(c1)); LOCK TABLE t4 WRITE; ---error ER_LOCK_OR_ACTIVE_TRANSACTION TRUNCATE TABLE t3; SELECT * FROM t3; +SELECT * FROM t1; +SELECT * FROM t2; --echo # --echo # Truncate temporary child table under locked tables. ---error ER_LOCK_OR_ACTIVE_TRANSACTION +INSERT INTO t1 VALUES (1); +INSERT INTO t2 VALUES (2); TRUNCATE TABLE t1; SELECT * FROM t3; +SELECT * FROM t1; +SELECT * FROM t2; UNLOCK TABLES; DROP TABLE t1, t2, t3, t4; @@ -846,7 +854,8 @@ SHOW CREATE TABLE t3; DROP TABLE t1, t2; # --echo # ---echo # CREATE ... LIKE +--echo # Bug#37371 "CREATE TABLE LIKE merge loses UNION parameter" +--echo # Demonstrate that this is no longer the case. --echo # --echo # 1. Create like. CREATE TABLE t1 (c1 INT); @@ -858,20 +867,24 @@ INSERT INTO t2 VALUES (2); INSERT INTO t3 VALUES (3); CREATE TABLE t4 LIKE t3; SHOW CREATE TABLE t4; ---error ER_OPEN_AS_READONLY INSERT INTO t4 VALUES (4); DROP TABLE t4; --echo # --echo # 1. Create like with locked tables. LOCK TABLES t3 WRITE, t2 WRITE, t1 WRITE; +--error ER_TABLE_NOT_LOCKED CREATE TABLE t4 LIKE t3; --error ER_TABLE_NOT_LOCKED SHOW CREATE TABLE t4; --error ER_TABLE_NOT_LOCKED INSERT INTO t4 VALUES (4); -UNLOCK TABLES; +CREATE TEMPORARY TABLE t4 LIKE t3; +--error ER_WRONG_MRG_TABLE SHOW CREATE TABLE t4; ---error ER_OPEN_AS_READONLY +--error ER_WRONG_MRG_TABLE +INSERT INTO t4 VALUES (4); +UNLOCK TABLES; +--error ER_WRONG_MRG_TABLE INSERT INTO t4 VALUES (4); DROP TABLE t4; # @@ -969,9 +982,9 @@ CREATE TABLE t2 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t1) LOCK TABLES t1 WRITE, t2 WRITE; INSERT INTO t1 VALUES (1); DROP TABLE t1; ---error 1168 +--error ER_TABLE_NOT_LOCKED SELECT * FROM t2; ---error ER_NO_SUCH_TABLE +--error ER_TABLE_NOT_LOCKED SELECT * FROM t1; UNLOCK TABLES; DROP TABLE t2; @@ -1407,6 +1420,7 @@ FLUSH TABLES m1, t1; UNLOCK TABLES; DROP TABLE t1, m1; + # # Bug#35068 - Assertion fails when reading from i_s.tables # and there is incorrect merge table @@ -1694,3 +1708,333 @@ while ($1) --enable_query_log drop table t_parent; set @@global.table_definition_cache=@save_table_definition_cache; + +# +# WL#4144 - Lock MERGE engine children +# +# Test DATA/INDEX DIRECTORY +# +--disable_warnings +DROP DATABASE IF EXISTS mysql_test1; +--enable_warnings +CREATE DATABASE mysql_test1; +--disable_query_log +# data/index directory don't work in HAVE_purify builds. Disable +# build-dependent warnings. +--disable_warnings +--echo CREATE TABLE t1 ... DATA DIRECTORY=... INDEX DIRECTORY=... +eval CREATE TABLE t1 (c1 INT) + DATA DIRECTORY='$MYSQLTEST_VARDIR/tmp' + INDEX DIRECTORY='$MYSQLTEST_VARDIR/tmp'; +--echo CREATE TABLE mysql_test1.t2 ... DATA DIRECTORY=... INDEX DIRECTORY=... +eval CREATE TABLE mysql_test1.t2 (c1 INT) + DATA DIRECTORY='$MYSQLTEST_VARDIR/tmp' + INDEX DIRECTORY='$MYSQLTEST_VARDIR/tmp'; +--enable_query_log +--enable_warnings +CREATE TABLE m1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,mysql_test1.t2) + INSERT_METHOD=LAST; +INSERT INTO t1 VALUES (1); +INSERT INTO mysql_test1.t2 VALUES (2); +SELECT * FROM m1; +#--copy_file $MYSQLTEST_VARDIR/master-data/test/m1.MRG /tmp/mysql-test-m1.MRG +DROP TABLE t1, mysql_test1.t2, m1; +DROP DATABASE mysql_test1; +# +# Review detected Crash #1. Detaching main tables while in sub statement. +# +CREATE TABLE t1 (c1 INT); +CREATE TABLE t2 (c1 INT); +INSERT INTO t1 (c1) VALUES (1); +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2) INSERT_METHOD=FIRST; +CREATE TABLE t3 (c1 INT); +INSERT INTO t3 (c1) VALUES (1); +CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MAX(c1) FROM t3); +CREATE VIEW v1 AS SELECT foo.c1 c1, f1() c2, bar.c1 c3, f1() c4 + FROM tm1 foo, tm1 bar, t3; +SELECT * FROM v1; +DROP FUNCTION f1; +DROP VIEW v1; +DROP TABLE tm1, t1, t2, t3; +# +# Review detected Crash #2. Trying to attach temporary table twice. +# +CREATE TEMPORARY TABLE t1 (c1 INT); +CREATE TEMPORARY TABLE t2 (c1 INT); +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2) + INSERT_METHOD=FIRST; +CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MAX(c1) FROM tm1); +INSERT INTO tm1 (c1) VALUES (1); +SELECT f1() FROM (SELECT 1) AS c1; +DROP FUNCTION f1; +DROP TABLE tm1, t1, t2; +# +# Review suggested test. DDL in a stored function. +# +DELIMITER |; +CREATE FUNCTION f1() RETURNS INT +BEGIN + CREATE TEMPORARY TABLE t1 (c1 INT); + CREATE TEMPORARY TABLE t2 (c1 INT); + CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2); + INSERT INTO t1 (c1) VALUES (1); + RETURN (SELECT MAX(c1) FROM tm1); +END| +DELIMITER ;| +SELECT f1() FROM (SELECT 1 UNION SELECT 1) c1; +DROP FUNCTION f1; +DROP TABLE tm1, t1, t2; +# +CREATE TEMPORARY TABLE t1 (c1 INT); +INSERT INTO t1 (c1) VALUES (1); +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1); +DELIMITER |; +--error ER_SP_BADSTATEMENT +CREATE FUNCTION f1() RETURNS INT +BEGIN + CREATE TEMPORARY TABLE t2 (c1 INT); + ALTER TEMPORARY TABLE tm1 UNION=(t1,t2); + INSERT INTO t2 (c1) VALUES (2); + RETURN (SELECT MAX(c1) FROM tm1); +END| +DELIMITER ;| +DROP TABLE tm1, t1; +# +# Base table. No LOCK TABLES, no functions/triggers. +# +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +DROP TABLE tm1, t1; +# +# Base table. No LOCK TABLES, sub-statement that is run inside a function. +# +DELIMITER |; +CREATE FUNCTION f1() RETURNS INT +BEGIN + INSERT INTO tm1 VALUES (1); + RETURN (SELECT MAX(c1) FROM tm1); +END| +DELIMITER ;| +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +SELECT f1(); +DROP FUNCTION f1; +DROP TABLE tm1, t1; +# +# Base table. LOCK TABLES, no functions/triggers. +# +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +LOCK TABLE tm1 WRITE; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +UNLOCK TABLES; +DROP TABLE tm1, t1; +# +# Base table. LOCK TABLES, sub-statement that is run inside a function. +# +DELIMITER |; +CREATE FUNCTION f1() RETURNS INT +BEGIN + INSERT INTO tm1 VALUES (1); + RETURN (SELECT MAX(c1) FROM tm1); +END| +DELIMITER ;| +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +LOCK TABLE tm1 WRITE; +SELECT f1(); +UNLOCK TABLES; +DROP FUNCTION f1; +DROP TABLE tm1, t1; +# +# Base table. LOCK TABLES statement that locks a table that has a trigger +# that inserts into a merge table, so an attempt is made to lock tables +# of a sub-statement. +# +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t2 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST; +CREATE TRIGGER t2_ai AFTER INSERT ON t2 + FOR EACH ROW INSERT INTO tm1 VALUES(11); +LOCK TABLE t2 WRITE; +INSERT INTO t2 VALUES (2); +SELECT * FROM tm1; +SELECT * FROM t2; +UNLOCK TABLES; +DROP TRIGGER t2_ai; +DROP TABLE tm1, t1, t2; +# +# Temporary. No LOCK TABLES, no functions/triggers. +# +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) + INSERT_METHOD=LAST; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +DROP TABLE tm1, t1; +# +# Temporary. No LOCK TABLES, sub-statement that is run inside a function. +# +DELIMITER |; +CREATE FUNCTION f1() RETURNS INT +BEGIN + INSERT INTO tm1 VALUES (1); + RETURN (SELECT MAX(c1) FROM tm1); +END| +DELIMITER ;| +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) + INSERT_METHOD=LAST; +SELECT f1(); +DROP FUNCTION f1; +DROP TABLE tm1, t1; +# +# Temporary. LOCK TABLES, no functions/triggers. +# +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) + INSERT_METHOD=LAST; +CREATE TABLE t9 (c1 INT) ENGINE=MyISAM; +LOCK TABLE t9 WRITE; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +UNLOCK TABLES; +DROP TABLE tm1, t1, t9; +# +# Temporary. LOCK TABLES, sub-statement that is run inside a function. +# +DELIMITER |; +CREATE FUNCTION f1() RETURNS INT +BEGIN + INSERT INTO tm1 VALUES (1); + RETURN (SELECT MAX(c1) FROM tm1); +END| +DELIMITER ;| +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) + INSERT_METHOD=LAST; +CREATE TABLE t9 (c1 INT) ENGINE=MyISAM; +LOCK TABLE t9 WRITE; +SELECT f1(); +UNLOCK TABLES; +DROP FUNCTION f1; +DROP TABLE tm1, t1, t9; +# +# Temporary. LOCK TABLES statement that locks a table that has a trigger +# that inserts into a merge table, so an attempt is made to lock tables +# of a sub-statement. +# +CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) + INSERT_METHOD=LAST; +CREATE TABLE t2 (c1 INT) ENGINE=MyISAM; +CREATE TRIGGER t2_ai AFTER INSERT ON t2 + FOR EACH ROW INSERT INTO tm1 VALUES(11); +LOCK TABLE t2 WRITE; +INSERT INTO t2 VALUES (2); +SELECT * FROM tm1; +SELECT * FROM t2; +UNLOCK TABLES; +DROP TRIGGER t2_ai; +DROP TABLE tm1, t1, t2; +--echo # +--echo # Don't select MERGE child when trying to get prelocked table. +--echo # +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) + INSERT_METHOD=LAST; +CREATE TRIGGER tm1_ai AFTER INSERT ON tm1 + FOR EACH ROW INSERT INTO t1 VALUES(11); +LOCK TABLE tm1 WRITE, t1 WRITE; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +UNLOCK TABLES; +LOCK TABLE t1 WRITE, tm1 WRITE; +INSERT INTO tm1 VALUES (1); +SELECT * FROM tm1; +UNLOCK TABLES; +DROP TRIGGER tm1_ai; +DROP TABLE tm1, t1; + +# Don't resurrect chopped off prelocked tables. +# The problem is not visible by test results; only by debugging. +# +CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t2 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t3 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t4 (c1 INT) ENGINE=MyISAM; +CREATE TABLE t5 (c1 INT) ENGINE=MyISAM; +CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2,t3,t4,t5) + INSERT_METHOD=LAST; +CREATE TRIGGER t2_au AFTER UPDATE ON t2 + FOR EACH ROW INSERT INTO t3 VALUES(33); +CREATE FUNCTION f1() RETURNS INT + RETURN (SELECT MAX(c1) FROM t4); +LOCK TABLE tm1 WRITE, t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE, t5 WRITE; +INSERT INTO t1 VALUES(1); +INSERT INTO t2 VALUES(2); +INSERT INTO t3 VALUES(3); +INSERT INTO t4 VALUES(4); +INSERT INTO t5 VALUES(5); + connect (con1,localhost,root,,); + send UPDATE t2, tm1 SET t2.c1=f1(); +connection default; +# Force reopen in other thread. +#sleep 1; +FLUSH TABLES; +#sleep 1; +FLUSH TABLES; +#sleep 1; +UNLOCK TABLES; + connection con1; + reap; + disconnect con1; +connection default; +SELECT * FROM tm1; +DROP TRIGGER t2_au; +DROP FUNCTION f1; +DROP TABLE tm1, t1, t2, t3, t4, t5; + +--echo # +--echo # Bug47098 assert in MDL_context::destroy on HANDLER +--echo # <damaged merge table> OPEN +--echo # +--echo # Test that merge tables are closed correctly when opened using +--echo # HANDLER ... OPEN. +--echo # The general case. +--disable_warnings +DROP TABLE IF EXISTS t1, t2, t3; +--enable_warnings +--echo # Connection con1. +connect (con1,localhost,root,,); +CREATE TABLE t1 (c1 int); +CREATE TABLE t2 (c1 int); +CREATE TABLE t3 (c1 int) ENGINE = MERGE UNION (t1,t2); +START TRANSACTION; +--error ER_ILLEGAL_HA +HANDLER t3 OPEN; +DROP TABLE t1, t2, t3; +--echo # Connection default. +connection default; +--echo # Disconnecting con1, all mdl_tickets must have been released. +disconnect con1; +--echo # The bug-specific case. +--echo # Connection con1. +connect (con1,localhost,root,,); +CREATE TABLE t1 (c1 int); +CREATE TABLE t2 (c1 int); +CREATE TABLE t3 (c1 int) ENGINE = MERGE UNION (t1,t2); +DROP TABLE t2; +START TRANSACTION; +--error ER_WRONG_MRG_TABLE +HANDLER t3 OPEN; +DROP TABLE t1, t3; +--echo # Connection default. +connection default; +--echo # Disconnecting con1, all mdl_tickets must have been released. +disconnect con1; + +--echo End of 6.0 tests diff --git a/mysql-test/t/merge_recover-master.opt b/mysql-test/t/merge_recover-master.opt new file mode 100644 index 00000000000..875a25ad513 --- /dev/null +++ b/mysql-test/t/merge_recover-master.opt @@ -0,0 +1 @@ +--myisam-recover=force diff --git a/mysql-test/t/merge_recover.test b/mysql-test/t/merge_recover.test new file mode 100644 index 00000000000..f2cb204eeb6 --- /dev/null +++ b/mysql-test/t/merge_recover.test @@ -0,0 +1,113 @@ +--echo # +--echo # Test of MyISAM MRG tables with corrupted children. +--echo # Run with --myisam-recover=force option. +--echo # +--echo # Preparation: we need to make sure that the merge parent +--echo # is never left in the table cache when closed, since this may +--echo # have effect on merge children. +--echo # For that, we set the table cache to minimal size and populate it +--echo # in a concurrent connection. +connect(con1,localhost,root,,test,,); +--echo # +--echo # Switching to connection con1 +--echo # +connection con1; +--echo # +--echo # Minimal values. +--echo # + +call mtr.add_suppression("Got an error from thread_id=.*ha_myisam.cc:"); +call mtr.add_suppression("MySQL thread id .*, query id .* localhost.*root Checking table"); +call mtr.add_suppression(" '\..test.t1'"); + +set global table_open_cache=256; +set global table_definition_cache=400; +--disable_warnings +drop procedure if exists p_create; +--enable_warnings +delimiter |; +create procedure p_create() +begin + declare i int default 1; + set @lock_table_stmt="lock table "; + set @drop_table_stmt="drop table "; + while i < @@global.table_definition_cache + 1 do + set @table_name=concat("t_", i); + set @opt_comma=if(i=1, "", ", "); + set @lock_table_stmt=concat(@lock_table_stmt, @opt_comma, + @table_name, " read"); + set @drop_table_stmt=concat(@drop_table_stmt, @opt_comma, @table_name); + set @create_table_stmt=concat("create table if not exists ", + @table_name, " (a int)"); + prepare stmt from @create_table_stmt; + execute stmt; + deallocate prepare stmt; + set i= i+1; + end while; +end| +delimiter ;| +call p_create(); +drop procedure p_create; +--disable_query_log +let $lock=`select @lock_table_stmt`; +eval $lock; +--enable_query_log +--echo # +--echo # Switching to connection 'default' +--echo # +connection default; +--echo # +--echo # We have to disable the ps-protocol, to avoid +--echo # "Prepared statement needs to be re-prepared" errors +--echo # -- table def versions change all the time with full table cache. +--echo # +--disable_ps_protocol +--disable_warnings +drop table if exists t1, t1_mrg, t1_copy; +--enable_warnings +let $MYSQLD_DATADIR=`select @@datadir`; +--echo # +--echo # Prepare a MERGE engine table, that refers to a corrupted +--echo # child. +--echo # +create table t1 (a int, key(a)) engine=myisam; +create table t1_mrg (a int) union (t1) engine=merge; +--echo # +--echo # Create a table with a corrupted index file: +--echo # save an old index file, insert more rows, +--echo # overwrite the new index file with the old one. +--echo # +insert into t1 (a) values (1), (2), (3); +flush table t1; +--copy_file $MYSQLD_DATADIR/test/t1.MYI $MYSQLD_DATADIR/test/t1_copy.MYI +insert into t1 (a) values (4), (5), (6); +flush table t1; +--remove_file $MYSQLD_DATADIR/test/t1.MYI +--copy_file $MYSQLD_DATADIR/test/t1_copy.MYI $MYSQLD_DATADIR/test/t1.MYI +--remove_file $MYSQLD_DATADIR/test/t1_copy.MYI +--echo # check table is needed to mark the table as crashed. +check table t1; +--echo # +--echo # At this point we have a merge table t1_mrg pointing to t1, +--echo # and t1 is corrupted, and will be auto-repaired at open. +--echo # Check that this doesn't lead to memory corruption. +--echo # +--replace_regex /'.*[\/\\]/'/ +select * from t1_mrg; +--echo # +--echo # Cleanup +--echo # +drop table t1, t1_mrg; +--echo # +--echo # Switching to connection con1 +--echo # +connection con1; +unlock tables; +prepare stmt from @drop_table_stmt; +execute stmt; +deallocate prepare stmt; +set @@global.table_definition_cache=default; +set @@global.table_open_cache=default; +disconnect con1; +connection default; +--enable_ps_protocol diff --git a/mysql-test/t/multi_update.test b/mysql-test/t/multi_update.test index fc37fd6a27d..68b44a33428 100644 --- a/mysql-test/t/multi_update.test +++ b/mysql-test/t/multi_update.test @@ -474,7 +474,8 @@ drop table t1,t2; # # Test alter table and a concurrent multi update -# (This will force update to reopen tables) +# (Before we have introduced data-lock-aware metadata locks +# this test case forced update to reopen tables). # create table t1 (a int, b int); @@ -494,9 +495,9 @@ send alter table t1 add column c int default 100 after a; connect (updater,localhost,root,,test); connection updater; # Wait till "alter table t1 ..." of session changer is in work. -# = There is one session is in state "Locked". +# = There is one session waiting. let $wait_condition= select count(*)= 1 from information_schema.processlist - where state= 'Locked'; + where state= 'Waiting for table'; --source include/wait_condition.inc send update t1, v1 set t1.b=t1.a+t1.b+v1.b where t1.a=v1.a; @@ -505,9 +506,9 @@ connection locker; # - "alter table t1 ..." of session changer and # - "update t1, v1 ..." of session updater # are in work. -# = There are two session is in state "Locked". +# = There are two session waiting. let $wait_condition= select count(*)= 2 from information_schema.processlist - where state= 'Locked'; + where state= 'Waiting for table'; --source include/wait_condition.inc unlock tables; diff --git a/mysql-test/t/not_embedded_server.test b/mysql-test/t/not_embedded_server.test index fa2b659ec57..917d5871682 100644 --- a/mysql-test/t/not_embedded_server.test +++ b/mysql-test/t/not_embedded_server.test @@ -4,12 +4,6 @@ -- source include/not_embedded.inc -# -# Produce output -# - -select 1; - # The following fails sporadically because 'check-testcase' runs # queries before this test and there is no way to guarantee that any # previous process finishes. The purpose of the test is not clearly @@ -36,6 +30,8 @@ select 1; #execute stmt1; #deallocate prepare stmt1; +call mtr.add_suppression("Can't open and lock privilege tables: Table 'host' was not locked with LOCK TABLES"); + # # Bug#43835: SHOW VARIABLES does not include 0 for slave_skip_errors # @@ -43,3 +39,18 @@ select 1; SHOW VARIABLES like 'slave_skip_errors'; # End of 5.1 tests + +--echo # +--echo # WL#4284: Transactional DDL locking +--echo # +--echo # FLUSH PRIVILEGES should not implicitly unlock locked tables. +--echo # +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (c1 int); +lock tables t1 read; +--error ER_TABLE_NOT_LOCKED +flush privileges; +unlock tables; +drop table t1; diff --git a/mysql-test/t/partition.test b/mysql-test/t/partition.test index 1904b4cb805..18b7e0d343b 100644 --- a/mysql-test/t/partition.test +++ b/mysql-test/t/partition.test @@ -95,6 +95,19 @@ show indexes from t1; drop table t1; # +# Bug#40181: hang if create index +# +create table t1 (a int) +partition by hash (a); +create index i on t1 (a); +insert into t1 values (1); +insert into t1 select * from t1; +--error ER_DUP_KEYNAME +create index i on t1 (a); +create index i2 on t1 (a); +drop table t1; + +# # Bug#36001: Partitions: spelling and using some error messages # --error ER_FOREIGN_KEY_ON_PARTITIONED diff --git a/mysql-test/t/partition_innodb_semi_consistent.test b/mysql-test/t/partition_innodb_semi_consistent.test index 6a6a7cf958e..2bf879603a4 100644 --- a/mysql-test/t/partition_innodb_semi_consistent.test +++ b/mysql-test/t/partition_innodb_semi_consistent.test @@ -157,7 +157,7 @@ connection con1; --echo # 3. test for updated key column: TRUNCATE t1; -TRUNCATE t2; +DELETE FROM t2; INSERT INTO t1 VALUES (1,'init'); diff --git a/mysql-test/t/partition_sync.test b/mysql-test/t/partition_sync.test index a732b35b8b9..85eb33ebb6b 100644 --- a/mysql-test/t/partition_sync.test +++ b/mysql-test/t/partition_sync.test @@ -1,4 +1,5 @@ --source include/have_partition.inc +--source include/have_debug.inc # Save the initial number of concurrent sessions. --source include/count_sessions.inc @@ -36,6 +37,57 @@ disconnect con1; DROP TABLE t1; +--echo # +--echo # Bug #46654 False deadlock on concurrent DML/DDL +--echo # with partitions, inconsistent behavior +--echo # + +--disable_warnings +DROP TABLE IF EXISTS tbl_with_partitions; +--enable_warnings + +CREATE TABLE tbl_with_partitions ( i INT ) + PARTITION BY HASH(i); +INSERT INTO tbl_with_partitions VALUES (1); + +connect(con2,localhost,root); +connect(con3,localhost,root); + +--echo # Connection 3 +connection con3; +LOCK TABLE tbl_with_partitions READ; + +--echo # Connection 1 +--echo # Access table with disabled autocommit +connection default; +SET AUTOCOMMIT = 0; +SELECT * FROM tbl_with_partitions; + +--echo # Connection 2 +--echo # Alter table, abort after prepare +connection con2; +set session debug="+d,abort_copy_table"; +--error ER_LOCK_WAIT_TIMEOUT +ALTER TABLE tbl_with_partitions ADD COLUMN f INT; + +--echo # Connection 1 +--echo # Try accessing the table after Alter aborted. +--echo # This used to give ER_LOCK_DEADLOCK. +connection default; +SELECT * FROM tbl_with_partitions; + +--echo # Connection 3 +connection con3; +UNLOCK TABLES; + +--echo # Connection 1 +--echo # Cleanup +connection default; +disconnect con2; +disconnect con3; +DROP TABLE tbl_with_partitions; + + # Check that all connections opened by test cases in this file are really # gone so execution of other tests won't be affected by their presence. --source include/wait_until_count_sessions.inc diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index abb6b7c81f4..a883c00819b 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -3251,7 +3251,44 @@ DROP PROCEDURE p2; ########################################################################### ---echo ---echo End of 6.0 tests. + +--echo # +--echo # WL#4284: Transactional DDL locking +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings +CREATE TABLE t1 (a INT); +BEGIN; +SELECT * FROM t1; +--echo # Test that preparing a CREATE TABLE does not take a exclusive metdata lock. +PREPARE stmt1 FROM "CREATE TABLE t1 AS SELECT 1"; +--error ER_TABLE_EXISTS_ERROR +EXECUTE stmt1; +DEALLOCATE PREPARE stmt1; +DROP TABLE t1; + +--echo # +--echo # WL#4284: Transactional DDL locking +--echo # +--echo # Test that metadata locks taken during prepare are released. +--echo # + +connect(con1,localhost,root,,); +connection default; +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings +CREATE TABLE t1 (a INT); +connection con1; +BEGIN; +PREPARE stmt1 FROM "SELECT * FROM t1"; +connection default; +DROP TABLE t1; +disconnect con1; + +--echo # +--echo # End of 6.0 tests. ########################################################################### diff --git a/mysql-test/t/ps_ddl.test b/mysql-test/t/ps_ddl.test index fee235cd36c..6a2b49ac9b7 100644 --- a/mysql-test/t/ps_ddl.test +++ b/mysql-test/t/ps_ddl.test @@ -278,8 +278,6 @@ deallocate prepare stmt; --echo # Test 7-a: dependent PROCEDURE has changed --echo # ---echo # Note, this scenario is not supported, subject of Bug#12093 ---echo # create table t1 (a int); create trigger t1_ai after insert on t1 for each row @@ -291,11 +289,10 @@ execute stmt using @var; drop procedure p1; create procedure p1 (a int) begin end; set @var= 2; ---error ER_SP_DOES_NOT_EXIST execute stmt using @var; --echo # Cleanup drop procedure p1; -call p_verify_reprepare_count(0); +call p_verify_reprepare_count(1); --echo # Test 7-b: dependent FUNCTION has changed --echo # @@ -355,11 +352,13 @@ set @var=8; --echo # XXX: bug, the SQL statement in the trigger is still --echo # pointing at table 't3', since the view was expanded --echo # at first statement execution. +--echo # Since the view definition is inlined in the statement +--echo # at prepare, changing the view definition does not cause +--echo # repreparation. --echo # Repreparation of the main statement doesn't cause repreparation --echo # of trigger statements. ---error ER_NO_SUCH_TABLE execute stmt using @var; -call p_verify_reprepare_count(1); +call p_verify_reprepare_count(0); --echo # --echo # Sic: the insert went into t3, even though the view now --echo # points at t2. This is because neither the merged view @@ -642,6 +641,9 @@ deallocate prepare stmt; --echo Part 16: VIEW -> TEMPORARY TABLE transitions --echo ===================================================================== +--echo # +--echo # Test 1: Merged view +--echo # create table t2 (a int); insert into t2 (a) values (1); create view t1 as select * from t2; @@ -651,9 +653,39 @@ execute stmt; call p_verify_reprepare_count(0); create temporary table t1 (a int); +# t1 still refers to the view - no reprepare has been done. execute stmt; -call p_verify_reprepare_count(1); +call p_verify_reprepare_count(0); + +drop view t1; +# t1 still refers to the, now deleted, view - no reprepare has been done. +--error ER_NO_SUCH_TABLE +execute stmt; +call p_verify_reprepare_count(0); + +drop table t2; +drop temporary table t1; +deallocate prepare stmt; + +--echo # +--echo # Test 2: Materialized view +--echo # +create table t2 (a int); +insert into t2 (a) values (1); +create algorithm = temptable view t1 as select * from t2; + +prepare stmt from "select * from t1"; +execute stmt; +call p_verify_reprepare_count(0); + +create temporary table t1 (a int); +# t1 still refers to the view - no reprepare has been done. +execute stmt; +call p_verify_reprepare_count(0); + drop view t1; +# t1 still refers to the, now deleted, view - no reprepare has been done. +--error ER_NO_SUCH_TABLE execute stmt; call p_verify_reprepare_count(0); @@ -661,6 +693,28 @@ drop table t2; drop temporary table t1; deallocate prepare stmt; +--echo # +--echo # Test 3: View referencing an Information schema table +--echo # +create view t1 as select table_name from information_schema.views; + +prepare stmt from "select * from t1"; +execute stmt; +call p_verify_reprepare_count(0); + +create temporary table t1 (a int); +# t1 has been substituted with a reference to the IS table +execute stmt; +call p_verify_reprepare_count(0); + +drop view t1; +# Since the IS table has been substituted in, the statement still works +execute stmt; +call p_verify_reprepare_count(0); + +drop temporary table t1; +deallocate prepare stmt; + --echo ===================================================================== --echo Part 17: VIEW -> VIEW (DDL) transitions --echo ===================================================================== @@ -1453,7 +1507,7 @@ execute stmt; call p_verify_reprepare_count(0); drop temporary table t2; execute stmt; -call p_verify_reprepare_count(1); +call p_verify_reprepare_count(0); drop table t2; execute stmt; call p_verify_reprepare_count(0); @@ -1493,27 +1547,24 @@ execute stmt; call p_verify_reprepare_count(0); drop table t1; deallocate prepare stmt; ---echo # XXX: no validation of the first table in case of ---echo # CREATE TEMPORARY TABLE. This is a shortcoming of the current code, ---echo # but since validation is not strictly necessary, nothing is done ---echo # about it. ---echo # Will be fixed as part of work on Bug#21431 "Incomplete support of ---echo # temporary tables" create table t1 (a int); insert into t1 (a) values (1); prepare stmt from "create temporary table if not exists t2 as select * from t1"; execute stmt; drop table t2; execute stmt; +call p_verify_reprepare_count(0); execute stmt; +call p_verify_reprepare_count(1); select * from t2; execute stmt; +call p_verify_reprepare_count(0); select * from t2; drop table t2; create temporary table t2 (a varchar(10)); execute stmt; select * from t2; -call p_verify_reprepare_count(0); +call p_verify_reprepare_count(1); drop table t1; create table t1 (x int); execute stmt; diff --git a/mysql-test/t/ps_ddl1.test b/mysql-test/t/ps_ddl1.test index 379ed576b5f..0145d445a14 100644 --- a/mysql-test/t/ps_ddl1.test +++ b/mysql-test/t/ps_ddl1.test @@ -363,7 +363,7 @@ end| delimiter ;| --error ER_SP_DOES_NOT_EXIST execute stmt; -call p_verify_reprepare_count(1); +call p_verify_reprepare_count(0); --error ER_SP_DOES_NOT_EXIST execute stmt; call p_verify_reprepare_count(0); diff --git a/mysql-test/t/query_cache_28249.test b/mysql-test/t/query_cache_28249.test index 390a1ce6e3d..c95d7553988 100644 --- a/mysql-test/t/query_cache_28249.test +++ b/mysql-test/t/query_cache_28249.test @@ -58,18 +58,18 @@ connection user3; # Typical information_schema.processlist content after sufficient sleep time # ID USER COMMAND TIME STATE INFO # .... -# 2 root Query 5 Locked SELECT *, (SELECT COUNT(*) FROM t2) FROM t1 +# 2 root Query 5 Table lock SELECT *, (SELECT COUNT(*) FROM t2) FROM t1 # .... # XXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # The values marked with 'X' must be reached. --echo # Poll till the select of connection user1 is blocked by the write lock on t1. let $wait_condition= SELECT COUNT(*) = 1 FROM information_schema.processlist -WHERE state = 'Locked' +WHERE state = 'Table lock' AND info = '$select_for_qc'; --source include/wait_condition.inc eval SELECT user,command,state,info FROM information_schema.processlist -WHERE state = 'Locked' +WHERE state = 'Table lock' AND info = '$select_for_qc'; INSERT INTO t1 VALUES (4); diff --git a/mysql-test/t/read_only_innodb.test b/mysql-test/t/read_only_innodb.test index f8c25fdee1d..9e001f2b997 100644 --- a/mysql-test/t/read_only_innodb.test +++ b/mysql-test/t/read_only_innodb.test @@ -33,7 +33,7 @@ set global read_only=1; connection con1; select @@global.read_only; select * from table_11733 ; --- error ER_OPTION_PREVENTS_STATEMENT +--error ER_OPTION_PREVENTS_STATEMENT COMMIT; connection default; diff --git a/mysql-test/t/repair.test b/mysql-test/t/repair.test index eb2ca7992a6..3c55f06ff4c 100644 --- a/mysql-test/t/repair.test +++ b/mysql-test/t/repair.test @@ -158,3 +158,33 @@ CREATE TEMPORARY TABLE tt1 (c1 INT); REPAIR TABLE tt1 USE_FRM; DROP TABLE tt1; + +--echo # +--echo # Bug #48248 assert in MDL_ticket::upgrade_shared_lock_to_exclusive +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1(a INT); +LOCK TABLES t1 READ; +REPAIR TABLE t1; + +UNLOCK TABLES; +DROP TABLE t1; + + +--echo # +--echo # Test for bug #50784 "MDL: Assertion `m_tickets.is_empty() || +--echo # m_tickets.front() == m_trans_sentinel'" +--echo # +--disable_warnings +drop tables if exists t1, t2; +--enable_warnings +create table t1 (i int); +create table t2 (j int); +set @@autocommit= 0; +repair table t1, t2; +set @@autocommit= default; +drop tables t1, t2; diff --git a/mysql-test/t/schema.test b/mysql-test/t/schema.test index a08d9b38935..f106b9e4865 100644 --- a/mysql-test/t/schema.test +++ b/mysql-test/t/schema.test @@ -4,6 +4,9 @@ # Drop mysqltest1 database, as it can left from the previous tests. # +# Save the initial number of concurrent sessions. +--source include/count_sessions.inc + --disable_warnings drop database if exists mysqltest1; --enable_warnings @@ -12,3 +15,93 @@ create schema foo; show create schema foo; show schemas; drop schema foo; + + +--echo # +--echo # Bug #48940 MDL deadlocks against mysql_rm_db +--echo # + +--disable_warnings +DROP SCHEMA IF EXISTS schema1; +--enable_warnings + +connect(con2, localhost, root); + +--echo # Connection default +connection default; + +CREATE SCHEMA schema1; +CREATE TABLE schema1.t1 (a INT); + +SET autocommit= FALSE; +INSERT INTO schema1.t1 VALUES (1); + +--echo # Connection 2 +connection con2; +--send DROP SCHEMA schema1 + +--echo # Connection default +connection default; +let $wait_condition= SELECT COUNT(*)= 1 FROM information_schema.processlist + WHERE state= 'Waiting for table' + AND info='DROP SCHEMA schema1'; +--source include/wait_condition.inc +# Listing the error twice to prevent result diffences based on filename +--error 1,1 +ALTER SCHEMA schema1 DEFAULT CHARACTER SET utf8; +SET autocommit= TRUE; + +--echo # Connection 2 +connection con2; +--reap + +--echo # Connection default +connection default; +disconnect con2; + + +--echo # +--echo # Bug #49988 MDL deadlocks with mysql_create_db, reload_acl_and_cache +--echo # + +--disable_warnings +DROP SCHEMA IF EXISTS schema1; +--enable_warnings + +connect (con2, localhost, root); + +--echo # Connection default +connection default; +CREATE SCHEMA schema1; +CREATE TABLE schema1.t1 (id INT); +LOCK TABLE schema1.t1 WRITE; + +--echo # Connection con2 +connection con2; +--send DROP SCHEMA schema1 + +--echo # Connection default +connection default; +let $wait_condition=SELECT COUNT(*)=1 FROM information_schema.processlist + WHERE state='Waiting for table' and info='DROP SCHEMA schema1'; +--source include/wait_condition.inc + +--echo # CREATE SCHEMA used to give a deadlock. +--echo # Now we prohibit CREATE SCHEMA in LOCK TABLES mode. +--error ER_LOCK_OR_ACTIVE_TRANSACTION +CREATE SCHEMA IF NOT EXISTS schema1; + +--echo # UNLOCK TABLES so DROP SCHEMA can continue. +UNLOCK TABLES; + +--echo # Connection con2 +connection con2; +--reap + +--echo # Connection default +connection default; +disconnect con2; + +# Check that all connections opened by test cases in this file are really +# gone so execution of other tests won't be affected by their presence. +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/t/sp-error.test b/mysql-test/t/sp-error.test index e33adf56284..c8b2595e23d 100644 --- a/mysql-test/t/sp-error.test +++ b/mysql-test/t/sp-error.test @@ -723,7 +723,7 @@ lock table t1 read| # This should fail since we forgot to lock mysql.proc for writing # explicitly, and we can't open mysql.proc for _writing_ if there # are locked tables. ---error 1100 +--error ER_LOCK_OR_ACTIVE_TRANSACTION alter procedure bug9566 comment 'Some comment'| unlock tables| # This should succeed @@ -2490,6 +2490,35 @@ SELECT AVG (a) FROM t1 WHERE b = 999999; SELECT non_existent (a) FROM t1 WHERE b = 999999; DROP TABLE t1; + +# +# Bug #46374 crash, INSERT INTO t1 uses function, function modifies t1 +# +CREATE TABLE t1 ( f2 INTEGER, f3 INTEGER ); +INSERT INTO t1 VALUES ( 1, 1 ); + +delimiter |; + +CREATE FUNCTION func_1 () RETURNS INTEGER +BEGIN + INSERT INTO t1 SELECT * FROM t1 ; + RETURN 1 ; +END| + +delimiter ;| + +# The bug caused the following INSERT statement to trigger +# an assertion. Error 1442 is the correct response +# +--error 1442 +INSERT INTO t1 SELECT * FROM (SELECT 2 AS f1, 2 AS f2) AS A WHERE func_1() = 5; + +# Cleanup +DROP FUNCTION func_1; +DROP TABLE t1; + + + --echo # --echo # Bug #47788: Crash in TABLE_LIST::hide_view_error on UPDATE + VIEW + --echo # SP + MERGE + ALTER @@ -2513,3 +2542,4 @@ DROP VIEW v1; DROP TABLE t1; --echo End of 5.1 tests + diff --git a/mysql-test/t/sp-lock.test b/mysql-test/t/sp-lock.test new file mode 100644 index 00000000000..0b31eabb0f1 --- /dev/null +++ b/mysql-test/t/sp-lock.test @@ -0,0 +1,898 @@ +# Copyright (C) 2009 Sun Microsystems, Inc +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +# +# Metadata lock handling for stored procedures and +# functions. +# +--echo # +--echo # Test coverage for changes performed by the fix +--echo # for Bug#30977 "Concurrent statement using stored function +--echo # and DROP FUNCTION breaks SBR. +--echo # + +--echo # +--echo # 1) Verify that the preceding transaction is +--echo # (implicitly) committed before CREATE/ALTER/DROP +--echo # PROCEDURE. Note, that this is already tested +--echo # in implicit_commit.test, but here we use an alternative +--echo # approach. +--echo # + +--echo # Start a transaction, create a savepoint, +--echo # then call a DDL operation on a procedure, and then check +--echo # that the savepoint is no longer present. + +--disable_warnings +drop table if exists t1; +drop procedure if exists p1; +drop procedure if exists p2; +drop procedure if exists p3; +drop procedure if exists p4; +drop function if exists f1; +--enable_warnings +create table t1 (a int); +--echo # +--echo # Test 'CREATE PROCEDURE'. +--echo # +begin; +savepoint sv; +create procedure p1() begin end; +--error ER_SP_DOES_NOT_EXIST +rollback to savepoint sv; +--echo # +--echo # Test 'ALTER PROCEDURE'. +--echo # +begin; +savepoint sv; +alter procedure p1 comment 'changed comment'; +--error ER_SP_DOES_NOT_EXIST +rollback to savepoint sv; +--echo # +--echo # Test 'DROP PROCEDURE'. +--echo # +begin; +savepoint sv; +drop procedure p1; +--error ER_SP_DOES_NOT_EXIST +rollback to savepoint sv; +--echo # +--echo # Test 'CREATE FUNCTION'. +--echo # +begin; +savepoint sv; +create function f1() returns int return 1; +--error ER_SP_DOES_NOT_EXIST +rollback to savepoint sv; +--echo # +--echo # Test 'ALTER FUNCTION'. +--echo # +begin; +savepoint sv; +alter function f1 comment 'new comment'; +--error ER_SP_DOES_NOT_EXIST +rollback to savepoint sv; +--echo # +--echo # Test 'DROP FUNCTION'. +--echo # +begin; +savepoint sv; +drop function f1; +--error ER_SP_DOES_NOT_EXIST +rollback to savepoint sv; + +--echo # +--echo # 2) Verify that procedure DDL operations fail +--echo # under lock tables. +--echo # +--echo # Auxiliary routines to test ALTER. +create procedure p1() begin end; +create function f1() returns int return 1; + +lock table t1 write; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +create procedure p2() begin end; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +alter procedure p1 comment 'changed comment'; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +drop procedure p1; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +create function f2() returns int return 1; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +alter function f1 comment 'changed comment'; +lock table t1 read; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +create procedure p2() begin end; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +alter procedure p1 comment 'changed comment'; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +drop procedure p1; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +create function f2() returns int return 1; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +alter function f1 comment 'changed comment'; +unlock tables; +--echo # +--echo # Even if we locked a temporary table. +--echo # Todo: this is a restriction we could possibly lift. +--echo # +drop table t1; +create temporary table t1 (a int); +lock table t1 read; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +create procedure p2() begin end; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +alter procedure p1 comment 'changed comment'; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +drop procedure p1; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +create function f2() returns int return 1; +--error ER_LOCK_OR_ACTIVE_TRANSACTION +alter function f1 comment 'changed comment'; +unlock tables; + +drop function f1; +drop procedure p1; +drop temporary table t1; + +--echo # +--echo # 3) Verify that CREATE/ALTER/DROP routine grab an +--echo # exclusive lock. +--echo # +--echo # For that, start a transaction, use a routine. In a concurrent +--echo # connection, try to drop or alter the routine. It should place +--echo # a pending or exclusive lock and block. In another concurrnet +--echo # connection, try to use the routine. +--echo # That should block on the pending exclusive lock. +--echo # +--echo # Establish helper connections. +connect(con1, localhost, root,,); +connect(con2, localhost, root,,); +connect(con3, localhost, root,,); + +--echo # +--echo # Test DROP PROCEDURE. +--echo # +--echo # --> connection default +connection default; +create procedure p1() begin end; +delimiter |; +create function f1() returns int +begin + call p1(); + return 1; +end| +delimiter ;| +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'drop procedure p1'... +send drop procedure p1; +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop procedure t1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop procedure p1'; +--source include/wait_condition.inc +--echo # Demonstrate that there is a pending exclusive lock. +--echo # Sending 'select f1()'... +send select f1(); +--echo # --> connection con3 +connection con3; +--echo # Waitng for 'select f1()' to get blocked by a pending MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='select f1()'; +--echo # --> connection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop procedure p1'... +reap; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'select f1()' +--error ER_SP_DOES_NOT_EXIST +reap; +--echo # --> connection default +connection default; + +--echo # +--echo # Test CREATE PROCEDURE. +--echo # +create procedure p1() begin end; +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'create procedure p1'... +send create procedure p1() begin end; +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'create procedure t1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='create procedure p1() begin end'; +--source include/wait_condition.inc +--echo # Demonstrate that there is a pending exclusive lock. +--echo # Sending 'select f1()'... +send select f1(); +--echo # --> connection con3 +connection con3; +--echo # Waitng for 'select f1()' to get blocked by a pending MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='select f1()'; +--echo # --> connection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'create procedure p1'... +--error ER_SP_ALREADY_EXISTS +reap; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'select f1()' +reap; +connection default; + +--echo # +--echo # Test ALTER PROCEDURE. +--echo # +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'alter procedure p1'... +send alter procedure p1 contains sql; +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'alter procedure t1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='alter procedure p1 contains sql'; +--source include/wait_condition.inc +--echo # Demonstrate that there is a pending exclusive lock. +--echo # Sending 'select f1()'... +send select f1(); +--echo # --> connection con3 +connection con3; +--echo # Waitng for 'select f1()' to get blocked by a pending MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='select f1()'; +--echo # --> connection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'alter procedure p1'... +reap; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'select f1()' +reap; +--echo # --> connection default +connection default; + +--echo # +--echo # Test DROP FUNCTION. +--echo # +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'drop function f1'... +send drop function f1; +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop function f1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop function f1'; +--source include/wait_condition.inc +--echo # Demonstrate that there is a pending exclusive lock. +--echo # Sending 'select f1()'... +send select f1(); +--echo # --> connection con3 +connection con3; +--echo # Waitng for 'select f1()' to get blocked by a pending MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='select f1()'; +--echo # --> connection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop function f1'... +reap; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'select f1()' +--error ER_SP_DOES_NOT_EXIST +reap; +--echo # --> connection default +connection default; + +--echo # +--echo # Test CREATE FUNCTION. +--echo # +create function f1() returns int return 1; +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'create function f1'... +send create function f1() returns int return 2; +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'create function f1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='create function f1() returns int return 2'; +--source include/wait_condition.inc +--echo # Demonstrate that there is a pending exclusive lock. +--echo # Sending 'select f1()'... +send select f1(); +--echo # --> connection con3 +connection con3; +--echo # Waitng for 'select f1()' to get blocked by a pending MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='select f1()'; +--echo # --> connection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'create function f1'... +--error ER_SP_ALREADY_EXISTS +reap; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'select f1()' +reap; +--echo # --> connection default +connection default; + +--echo # +--echo # Test ALTER FUNCTION. +--echo # +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'alter function f1'... +send alter function f1 contains sql; +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'alter function f1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='alter function f1 contains sql'; +--source include/wait_condition.inc +--echo # Demonstrate that there is a pending exclusive lock. +--echo # Sending 'select f1()'... +send select f1(); +--echo # --> connection con3 +connection con3; +--echo # Waitng for 'select f1()' to get blocked by a pending MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='select f1()'; +--echo # --> connection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'alter function f1'... +reap; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'select f1()' +reap; +--echo # --> connection default +connection default; +drop function f1; +drop procedure p1; + +--echo # +--echo # 4) MDL lock should not be taken for +--echo # unrolled CALL statements. +--echo # The primary goal of metadata locks is a consistent binary log. +--echo # When a call statement is unrolled, it doesn't get to the +--echo # binary log, instead the statements that are contained +--echo # in the procedure body do. This can nest to any level. +--echo # +create procedure p1() begin end; +create procedure p2() begin end; +create table t1 (a int); +delimiter |; +create procedure p3() +begin + call p1(); + call p1(); + call p2(); +end| +create procedure p4() +begin + call p1(); + call p1(); + call p2(); + call p2(); + call p3(); +end| +delimiter ;| +begin; +select * from t1; +savepoint sv; +call p4(); +--echo # Prepared statement should not add any locks either. +prepare stmt from "call p4()"; +execute stmt; +execute stmt; +--echo # --> connection con1 +connection con1; +drop procedure p1; +drop procedure p2; +drop procedure p3; +drop procedure p4; +--echo # --> connection default +connection default; +--echo # This is to verify there was no implicit commit. +rollback to savepoint sv; +--error ER_SP_DOES_NOT_EXIST +call p4(); +commit; +drop table t1; + +--echo # +--echo # 5) Locks should be taken on routines +--echo # used indirectly by views or triggers. +--echo # +--echo # +--echo # A function is used from a trigger. +--echo # +create function f1() returns int return 1; +create table t1 (a int); +create table t2 (a int, b int); +create trigger t1_ai after insert on t1 for each row + insert into t2 (a, b) values (new.a, f1()); +begin; +insert into t1 (a) values (1); +--echo # --> connection con1 +connection con1; +--echo # Sending 'drop function f1' +send drop function f1; +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop function f1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop function f1'; +--source include/wait_condition.inc +--echo # --> connnection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop function f1'... +reap; +--echo # --> connection default +connection default; +--echo # +--echo # A function is used from a view. +--echo # +create function f1() returns int return 1; +create view v1 as select f1() as a; +begin; +select * from v1; +--echo # --> connection con1 +connection con1; +--echo # Sending 'drop function f1' +send drop function f1; +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop function f1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop function f1'; +--source include/wait_condition.inc +--echo # --> connnection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop function f1'... +reap; +--echo # --> connection default +connection default; +--echo # +--echo # A procedure is used from a function. +--echo # +delimiter |; +create function f1() returns int +begin + declare v_out int; + call p1(v_out); + return v_out; +end| +delimiter ;| +create procedure p1(out v_out int) set v_out=3; +begin; +select * from v1; +--echo # --> connection con1 +connection con1; +--echo # Sending 'drop procedure p1' +send drop procedure p1; +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop procedure p1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop procedure p1'; +--source include/wait_condition.inc +--echo # --> connnection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop procedure p1'... +reap; +--echo # --> connection default +connection default; + +--echo # +--echo # Deep nesting: a function is used from a procedure used +--echo # from a function used from a view used in a trigger. +--echo # +create function f2() returns int return 4; +create procedure p1(out v_out int) set v_out=f2(); +drop trigger t1_ai; +create trigger t1_ai after insert on t1 for each row + insert into t2 (a, b) values (new.a, (select max(a) from v1)); +begin; +insert into t1 (a) values (3); +--echo # --> connection con1 +connection con1; +--echo # Sending 'drop function f2' +send drop function f2; +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop function f2' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop function f2'; +--source include/wait_condition.inc +--echo # --> connnection default +connection default; +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop function f2'... +reap; +--echo # --> connection default +connection default; + +drop view v1; +drop function f1; +drop procedure p1; +drop table t1, t2; + +--echo # +--echo # 6) Check that ER_LOCK_DEADLOCK is reported if +--echo # acquisition of a shared lock fails during a transaction or +--echo # we need to back off to flush the sp cache. +--echo # +--echo # Sic: now this situation does not require a back off since we +--echo # flush the cache on the fly. +--echo # +create function f1() returns int return 7; +create table t1 (a int); +begin; +select * from t1; +# Used to have a back-off here, with optional ER_LOCK_DEADLOCK +#--error ER_LOCK_DEADLOCK +select f1(); +commit; +drop table t1; +drop function f1; + +--echo # +--echo # 7) Demonstrate that under LOCK TABLES we accumulate locks +--echo # on stored routines, and release metadata locks in +--echo # ROLLBACK TO SAVEPOINT. That is done only for those stored +--echo # routines that are not part of LOCK TABLES prelocking list. +--echo # Those stored routines that are part of LOCK TABLES +--echo # prelocking list are implicitly locked when entering +--echo # LOCK TABLES, and ROLLBACK TO SAVEPOINT has no effect on +--echo # them. +--echo # +create function f1() returns varchar(20) return "f1()"; +create function f2() returns varchar(20) return "f2()"; +create view v1 as select f1() as a; +set @@session.autocommit=0; +lock table v1 read; +select * from v1; +savepoint sv; +select f2(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'drop function f1'... +send drop function f1; +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'drop function f1' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop function f1'; +--source include/wait_condition.inc +--echo # Sending 'drop function f2'... +send drop function f2; +--echo # --> connection default +connection default; +--echo # Waitng for 'drop function f2' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='drop function f2'; +--source include/wait_condition.inc +rollback to savepoint sv; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'drop function f2'... +reap; +--echo # --> connection default +connection default; +unlock tables; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'drop function f1'... +reap; +--echo # --> connection default +connection default; +--error ER_SP_DOES_NOT_EXIST +drop function f1; +--error ER_SP_DOES_NOT_EXIST +drop function f2; +drop view v1; +set @@session.autocommit=default; + +--echo # +--echo # 8) Check the situation when we're preparing or executing a +--echo # prepared statement, and as part of that try to flush the +--echo # session sp cache. However, one of the procedures that +--echo # needs a flush is in use. Verify that there is no infinite +--echo # reprepare loop and no crash. +--echo # +create function f1() returns int return 1; +delimiter |; +--echo # +--echo # We just mention p1() in the body of f2() to make +--echo # sure that p1() metadata is validated when validating +--echo # 'select f2()'. +--echo # Recursion is not allowed in stored functions, so +--echo # an attempt to just invoke p1() from f2() which is in turn +--echo # called from p1() would have given a run-time error. +--echo # +create function f2() returns int +begin + if @var is null then + call p1(); + end if; + return 1; +end| +create procedure p1() +begin + select f1() into @var; + execute stmt; +end| +delimiter ;| +--echo # --> connection con2 +connection con2; +prepare stmt from "select f2()"; +--echo # --> connection default +connection default; +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'alter function f1 ...'... +send alter function f1 comment "comment"; +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'alter function f1 ...' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info like 'alter function f1 comment%'; +--source include/wait_condition.inc +--echo # Sending 'call p1()'... +send call p1(); +connection default; +--echo # Waitng for 'call p1()' to get blocked on MDL lock on f1... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='select f1() into @var'; +--source include/wait_condition.inc +--echo # Let 'alter function f1 ...' go through... +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'alter function f1 ...' +reap; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'call p1()'... +reap; +deallocate prepare stmt; +--echo # --> connection default +connection default; +drop function f1; +drop function f2; +drop procedure p1; + +--echo # +--echo # 9) Check the situation when a stored function is invoked +--echo # from a stored procedure, and recursively invokes the +--echo # stored procedure that is in use. But for the second +--echo # invocation, a cache flush is requested. We can't +--echo # flush the procedure that's in use, and are forced +--echo # to use an old version. It is not a violation of +--echo # consistency, since we unroll top-level calls. +--echo # Just verify the code works. +--echo # +create function f1() returns int return 1; +begin; +select f1(); +--echo # --> connection con1 +connection con1; +--echo # Sending 'alter function f1 ...'... +send alter function f1 comment "comment"; +--echo # --> connection con2 +connection con2; +--echo # Waitng for 'alter function f1 ...' to get blocked on MDL lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info like 'alter function f1 comment%'; +--source include/wait_condition.inc +delimiter |; +--echo # +--echo # We just mention p1() in the body of f2() to make +--echo # sure that p1() is prelocked for f2(). +--echo # Recursion is not allowed in stored functions, so +--echo # an attempt to just invoke p1() from f2() which is in turn +--echo # called from p1() would have given a run-time error. +--echo # +create function f2() returns int +begin + if @var is null then + call p1(); + end if; + return 1; +end| +create procedure p1() +begin + select f1() into @var; + select f2() into @var; +end| +delimiter ;| +--echo # Sending 'call p1()'... +send call p1(); +connection default; +--echo # Waitng for 'call p1()' to get blocked on MDL lock on f1... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='Waiting for table' and info='select f1() into @var'; +--source include/wait_condition.inc +--echo # Let 'alter function f1 ...' go through... +commit; +--echo # --> connection con1 +connection con1; +--echo # Reaping 'alter function f1 ...' +reap; +--echo # --> connection con2 +connection con2; +--echo # Reaping 'call p1()'... +reap; +--echo # --> connection default +connection default; +drop function f1; +drop function f2; +drop procedure p1; + +--echo # +--echo # 10) A select from information_schema.routines now +--echo # flushes the stored routines caches. Test that this +--echo # does not remove from the cache a stored routine +--echo # that is already prelocked. +--echo # +create function f1() returns int return get_lock("30977", 100000); +create function f2() returns int return 2; +delimiter |; +create function f3() returns varchar(255) +begin + declare res varchar(255); + declare c cursor for select routine_name from + information_schema.routines where routine_name='f1'; + select f1() into @var; + open c; + fetch c into res; + close c; + select f2() into @var; + return res; +end| +delimiter ;| +--echo # --> connection con1 +connection con1; +select get_lock("30977", 0); +--echo # --> connection default +connection default; +--echo # Sending 'select f3()'... +send select f3(); +--echo # --> connection con1 +connection con1; +--echo # Waitng for 'select f3()' to get blocked on the user level lock... +let $wait_condition=select count(*)=1 from information_schema.processlist +where state='User lock' and info='select f1() into @var'; +--source include/wait_condition.inc +--echo # Do something to change the cache version. +create function f4() returns int return 4; +drop function f4; +select release_lock("30977"); +--echo # --> connection default +connection default; +--echo # Reaping 'select f3()'... +--echo # Routine 'f2()' should exist and get executed successfully. +reap; +select @var; +drop function f1; +drop function f2; +drop function f3; + + +--echo # 11) Check the situation when the connection is flushing the +--echo # SP cache which contains a procedure that is being executed. +--echo # +--echo # Function f1() calls p1(). Procedure p1() has a DROP +--echo # VIEW statement, which, we know, invalidates the routines cache. +--echo # During cache flush p1() must not be flushed since it's in +--echo # use. +--echo # +delimiter |; +create function f1() returns int +begin + call p1(); + return 1; +end| +create procedure p1() +begin + create view v1 as select 1; + drop view v1; + select f1() into @var; + set @exec_count=@exec_count+1; +end| +delimiter ;| +set @exec_count=0; +--error ER_SP_RECURSION_LIMIT +call p1(); +select @exec_count; +set @@session.max_sp_recursion_depth=5; +set @exec_count=0; +--error ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG +call p1(); +select @exec_count; +drop procedure p1; +drop function f1; +set @@session.max_sp_recursion_depth=default; + +--echo # --> connection con1 +connection con1; +disconnect con1; +--source include/wait_until_disconnected.inc +--echo # --> connection con2 +connection con2; +disconnect con2; +--source include/wait_until_disconnected.inc +--echo # --> connection con3 +connection con3; +disconnect con3; +--source include/wait_until_disconnected.inc +--echo # --> connection default +connection default; +--echo # +--echo # End of 5.5 tests +--echo # diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test index db4d57649d8..366d4bfddc5 100644 --- a/mysql-test/t/sp.test +++ b/mysql-test/t/sp.test @@ -1308,9 +1308,11 @@ select f3()| select id, f3() from t1 as t11 order by id| # Degenerate cases work too :) select f0()| +# But these should not (particularly views should be locked explicitly). +--error ER_TABLE_NOT_LOCKED select * from v0| +--error ER_TABLE_NOT_LOCKED select *, f0() from v0, (select 123) as d1| -# But these should not ! --error ER_TABLE_NOT_LOCKED select id, f3() from t1| --error ER_TABLE_NOT_LOCKED @@ -8497,3 +8499,84 @@ SELECT routine_comment FROM information_schema.routines WHERE routine_name = "p1 DROP PROCEDURE p1; + +--echo # +--echo # Bug #47313 assert in check_key_in_view during CALL procedure +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS t1, t2_unrelated; +DROP PROCEDURE IF EXISTS p1; +--enable_warnings + +CREATE PROCEDURE p1(IN x INT) INSERT INTO t1 VALUES (x); +CREATE VIEW t1 AS SELECT 10 AS f1; + +--echo # t1 refers to the view +--error ER_NON_INSERTABLE_TABLE +CALL p1(1); + +CREATE TEMPORARY TABLE t1 (f1 INT); + +--echo # t1 still refers to the view since it was inlined +--error ER_NON_INSERTABLE_TABLE +CALL p1(2); + +DROP VIEW t1; + +--echo # t1 now refers to the temporary table +CALL p1(3); + +--echo # Check which values were inserted into the temp table. +SELECT * FROM t1; + +DROP TEMPORARY TABLE t1; +DROP PROCEDURE p1; + +--echo # Now test what happens if the sp cache is invalidated. + +CREATE PROCEDURE p1(IN x INT) INSERT INTO t1 VALUES (x); +CREATE VIEW t1 AS SELECT 10 AS f1; +CREATE VIEW v2_unrelated AS SELECT 1 AS r1; + +--echo # Load the procedure into the sp cache +--error ER_NON_INSERTABLE_TABLE +CALL p1(4); + +CREATE TEMPORARY TABLE t1 (f1 int); + +ALTER VIEW v2_unrelated AS SELECT 2 AS r1; + +--echo # Alter view causes the sp cache to be invalidated. +--echo # Now t1 refers to the temporary table, not the view. +CALL p1(5); + +--echo # Check which values were inserted into the temp table. +SELECT * FROM t1; + +DROP TEMPORARY TABLE t1; +DROP VIEW t1, v2_unrelated; +DROP PROCEDURE p1; + +CREATE PROCEDURE p1(IN x INT) INSERT INTO t1 VALUES (x); +CREATE TEMPORARY TABLE t1 (f1 INT); + +--echo # t1 refers to the temporary table +CALL p1(6); + +CREATE VIEW t1 AS SELECT 10 AS f1; + +--echo # Create view causes the sp cache to be invalidated. +--echo # t1 still refers to the temporary table since it shadows the view. +CALL p1(7); + +DROP VIEW t1; + +--echo # Check which values were inserted into the temp table. +SELECT * FROM t1; + +DROP TEMPORARY TABLE t1; +DROP PROCEDURE p1; + + diff --git a/mysql-test/t/sp_notembedded.test b/mysql-test/t/sp_notembedded.test index f593e184ad2..f7984952e33 100644 --- a/mysql-test/t/sp_notembedded.test +++ b/mysql-test/t/sp_notembedded.test @@ -322,7 +322,7 @@ set session low_priority_updates=on; connection rl_wait; let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Locked" and + where state = "Table lock" and info = "update t1 set value='updated' where value='old'"; --source include/wait_condition.inc diff --git a/mysql-test/t/status.test b/mysql-test/t/status.test index f951218f5c8..cca54f6c762 100644 --- a/mysql-test/t/status.test +++ b/mysql-test/t/status.test @@ -58,7 +58,7 @@ let $ID= `select connection_id()`; connection con2; --echo # Switched to connection: con2 # wait for the other query to start executing -let $wait_condition= select 1 from INFORMATION_SCHEMA.PROCESSLIST where ID = $ID and STATE = "Locked"; +let $wait_condition= select 1 from INFORMATION_SCHEMA.PROCESSLIST where ID = $ID and STATE = "Table lock"; --source include/wait_condition.inc unlock tables; diff --git a/mysql-test/t/trigger.test b/mysql-test/t/trigger.test index f3b9d6bb91e..7ff61bd96e1 100644 --- a/mysql-test/t/trigger.test +++ b/mysql-test/t/trigger.test @@ -2489,3 +2489,28 @@ DROP TRIGGER trg1; DROP TRIGGER trg2; DROP TABLE t1; + +--echo # +--echo # Bug #46747 "Crash in MDL_ticket::upgrade_shared_lock_to_exclusive +--echo # on TRIGGER + TEMP table". +--echo # + +--disable_warnings +drop trigger if exists t1_bi; +drop temporary table if exists t1; +drop table if exists t1; +--enable_warnings + +create table t1 (i int); +create trigger t1_bi before insert on t1 for each row set @a:=1; +--echo # Create temporary table which shadows base table with trigger. +create temporary table t1 (j int); +--echo # Dropping of trigger should succeed. +drop trigger t1_bi; +select trigger_name from information_schema.triggers + where event_object_schema = 'test' and event_object_table = 't1'; +--echo # Clean-up. +drop temporary table t1; +drop table t1; + +--echo End of 6.0 tests. diff --git a/mysql-test/t/trigger_notembedded.test b/mysql-test/t/trigger_notembedded.test index 7a7e6c6bc85..8a570a7e87d 100644 --- a/mysql-test/t/trigger_notembedded.test +++ b/mysql-test/t/trigger_notembedded.test @@ -896,7 +896,7 @@ connection default; --echo connection: default let $wait_condition= select count(*) = 1 from information_schema.processlist - where state = "Flushing tables"; + where state = "Waiting for table"; --source include/wait_condition.inc create trigger t1_bi before insert on t1 for each row begin end; unlock tables; diff --git a/mysql-test/t/truncate.test b/mysql-test/t/truncate.test index ba5364bd324..cdfa448f78a 100644 --- a/mysql-test/t/truncate.test +++ b/mysql-test/t/truncate.test @@ -2,7 +2,7 @@ # Test of truncate # --disable_warnings -drop table if exists t1; +drop table if exists t1, t2; --enable_warnings create table t1 (a integer, b integer,c1 CHAR(10)); @@ -69,3 +69,97 @@ drop table t1; # End of 5.0 tests +--echo # +--echo # Bug#20667 - Truncate table fails for a write locked table +--echo # +CREATE TABLE t1 (c1 INT); +LOCK TABLE t1 WRITE; +INSERT INTO t1 VALUES (1); +SELECT * FROM t1; +TRUNCATE TABLE t1; +SELECT * FROM t1; +UNLOCK TABLES; +# +LOCK TABLE t1 READ; +--error ER_TABLE_NOT_LOCKED_FOR_WRITE +TRUNCATE TABLE t1; +UNLOCK TABLES; +# +CREATE TABLE t2 (c1 INT); +LOCK TABLE t2 WRITE; +--error ER_TABLE_NOT_LOCKED +TRUNCATE TABLE t1; +UNLOCK TABLES; +# +CREATE VIEW v1 AS SELECT t1.c1 FROM t1,t2 WHERE t1.c1 = t2.c1; +INSERT INTO t1 VALUES (1), (2), (3); +INSERT INTO t2 VALUES (1), (3), (4); +SELECT * FROM v1; +--error ER_NO_SUCH_TABLE +TRUNCATE v1; +SELECT * FROM v1; +# +LOCK TABLE t1 WRITE; +--error ER_TABLE_NOT_LOCKED +SELECT * FROM v1; +--error ER_NO_SUCH_TABLE +TRUNCATE v1; +--error ER_TABLE_NOT_LOCKED +SELECT * FROM v1; +UNLOCK TABLES; +# +LOCK TABLE t1 WRITE, t2 WRITE; +--error ER_TABLE_NOT_LOCKED +SELECT * FROM v1; +--error ER_NO_SUCH_TABLE +TRUNCATE v1; +--error ER_TABLE_NOT_LOCKED +SELECT * FROM v1; +UNLOCK TABLES; +# +LOCK TABLE v1 WRITE; +SELECT * FROM v1; +--error ER_NO_SUCH_TABLE +TRUNCATE v1; +SELECT * FROM v1; +UNLOCK TABLES; +# +LOCK TABLE t1 WRITE, t2 WRITE, v1 WRITE; +SELECT * FROM v1; +--error ER_NO_SUCH_TABLE +TRUNCATE v1; +SELECT * FROM v1; +UNLOCK TABLES; +# +DROP VIEW v1; +DROP TABLE t1, t2; +# +CREATE PROCEDURE p1() SET @a = 5; +--error ER_NO_SUCH_TABLE +TRUNCATE p1; +SHOW CREATE PROCEDURE p1; +DROP PROCEDURE p1; + +--echo # +--echo # Bug#46452 Crash in MDL, HANDLER OPEN + TRUNCATE TABLE +--echo # +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +CREATE TABLE t1 AS SELECT 1 AS f1; + +HANDLER t1 OPEN; +--echo # Here comes the crash. +TRUNCATE t1; + +--echo # Currently TRUNCATE, just like other DDL, implicitly closes +--echo # open HANDLER table. +--error ER_UNKNOWN_TABLE +HANDLER t1 READ FIRST; + +# Cleanup +DROP TABLE t1; + +--echo # End of 6.0 tests + diff --git a/mysql-test/t/truncate_coverage.test b/mysql-test/t/truncate_coverage.test new file mode 100644 index 00000000000..b7c08b03c8b --- /dev/null +++ b/mysql-test/t/truncate_coverage.test @@ -0,0 +1,164 @@ +# +# Code coverage testing of TRUNCATE TABLE. +# +# Ingo Struewing, 2009-07-20 +# + +--source include/have_debug_sync.inc +SET DEBUG_SYNC='RESET'; + +--let $MYSQLD_DATADIR= `SELECT @@datadir` + +--disable_warnings +DROP TABLE IF EXISTS t1; +--enable_warnings + +--echo # +--echo # Bug#20667 - Truncate table fails for a write locked table +--echo # +######## +# Attack wait_while_table_is_used(). Kill query while trying to +# upgrade MDL. +# +CREATE TABLE t1 (c1 INT); +INSERT INTO t1 VALUES (1); +# +# Acquire a shared metadata lock on table by opening HANDLER for it and wait. +# TRUNCATE shall block on this metadata lock. +# We can't use normal DML as such statements would also block LOCK TABLES. +# +--echo # +--echo # connection con1 +--connect (con1, localhost, root,,) +HANDLER t1 OPEN; +# +# Get connection id of default connection. +# Lock the table and start TRUNCATE, which will block on MDL upgrade. +# +--echo # +--echo # connection default +--connection default +let $ID= `SELECT @id := CONNECTION_ID()`; +LOCK TABLE t1 WRITE; +SET DEBUG_SYNC='mdl_upgrade_shared_lock_to_exclusive SIGNAL waiting'; +send TRUNCATE TABLE t1; +# +# Get the default connection ID into a variable in an invisible statement. +# Kill the TRUNCATE query. This shall result in an error return +# from wait_while_table_is_used(). +# +--echo # +--echo # connection con2 +--connect (con2, localhost, root,,) +SET DEBUG_SYNC='now WAIT_FOR waiting'; +let $invisible_assignment_in_select = `SELECT @id := $ID`; +KILL QUERY @id; +--disconnect con2 +--echo # +--echo # connection con1 +--connection con1 +--echo # Release shared metadata lock by closing HANDLER. +HANDLER t1 CLOSE; +--disconnect con1 +--echo # +--echo # connection default +--connection default +--error ER_QUERY_INTERRUPTED +reap; +UNLOCK TABLES; +DROP TABLE t1; +SET DEBUG_SYNC='RESET'; +######## +# Attack reopen_tables(). Remove form file. +# +CREATE TABLE t1 (c1 INT); +INSERT INTO t1 VALUES (1); +# +# Acquire a shared metadata lock on table by opening HANDLER for it and wait. +# TRUNCATE shall block on this metadata lock. +# We can't use normal DML as such statements would also block LOCK TABLES. +# +--echo # +--echo # connection con1 +--connect (con1, localhost, root,,) +HANDLER t1 OPEN; +# +# Lock the table and start TRUNCATE, which will block on MDL upgrade. +# +--echo # +--echo # connection default +--connection default +LOCK TABLE t1 WRITE; +SET DEBUG_SYNC='mdl_upgrade_shared_lock_to_exclusive SIGNAL waiting'; +send TRUNCATE TABLE t1; +# +# Remove datafile. +# Commit to let TRUNCATE continue. +# +--echo # +--echo # connection con2 +--connect (con2, localhost, root,,) +SET DEBUG_SYNC='now WAIT_FOR waiting'; +--remove_file $MYSQLD_DATADIR/test/t1.frm +--disconnect con2 +--echo # +--echo # connection con1 +--connection con1 +HANDLER t1 CLOSE; +--disconnect con1 +--echo # +--echo # connection default +--connection default +--error ER_NO_SUCH_TABLE +reap; +UNLOCK TABLES; +--error ER_BAD_TABLE_ERROR +DROP TABLE t1; +SET DEBUG_SYNC='RESET'; +######## +# Attack acquire_exclusive_locks(). Hold a global read lock. +# Non-LOCK TABLE case. +# +CREATE TABLE t1 (c1 INT); +INSERT INTO t1 VALUES (1); +# +# Start a transaction and execute a DML in it. Since 5.4.4 this leaves +# a shared meta data lock (MDL) behind. TRUNCATE shall block on it. +# +--echo # +--echo # connection con1 +--connect (con1, localhost, root,,) +START TRANSACTION; +INSERT INTO t1 VALUES (2); +# +# Get connection id of default connection. +# Start TRUNCATE, which will block on acquire_exclusive_locks(). +# +--echo # +--echo # connection default +--connection default +let $ID= `SELECT @id := CONNECTION_ID()`; +SET DEBUG_SYNC='mdl_acquire_lock_wait SIGNAL waiting'; +send TRUNCATE TABLE t1; +# +# Get the default connection ID into a variable in an invisible statement. +# Kill the TRUNCATE query. This shall result in an error return +# from wait_while_table_is_used(). +# +--echo # +--echo # connection con1 +--connection con1 +SET DEBUG_SYNC='now WAIT_FOR waiting'; +let $invisible_assignment_in_select = `SELECT @id := $ID`; +KILL QUERY @id; +COMMIT; +--disconnect con1 +--echo # +--echo # connection default +--connection default +--error ER_QUERY_INTERRUPTED +reap; +UNLOCK TABLES; +DROP TABLE t1; +SET DEBUG_SYNC='RESET'; + diff --git a/mysql-test/t/view.test b/mysql-test/t/view.test index 4515f26bc52..8297013611f 100644 --- a/mysql-test/t/view.test +++ b/mysql-test/t/view.test @@ -1,3 +1,4 @@ + --disable_warnings drop table if exists t1,t2,t3,t4,t9,`t1a``b`,v1,v2,v3,v4,v5,v6; drop view if exists t1,t2,`t1a``b`,v1,v2,v3,v4,v5,v6; @@ -1016,10 +1017,8 @@ lock tables t1 read, v1 read; select * from v1; -- error ER_TABLE_NOT_LOCKED select * from t2; -drop view v1; ---error ER_TABLE_NOT_LOCKED_FOR_WRITE -drop table t1, t2; unlock tables; +drop view v1; drop table t1, t2; # @@ -3934,3 +3933,45 @@ create view v_9801 as drop table t_9801; +--echo # +--echo # Bug #47335 assert in get_table_share +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP VIEW IF EXISTS v1; +--enable_warnings + +CREATE TEMPORARY TABLE t1 (id INT); +--error ER_NO_SUCH_TABLE +ALTER VIEW t1 AS SELECT 1 AS f1; +DROP TABLE t1; + +CREATE VIEW v1 AS SELECT 1 AS f1; +CREATE TEMPORARY TABLE v1 (id INT); +ALTER VIEW v1 AS SELECT 2 AS f1; +DROP TABLE v1; +SELECT * FROM v1; +DROP VIEW v1; + + +--echo # +--echo # Bug #47635 assert in start_waiting_global_read_lock +--echo # during CREATE VIEW +--echo # + +--disable_warnings +DROP TABLE IF EXISTS t1, t2; +DROP VIEW IF EXISTS t2; +--enable_warnings + +CREATE TABLE t1 (f1 integer); +CREATE TEMPORARY TABLE IF NOT EXISTS t1 (f1 integer); +CREATE TEMPORARY TABLE t2 (f1 integer); +DROP TABLE t1; +FLUSH TABLES WITH READ LOCK; +--error ER_CANT_UPDATE_WITH_READLOCK +CREATE VIEW t2 AS SELECT * FROM t1; + +UNLOCK TABLES; +DROP TABLE t1, t2; diff --git a/mysql-test/t/view_grant.test b/mysql-test/t/view_grant.test index f01edb1e499..d94bcfc29ed 100644 --- a/mysql-test/t/view_grant.test +++ b/mysql-test/t/view_grant.test @@ -1047,9 +1047,9 @@ GRANT SELECT ON db26813.t1 TO u26813@localhost; connect (u1,localhost,u26813,,db26813); connection u1; ---error ER_SPECIFIC_ACCESS_DENIED_ERROR +--error ER_TABLEACCESS_DENIED_ERROR ALTER VIEW v1 AS SELECT f2 FROM t1; ---error ER_SPECIFIC_ACCESS_DENIED_ERROR +--error ER_TABLEACCESS_DENIED_ERROR ALTER VIEW v2 AS SELECT f2 FROM t1; --error ER_SPECIFIC_ACCESS_DENIED_ERROR ALTER VIEW v3 AS SELECT f2 FROM t1; diff --git a/mysql-test/t/xa.test b/mysql-test/t/xa.test index f84d822170f..0f705ae20c6 100644 --- a/mysql-test/t/xa.test +++ b/mysql-test/t/xa.test @@ -76,9 +76,10 @@ xa rollback 'testa','testb'; xa start 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz'; select * from t1; -drop table t1; disconnect con1; +connection default; +drop table t1; # # Bug#28323: Server crashed in xid cache operations diff --git a/mysys/hash.c b/mysys/hash.c index 5fa804ce7ce..39f3ad8d31e 100644 --- a/mysys/hash.c +++ b/mysys/hash.c @@ -232,6 +232,8 @@ my_hash_value_type my_calc_hash(const HASH *hash, { return calc_hash(hash, key, length ? length : hash->key_length); } + + /* Search after a record based on a key @@ -242,11 +244,17 @@ my_hash_value_type my_calc_hash(const HASH *hash, uchar* my_hash_first(const HASH *hash, const uchar *key, size_t length, HASH_SEARCH_STATE *current_record) { - return my_hash_first_from_hash_value(hash, - calc_hash(hash, key, length ? length : hash->key_length), - key, length, current_record); + uchar *res; + if (my_hash_inited(hash)) + res= my_hash_first_from_hash_value(hash, + calc_hash(hash, key, length ? length : hash->key_length), + key, length, current_record); + else + res= 0; + return res; } - + + uchar* my_hash_first_from_hash_value(const HASH *hash, my_hash_value_type hash_value, const uchar *key, diff --git a/mysys/my_static.c b/mysys/my_static.c index fb62e92dfd1..a86fe6c7ab7 100644 --- a/mysys/my_static.c +++ b/mysys/my_static.c @@ -92,6 +92,19 @@ void (*error_handler_hook)(uint error,const char *str,myf MyFlags)= void (*fatal_error_handler_hook)(uint error,const char *str,myf MyFlags)= my_message_no_curses; +static const char *proc_info_dummy(void *a __attribute__((unused)), + const char *b __attribute__((unused)), + const char *c __attribute__((unused)), + const char *d __attribute__((unused)), + const unsigned int e __attribute__((unused))) +{ + return 0; +} + +/* this is to be able to call set_thd_proc_info from the C code */ +const char *(*proc_info_hook)(void *, const char *, const char *, const char *, + const unsigned int)= proc_info_dummy; + #if defined(ENABLED_DEBUG_SYNC) /** Global pointer to be set if callback function is defined diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c index 5a71e58cdbf..6d62476ecf3 100644 --- a/mysys/thr_lock.c +++ b/mysys/thr_lock.c @@ -396,6 +396,7 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, struct timespec wait_timeout; enum enum_thr_lock_result result= THR_LOCK_ABORTED; my_bool can_deadlock= test(data->owner->info->n_cursors); + const char *old_proc_info; DBUG_ENTER("wait_for_lock"); /* @@ -434,6 +435,9 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, thread_var->current_cond= cond; data->cond= cond; + old_proc_info= proc_info_hook(NULL, "Table lock", + __func__, __FILE__, __LINE__); + if (can_deadlock) set_timespec(wait_timeout, table_lock_wait_timeout); while (!thread_var->abort || in_wait_list) @@ -504,6 +508,9 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, thread_var->current_mutex= 0; thread_var->current_cond= 0; mysql_mutex_unlock(&thread_var->mutex); + + proc_info_hook(NULL, old_proc_info, __func__, __FILE__, __LINE__); + DBUG_RETURN(result); } @@ -533,13 +540,31 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, /* Request for READ lock */ if (lock->write.data) { - /* We can allow a read lock even if there is already a write lock - on the table in one the following cases: - - This thread alread have a write lock on the table - - The write lock is TL_WRITE_ALLOW_READ or TL_WRITE_DELAYED - and the read lock is TL_READ_HIGH_PRIORITY or TL_READ - - The write lock is TL_WRITE_CONCURRENT_INSERT or TL_WRITE_ALLOW_WRITE - and the read lock is not TL_READ_NO_INSERT + /* + We can allow a read lock even if there is already a + write lock on the table if they are owned by the same + thread or if they satisfy the following lock + compatibility matrix: + + Request + /------- + H|++++ WRITE_ALLOW_WRITE + e|+++- WRITE_ALLOW_READ + l|+++- WRITE_CONCURRENT_INSERT + d|++++ WRITE_DELAYED + |||| + |||\= READ_NO_INSERT + ||\ = READ_HIGH_PRIORITY + |\ = READ_WITH_SHARED_LOCKS + \ = READ + + + = Request can be satisified. + - = Request cannot be satisified. + + READ_NO_INSERT and WRITE_ALLOW_WRITE should in principle + be incompatible. However this will cause starvation of + LOCK TABLE READ in InnoDB under high write load. + See Bug#42147 for more information. */ DBUG_PRINT("lock",("write locked 1 by thread: 0x%lx", @@ -631,6 +656,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, { if (lock->write.data->type == TL_WRITE_ONLY) { + /* purecov: begin tested */ /* Allow lock owner to bypass TL_WRITE_ONLY. */ if (!thr_lock_owner_equal(data->owner, lock->write.data->owner)) { @@ -639,6 +665,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, result= THR_LOCK_ABORTED; /* Can't wait for this one */ goto end; } + /* purecov: end */ } /* @@ -647,14 +674,23 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, write locks are of TL_WRITE_ALLOW_WRITE type. Note that, since lock requests for the same table are sorted in - such way that requests with higher thr_lock_type value come first, - lock being requested usually has equal or "weaker" type than one - which thread might have already acquired. - The exceptions are situations when: - - old lock type is TL_WRITE_ALLOW_READ and new lock type is - TL_WRITE_ALLOW_WRITE - - when old lock type is TL_WRITE_DELAYED - But these should never happen within MySQL. + such way that requests with higher thr_lock_type value come first + (with one exception (*)), lock being requested usually (**) has + equal or "weaker" type than one which thread might have already + acquired. + *) The only exception to this rule is case when type of old lock + is TL_WRITE_LOW_PRIORITY and type of new lock is changed inside + of thr_lock() from TL_WRITE_CONCURRENT_INSERT to TL_WRITE since + engine turns out to be not supporting concurrent inserts. + Note that since TL_WRITE has the same compatibility rules as + TL_WRITE_LOW_PRIORITY (their only difference is priority), + it is OK to grant new lock without additional checks in such + situation. + **) The exceptions are situations when: + - old lock type is TL_WRITE_ALLOW_READ and new lock type is + TL_WRITE_ALLOW_WRITE + - when old lock type is TL_WRITE_DELAYED + But these should never happen within MySQL. Therefore it is OK to allow acquiring write lock on the table if this thread already holds some write lock on it. @@ -663,7 +699,9 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner, different types of write lock on the same table). */ DBUG_ASSERT(! has_old_lock(lock->write.data, data->owner) || - (lock_type <= lock->write.data->type && + ((lock_type <= lock->write.data->type || + (lock_type == TL_WRITE && + lock->write.data->type == TL_WRITE_LOW_PRIORITY)) && ! ((lock_type < TL_WRITE_ALLOW_READ && lock->write.data->type == TL_WRITE_ALLOW_READ) || lock->write.data->type == TL_WRITE_DELAYED))); @@ -1026,12 +1064,43 @@ thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_OWNER *owner) (long) pos[0]->lock, pos[0]->type); fflush(stdout); #endif } - /* - Ensure that all get_locks() have the same status - If we lock the same table multiple times, we must use the same - status_param! - */ + thr_lock_merge_status(data, count); + DBUG_RETURN(THR_LOCK_SUCCESS); +} + + +/** + Ensure that all locks for a given table have the same + status_param. + + This is a MyISAM and possibly Maria specific crutch. MyISAM + engine stores data file length, record count and other table + properties in status_param member of handler. When a table is + locked, connection-local copy is made from a global copy + (myisam_share) by mi_get_status(). When a table is unlocked, + the changed status is transferred back to the global share by + mi_update_status(). + + One thing MyISAM doesn't do is to ensure that when the same + table is opened twice in a connection all instances share the + same status_param. This is necessary, however: for one, to keep + all instances of a connection "on the same page" with regard to + the current state of the table. For other, unless this is done, + myisam_share will always get updated from the last unlocked + instance (in mi_update_status()), and when this instance was not + the one that was used to update data, records may be lost. + + For each table, this function looks up the last lock_data in the + list of acquired locks, and makes sure that all other instances + share status_param with it. +*/ + +void +thr_lock_merge_status(THR_LOCK_DATA **data, uint count) +{ #if !defined(DONT_USE_RW_LOCKS) + THR_LOCK_DATA **pos= data; + THR_LOCK_DATA **end= data + count; if (count > 1) { THR_LOCK_DATA *last_lock= end[-1]; @@ -1073,7 +1142,6 @@ thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_OWNER *owner) } while (pos != data); } #endif - DBUG_RETURN(THR_LOCK_SUCCESS); } /* free all locks */ diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 82a94b64365..3d947a7b852 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -76,7 +76,8 @@ SET (SQL_SOURCE rpl_rli.cc rpl_mi.cc sql_servers.cc sql_connect.cc scheduler.cc sql_profile.cc event_parse_data.cc - sql_signal.cc rpl_handler.cc sys_vars.cc + sql_signal.cc rpl_handler.cc mdl.cc + transaction.cc sys_vars.cc ${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc ${PROJECT_SOURCE_DIR}/sql/sql_yacc.h ${PROJECT_SOURCE_DIR}/include/mysqld_error.h diff --git a/sql/Makefile.am b/sql/Makefile.am index 5f8bc8ef112..e7634f74c4b 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -112,7 +112,8 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \ event_data_objects.h event_scheduler.h \ sql_partition.h partition_info.h partition_element.h \ contributors.h sql_servers.h sql_signal.h records.h \ - sql_prepare.h rpl_handler.h replication.h sys_vars.h + sql_prepare.h rpl_handler.h replication.h mdl.h \ + sql_plist.h transaction.h sys_vars.h mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ item.cc item_sum.cc item_buff.cc item_func.cc \ @@ -158,7 +159,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ sql_plugin.cc sql_binlog.cc \ sql_builtin.cc sql_tablespace.cc partition_info.cc \ sql_servers.cc event_parse_data.cc sql_signal.cc \ - rpl_handler.cc + rpl_handler.cc mdl.cc transaction.cc nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index 9f3863eb2b0..432a4a4a555 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -572,7 +572,7 @@ Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type, TABLE_LIST tables; DBUG_ENTER("Event_db_repository::open_event_table"); - tables.init_one_table("mysql", "event", lock_type); + tables.init_one_table("mysql", 5, "event", 5, "event", lock_type); if (simple_open_n_lock_tables(thd, &tables)) { @@ -1126,7 +1126,7 @@ Event_db_repository::check_system_tables(THD *thd) /* Check mysql.db */ - tables.init_one_table("mysql", "db", TL_READ); + tables.init_one_table("mysql", 5, "db", 2, "db", TL_READ); if (simple_open_n_lock_tables(thd, &tables)) { @@ -1141,7 +1141,7 @@ Event_db_repository::check_system_tables(THD *thd) close_thread_tables(thd); } /* Check mysql.user */ - tables.init_one_table("mysql", "user", TL_READ); + tables.init_one_table("mysql", 5, "user", 4, "user", TL_READ); if (simple_open_n_lock_tables(thd, &tables)) { @@ -1161,7 +1161,7 @@ Event_db_repository::check_system_tables(THD *thd) close_thread_tables(thd); } /* Check mysql.event */ - tables.init_one_table("mysql", "event", TL_READ); + tables.init_one_table("mysql", 5, "event", 5, "event", TL_READ); if (simple_open_n_lock_tables(thd, &tables)) { diff --git a/sql/events.cc b/sql/events.cc index 8b81fa7951d..57f193aa473 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -296,15 +296,6 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, int ret; DBUG_ENTER("Events::create_event"); - /* - Let's commit the transaction first - MySQL manual specifies - that a DDL issues an implicit commit, and it doesn't say "successful - DDL", so that an implicit commit is a property of any successfully - parsed DDL statement. - */ - if (end_active_trans(thd)) - DBUG_RETURN(TRUE); - if (check_if_system_tables_error()) DBUG_RETURN(TRUE); @@ -379,7 +370,7 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, } /* If the definer is not set or set to CURRENT_USER, the value of CURRENT_USER will be written into the binary log as the definer for the SQL thread. */ - ret= write_bin_log(thd, TRUE, log_query.c_ptr(), log_query.length()); + write_bin_log(thd, TRUE, log_query.c_ptr(), log_query.length()); } } mysql_mutex_unlock(&LOCK_event_metadata); @@ -416,13 +407,6 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, DBUG_ENTER("Events::update_event"); - /* - For consistency, implicit COMMIT should be the first thing in the - execution chain. - */ - if (end_active_trans(thd)) - DBUG_RETURN(TRUE); - if (check_if_system_tables_error()) DBUG_RETURN(TRUE); @@ -498,7 +482,7 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, new_element); /* Binlog the alter event. */ DBUG_ASSERT(thd->query() && thd->query_length()); - ret= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } } mysql_mutex_unlock(&LOCK_event_metadata); @@ -537,20 +521,6 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists) int ret; DBUG_ENTER("Events::drop_event"); - /* - In MySQL, DDL must always commit: since mysql.* tables are - non-transactional, we must modify them outside a transaction - to not break atomicity. - But the second and more important reason to commit here - regardless whether we're actually changing mysql.event table - or not is replication: end_active_trans syncs the binary log, - and unless we run DDL in it's own transaction it may simply - never appear on the slave in case the outside transaction - rolls back. - */ - if (end_active_trans(thd)) - DBUG_RETURN(TRUE); - if (check_if_system_tables_error()) DBUG_RETURN(TRUE); @@ -572,7 +542,7 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists) event_queue->drop_event(thd, dbname, name); /* Binlog the drop event. */ DBUG_ASSERT(thd->query() && thd->query_length()); - ret= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } mysql_mutex_unlock(&LOCK_event_metadata); DBUG_RETURN(ret); @@ -700,7 +670,7 @@ send_show_create_event(THD *thd, Event_timed *et, Protocol *protocol) bool Events::show_create_event(THD *thd, LEX_STRING dbname, LEX_STRING name) { - Open_tables_state open_tables_backup; + Open_tables_backup open_tables_backup; Event_timed et; bool ret; @@ -755,7 +725,7 @@ Events::fill_schema_events(THD *thd, TABLE_LIST *tables, COND * /* cond */) { char *db= NULL; int ret; - Open_tables_state open_tables_backup; + Open_tables_backup open_tables_backup; DBUG_ENTER("Events::fill_schema_events"); if (check_if_system_tables_error()) diff --git a/sql/field.h b/sql/field.h index bb3636c654e..b091431de29 100644 --- a/sql/field.h +++ b/sql/field.h @@ -478,7 +478,6 @@ public: } /* Hash value */ virtual void hash(ulong *nr, ulong *nr2); - friend bool reopen_table(THD *,struct st_table *,bool); friend int cre_myisam(char * name, register TABLE *form, uint options, ulonglong auto_increment_value); friend class Copy_field; diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc index b254d5d387c..f544b2a9c20 100644 --- a/sql/ha_ndbcluster.cc +++ b/sql/ha_ndbcluster.cc @@ -668,7 +668,7 @@ int ha_ndbcluster::ndb_err(NdbTransaction *trans) bzero((char*) &table_list,sizeof(table_list)); table_list.db= m_dbname; table_list.alias= table_list.table_name= m_tabname; - close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE); + close_cached_tables(thd, &table_list, FALSE, FALSE); break; } default: @@ -4617,7 +4617,7 @@ int ha_ndbcluster::start_statement(THD *thd, trans_register_ha(thd, FALSE, ndbcluster_hton); if (!thd_ndb->trans) { - if (thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) + if (thd->in_multi_stmt_transaction()) trans_register_ha(thd, TRUE, ndbcluster_hton); DBUG_PRINT("trans",("Starting transaction")); thd_ndb->trans= ndb->startTransaction(); @@ -4687,7 +4687,7 @@ int ha_ndbcluster::init_handler_for_statement(THD *thd, Thd_ndb *thd_ndb) } #endif - if (thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) + if (thd->in_multi_stmt_transaction()) { const void *key= m_table; HASH_SEARCH_STATE state; @@ -4771,7 +4771,7 @@ int ha_ndbcluster::external_lock(THD *thd, int lock_type) if (opt_ndb_cache_check_time && m_rows_changed) { DBUG_PRINT("info", ("Rows has changed and util thread is running")); - if (thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) + if (thd->in_multi_stmt_transaction()) { DBUG_PRINT("info", ("Add share to list of tables to be invalidated")); /* NOTE push_back allocates memory using transactions mem_root! */ @@ -4790,7 +4790,7 @@ int ha_ndbcluster::external_lock(THD *thd, int lock_type) DBUG_PRINT("trans", ("Last external_lock")); PRINT_OPTION_FLAGS(thd); - if (!(thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) + if (!thd->in_multi_stmt_transaction()) { if (thd_ndb->trans) { @@ -4900,8 +4900,7 @@ static int ndbcluster_commit(handlerton *hton, THD *thd, bool all) PRINT_OPTION_FLAGS(thd); DBUG_PRINT("enter", ("Commit %s", (all ? "all" : "stmt"))); thd_ndb->start_stmt_count= 0; - if (trans == NULL || (!all && - thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) + if (trans == NULL || (!all && thd->in_multi_stmt_transaction())) { /* An odditity in the handler interface is that commit on handlerton @@ -4971,7 +4970,7 @@ static int ndbcluster_rollback(handlerton *hton, THD *thd, bool all) DBUG_ASSERT(ndb); thd_ndb->start_stmt_count= 0; if (trans == NULL || (!all && - thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) + thd->in_multi_stmt_transaction())) { /* Ignore end-of-statement until real rollback or commit is called */ DBUG_PRINT("info", ("Rollback before start or end-of-statement only")); @@ -7379,6 +7378,16 @@ int ndbcluster_find_files(handlerton *hton, THD *thd, } } + /* + ndbcluster_find_files() may be called from I_S code and ndbcluster_binlog + thread in situations when some tables are already open. This means that + code below will try to obtain exclusive metadata lock on some table + while holding shared meta-data lock on other tables. This might lead to + a deadlock, and therefore is disallowed by assertions of the metadata + locking subsystem. This is violation of metadata + locking protocol which has to be closed ASAP. + XXX: the scenario described above is not covered with any test. + */ if (!global_read_lock) { // Delete old files @@ -7402,8 +7411,9 @@ int ndbcluster_find_files(handlerton *hton, THD *thd, } } + /* Lock mutex before creating .FRM files. */ mysql_mutex_lock(&LOCK_open); - // Create new files + /* Create new files. */ List_iterator_fast<char> it2(create_list); while ((file_name_str=it2++)) { @@ -8254,17 +8264,15 @@ ndbcluster_cache_retrieval_allowed(THD *thd, ulonglong *engine_data) { Uint64 commit_count; - bool is_autocommit= !(thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); char *dbname= full_name; char *tabname= dbname+strlen(dbname)+1; #ifndef DBUG_OFF char buff[22], buff2[22]; #endif DBUG_ENTER("ndbcluster_cache_retrieval_allowed"); - DBUG_PRINT("enter", ("dbname: %s, tabname: %s, is_autocommit: %d", - dbname, tabname, is_autocommit)); + DBUG_PRINT("enter", ("dbname: %s, tabname: %s", dbname, tabname)); - if (!is_autocommit) + if (thd->in_multi_stmt_transaction()) { DBUG_PRINT("exit", ("No, don't use cache in transaction")); DBUG_RETURN(FALSE); @@ -8329,12 +8337,10 @@ ha_ndbcluster::register_query_cache_table(THD *thd, #ifndef DBUG_OFF char buff[22]; #endif - bool is_autocommit= !(thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); DBUG_ENTER("ha_ndbcluster::register_query_cache_table"); - DBUG_PRINT("enter",("dbname: %s, tabname: %s, is_autocommit: %d", - m_dbname, m_tabname, is_autocommit)); + DBUG_PRINT("enter",("dbname: %s, tabname: %s", m_dbname, m_tabname)); - if (!is_autocommit) + if (thd->in_multi_stmt_transaction()) { DBUG_PRINT("exit", ("Can't register table during transaction")); DBUG_RETURN(FALSE); @@ -8445,7 +8451,7 @@ int handle_trailing_share(NDB_SHARE *share) table_list.db= share->db; table_list.alias= table_list.table_name= share->table_name; mysql_mutex_assert_owner(&LOCK_open); - close_cached_tables(thd, &table_list, TRUE, FALSE, FALSE); + close_cached_tables(thd, &table_list, TRUE, FALSE); mysql_mutex_lock(&ndbcluster_mutex); /* ndb_share reference temporary free */ diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index 25f8474feae..a736dc4536f 100644 --- a/sql/ha_ndbcluster_binlog.cc +++ b/sql/ha_ndbcluster_binlog.cc @@ -266,7 +266,7 @@ static void run_query(THD *thd, char *buf, char *end, DBUG_PRINT("query", ("%s", thd->query())); DBUG_ASSERT(!thd->in_sub_stmt); - DBUG_ASSERT(!thd->prelocked_mode); + DBUG_ASSERT(!thd->locked_tables_mode); mysql_parse(thd, thd->query(), thd->query_length(), &found_semicolon); @@ -286,7 +286,13 @@ static void run_query(THD *thd, char *buf, char *end, 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 @@ -925,7 +931,7 @@ int ndbcluster_setup_binlog_table_shares(THD *thd) ndb_binlog_tables_inited= TRUE; if (opt_ndb_extra_logging) sql_print_information("NDB Binlog: ndb tables writable"); - close_cached_tables(NULL, NULL, TRUE, FALSE, FALSE); + close_cached_tables(NULL, NULL, TRUE, FALSE); mysql_mutex_unlock(&LOCK_open); /* Signal injector thread that all is setup */ mysql_cond_signal(&injector_cond); @@ -1739,7 +1745,7 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp, bzero((char*) &table_list,sizeof(table_list)); table_list.db= (char *)dbname; table_list.alias= table_list.table_name= (char *)tabname; - close_cached_tables(thd, &table_list, TRUE, FALSE, FALSE); + close_cached_tables(thd, &table_list, TRUE, FALSE); if ((error= ndbcluster_binlog_open_table(thd, share, table_share, table, 1))) @@ -1845,7 +1851,7 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp, bzero((char*) &table_list,sizeof(table_list)); table_list.db= (char *)dbname; table_list.alias= table_list.table_name= (char *)tabname; - close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE); + close_cached_tables(thd, &table_list, FALSE, FALSE); /* ndb_share reference create free */ DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u", share->key, share->use_count)); @@ -1966,7 +1972,7 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb, bzero((char*) &table_list,sizeof(table_list)); table_list.db= schema->db; table_list.alias= table_list.table_name= schema->name; - close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE); + close_cached_tables(thd, &table_list, FALSE, FALSE); } /* ndb_share reference temporary free */ if (share) @@ -2083,7 +2089,7 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb, mysql_mutex_unlock(&ndb_schema_share_mutex); /* end protect ndb_schema_share */ - close_cached_tables(NULL, NULL, FALSE, FALSE, FALSE); + close_cached_tables(NULL, NULL, FALSE, FALSE); // fall through case NDBEVENT::TE_ALTER: ndb_handle_schema_change(thd, ndb, pOp, tmp_share); @@ -2240,7 +2246,7 @@ ndb_binlog_thread_handle_schema_event_post_epoch(THD *thd, bzero((char*) &table_list,sizeof(table_list)); table_list.db= schema->db; table_list.alias= table_list.table_name= schema->name; - close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE); + close_cached_tables(thd, &table_list, FALSE, FALSE); } if (schema_type != SOT_ALTER_TABLE) break; @@ -2327,22 +2333,21 @@ struct ndb_binlog_index_row { /* Open the ndb_binlog_index table */ -static int open_ndb_binlog_index(THD *thd, TABLE_LIST *tables, - TABLE **ndb_binlog_index) +static int open_ndb_binlog_index(THD *thd, TABLE **ndb_binlog_index) { static char repdb[]= NDB_REP_DB; static char reptable[]= NDB_REP_TABLE; const char *save_proc_info= thd->proc_info; + TABLE_LIST *tables= &binlog_tables; - bzero((char*) tables, sizeof(*tables)); - tables->db= repdb; - tables->alias= tables->table_name= reptable; - tables->lock_type= TL_WRITE; + tables->init_one_table(repdb, strlen(repdb), reptable, strlen(reptable), + reptable, TL_WRITE); thd->proc_info= "Opening " NDB_REP_DB "." NDB_REP_TABLE; + tables->required_type= FRMTYPE_TABLE; uint counter; thd->clear_error(); - if (open_tables(thd, &tables, &counter, MYSQL_LOCK_IGNORE_FLUSH)) + if (simple_open_n_lock_tables(thd, tables)) { if (thd->killed) sql_print_error("NDB Binlog: Opening ndb_binlog_index: killed"); @@ -2376,28 +2381,11 @@ int ndb_add_ndb_binlog_index(THD *thd, void *_row) ulong saved_options= thd->variables.option_bits; thd->variables.option_bits&= ~OPTION_BIN_LOG; - for ( ; ; ) /* loop for need_reopen */ + if (!ndb_binlog_index && open_ndb_binlog_index(thd, &ndb_binlog_index)) { - if (!ndb_binlog_index && open_ndb_binlog_index(thd, &binlog_tables, &ndb_binlog_index)) - { - error= -1; - goto add_ndb_binlog_index_err; - } - - if (lock_tables(thd, &binlog_tables, 1, &need_reopen)) - { - if (need_reopen) - { - TABLE_LIST *p_binlog_tables= &binlog_tables; - close_tables_for_reopen(thd, &p_binlog_tables); - ndb_binlog_index= 0; - continue; - } - sql_print_error("NDB Binlog: Unable to lock table ndb_binlog_index"); - error= -1; - goto add_ndb_binlog_index_err; - } - break; + sql_print_error("NDB Binlog: Unable to lock table ndb_binlog_index"); + error= -1; + goto add_ndb_binlog_index_err; } /* @@ -2422,10 +2410,6 @@ int ndb_add_ndb_binlog_index(THD *thd, void *_row) goto add_ndb_binlog_index_err; } - mysql_unlock_tables(thd, thd->lock); - thd->lock= 0; - thd->variables.option_bits= saved_options; - return 0; add_ndb_binlog_index_err: close_thread_tables(thd); ndb_binlog_index= 0; @@ -3901,9 +3885,6 @@ restart: { static char db[]= ""; thd->db= db; - if (ndb_binlog_running) - open_ndb_binlog_index(thd, &binlog_tables, &ndb_binlog_index); - thd->db= db; } do_ndbcluster_binlog_close_connection= BCCC_running; for ( ; !((ndbcluster_binlog_terminating || diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index 792dbf5d1c8..20783a97d4b 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -2608,7 +2608,7 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) for the same table. */ if (is_not_tmp_table) - mysql_mutex_lock(&table_share->mutex); + mysql_mutex_lock(&table_share->LOCK_ha_data); if (!table_share->ha_data) { HA_DATA_PARTITION *ha_data; @@ -2619,7 +2619,7 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) if (!ha_data) { if (is_not_tmp_table) - mysql_mutex_unlock(&table_share->mutex); + mysql_mutex_unlock(&table_share->LOCK_ha_data); goto err_handler; } DBUG_PRINT("info", ("table_share->ha_data 0x%p", ha_data)); @@ -2628,7 +2628,7 @@ int ha_partition::open(const char *name, int mode, uint test_if_locked) pthread_mutex_init(&ha_data->mutex, MY_MUTEX_INIT_FAST); } if (is_not_tmp_table) - mysql_mutex_unlock(&table_share->mutex); + mysql_mutex_unlock(&table_share->LOCK_ha_data); /* Some handlers update statistics as part of the open call. This will in some cases corrupt the statistics of the partition handler and thus @@ -5267,34 +5267,35 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info, } -/* - General function to prepare handler for certain behavior +/** + General function to prepare handler for certain behavior. - SYNOPSIS - extra() + @param[in] operation operation to execute operation Operation type for extra call - RETURN VALUE - >0 Error code - 0 Success + @return status + @retval 0 success + @retval >0 error code + + @detail - DESCRIPTION extra() is called whenever the server wishes to send a hint to the storage engine. The MyISAM engine implements the most hints. We divide the parameters into the following categories: - 1) Parameters used by most handlers - 2) Parameters used by some non-MyISAM handlers - 3) Parameters used only by MyISAM - 4) Parameters only used by temporary tables for query processing - 5) Parameters only used by MyISAM internally - 6) Parameters not used at all - 7) Parameters only used by federated tables for query processing - 8) Parameters only used by NDB + 1) Operations used by most handlers + 2) Operations used by some non-MyISAM handlers + 3) Operations used only by MyISAM + 4) Operations only used by temporary tables for query processing + 5) Operations only used by MyISAM internally + 6) Operations not used at all + 7) Operations only used by federated tables for query processing + 8) Operations only used by NDB + 9) Operations only used by MERGE The partition handler need to handle category 1), 2) and 3). - 1) Parameters used by most handlers + 1) Operations used by most handlers ----------------------------------- HA_EXTRA_RESET: This option is used by most handlers and it resets the handler state @@ -5333,7 +5334,7 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info, ensure disk based tables are flushed at end of query execution. Currently is never used. - 2) Parameters used by some non-MyISAM handlers + 2) Operations used by some non-MyISAM handlers ---------------------------------------------- HA_EXTRA_KEYREAD_PRESERVE_FIELDS: This is a strictly InnoDB feature that is more or less undocumented. @@ -5352,7 +5353,7 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info, SQL constructs. Not used by MyISAM. - 3) Parameters used only by MyISAM + 3) Operations used only by MyISAM --------------------------------- HA_EXTRA_NORMAL: Only used in MyISAM to reset quick mode, not implemented by any other @@ -5483,7 +5484,7 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info, Only used by MyISAM, called when altering table, closing tables to enforce a reopen of the table files. - 4) Parameters only used by temporary tables for query processing + 4) Operations only used by temporary tables for query processing ---------------------------------------------------------------- HA_EXTRA_RESET_STATE: Same as reset() except that buffers are not released. If there is @@ -5514,7 +5515,7 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info, tables used in query processing. Not handled by partition handler. - 5) Parameters only used by MyISAM internally + 5) Operations only used by MyISAM internally -------------------------------------------- HA_EXTRA_REINIT_CACHE: This call reinitializes the READ CACHE described above if there is one @@ -5549,19 +5550,19 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info, HA_EXTRA_CHANGE_KEY_TO_UNIQUE: Only used by MyISAM, never called. - 6) Parameters not used at all + 6) Operations not used at all ----------------------------- HA_EXTRA_KEY_CACHE: HA_EXTRA_NO_KEY_CACHE: This parameters are no longer used and could be removed. - 7) Parameters only used by federated tables for query processing + 7) Operations only used by federated tables for query processing ---------------------------------------------------------------- HA_EXTRA_INSERT_WITH_UPDATE: Inform handler that an "INSERT...ON DUPLICATE KEY UPDATE" will be executed. This condition is unset by HA_EXTRA_NO_IGNORE_DUP_KEY. - 8) Parameters only used by NDB + 8) Operations only used by NDB ------------------------------ HA_EXTRA_DELETE_CANNOT_BATCH: HA_EXTRA_UPDATE_CANNOT_BATCH: @@ -5569,6 +5570,14 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info, and should perform them immediately. This may be needed when table has AFTER DELETE/UPDATE triggers which access to subject table. These flags are reset by the handler::extra(HA_EXTRA_RESET) call. + + 9) Operations only used by MERGE + ------------------------------ + HA_EXTRA_ADD_CHILDREN_LIST: + HA_EXTRA_ATTACH_CHILDREN: + HA_EXTRA_IS_ATTACHED_CHILDREN: + HA_EXTRA_DETACH_CHILDREN: + Special actions for MERGE tables. Ignore. */ int ha_partition::extra(enum ha_extra_function operation) @@ -5661,13 +5670,22 @@ int ha_partition::extra(enum ha_extra_function operation) /* Category 7), used by federated handlers */ case HA_EXTRA_INSERT_WITH_UPDATE: DBUG_RETURN(loop_extra(operation)); - /* Category 8) Parameters only used by NDB */ + /* Category 8) Operations only used by NDB */ case HA_EXTRA_DELETE_CANNOT_BATCH: case HA_EXTRA_UPDATE_CANNOT_BATCH: { /* Currently only NDB use the *_CANNOT_BATCH */ break; } + /* Category 9) Operations only used by MERGE */ + case HA_EXTRA_ADD_CHILDREN_LIST: + case HA_EXTRA_ATTACH_CHILDREN: + case HA_EXTRA_IS_ATTACHED_CHILDREN: + case HA_EXTRA_DETACH_CHILDREN: + { + /* Special actions for MERGE tables. Ignore. */ + break; + } /* http://dev.mysql.com/doc/refman/5.1/en/partitioning-limitations.html says we no longer support logging to partitioned tables, so we fail diff --git a/sql/ha_partition.h b/sql/ha_partition.h index e2e6c674c5e..843c1d6892e 100644 --- a/sql/ha_partition.h +++ b/sql/ha_partition.h @@ -933,7 +933,7 @@ private: if(table_share->tmp_table == NO_TMP_TABLE) { auto_increment_lock= TRUE; - mysql_mutex_lock(&table_share->mutex); + mysql_mutex_lock(&table_share->LOCK_ha_data); } } virtual void unlock_auto_increment() @@ -946,7 +946,7 @@ private: */ if(auto_increment_lock && !auto_increment_safe_stmt_log_lock) { - mysql_mutex_unlock(&table_share->mutex); + mysql_mutex_unlock(&table_share->LOCK_ha_data); auto_increment_lock= FALSE; } } diff --git a/sql/handler.cc b/sql/handler.cc index fa53c112156..41e35a007cf 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -27,6 +27,7 @@ #include "rpl_handler.h" #include "rpl_filter.h" #include <myisampack.h> +#include "transaction.h" #include <errno.h> #include "probes_mysql.h" @@ -887,16 +888,16 @@ void ha_close_connection(THD* thd) a transaction in a given engine is read-write and will not involve the two-phase commit protocol! - At the end of a statement, server call - ha_autocommit_or_rollback() is invoked. This call in turn - invokes handlerton::prepare() for every involved engine. - Prepare is followed by a call to handlerton::commit_one_phase() - If a one-phase commit will suffice, handlerton::prepare() is not - invoked and the server only calls handlerton::commit_one_phase(). - At statement commit, the statement-related read-write engine - flag is propagated to the corresponding flag in the normal - transaction. When the commit is complete, the list of registered - engines is cleared. + At the end of a statement, server call trans_commit_stmt is + invoked. This call in turn invokes handlerton::prepare() + for every involved engine. Prepare is followed by a call + to handlerton::commit_one_phase() If a one-phase commit + will suffice, handlerton::prepare() is not invoked and + the server only calls handlerton::commit_one_phase(). + At statement commit, the statement-related read-write + engine flag is propagated to the corresponding flag in the + normal transaction. When the commit is complete, the list + of registered engines is cleared. Rollback is handled in a similar fashion. @@ -907,7 +908,7 @@ void ha_close_connection(THD* thd) do not "register" in thd->transaction lists, and thus do not modify the transaction state. Besides, each DDL in MySQL is prefixed with an implicit normal transaction commit - (a call to end_active_trans()), and thus leaves nothing + (a call to trans_commit_implicit()), and thus leaves nothing to modify. However, as it has been pointed out with CREATE TABLE .. SELECT, some DDL statements can start a *new* transaction. @@ -1162,7 +1163,7 @@ int ha_commit_trans(THD *thd, bool all) rw_trans= is_real_trans && (rw_ha_count > 0); if (rw_trans && - wait_if_global_read_lock(thd, 0, 0)) + thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, FALSE)) { ha_rollback_trans(thd, all); DBUG_RETURN(1); @@ -1221,7 +1222,7 @@ int ha_commit_trans(THD *thd, bool all) RUN_HOOK(transaction, after_commit, (thd, FALSE)); end: if (rw_trans) - start_waiting_global_read_lock(thd); + thd->global_read_lock.start_waiting_global_read_lock(thd); } /* Free resources and perform other cleanup even for 'empty' transactions. */ else if (is_real_trans) @@ -1369,47 +1370,6 @@ int ha_rollback_trans(THD *thd, bool all) DBUG_RETURN(error); } -/** - This is used to commit or rollback a single statement depending on - the value of error. - - @note - Note that if the autocommit is on, then the following call inside - InnoDB will commit or rollback the whole transaction (= the statement). The - autocommit mechanism built into InnoDB is based on counting locks, but if - the user has used LOCK TABLES then that mechanism does not know to do the - commit. -*/ -int ha_autocommit_or_rollback(THD *thd, int error) -{ - DBUG_ENTER("ha_autocommit_or_rollback"); - - if (thd->transaction.stmt.ha_list) - { - if (!error) - { - if (ha_commit_trans(thd, 0)) - error=1; - } - else - { - (void) ha_rollback_trans(thd, 0); - if (thd->transaction_rollback_request && !thd->in_sub_stmt) - (void) ha_rollback(thd); - } - - thd->variables.tx_isolation=thd->session_tx_isolation; - } - else - { - if (!error) - RUN_HOOK(transaction, after_commit, (thd, FALSE)); - else - RUN_HOOK(transaction, after_rollback, (thd, FALSE)); - } - DBUG_RETURN(error); -} - struct xahton_st { XID *xid; @@ -3001,20 +2961,13 @@ static bool update_frm_version(TABLE *table) path, O_RDWR|O_BINARY, MYF(MY_WME))) >= 0) { uchar version[4]; - char *key= table->s->table_cache_key.str; - uint key_length= table->s->table_cache_key.length; - TABLE *entry; - HASH_SEARCH_STATE state; int4store(version, MYSQL_VERSION_ID); if ((result= mysql_file_pwrite(file, (uchar*) version, 4, 51L, MYF_RW))) goto err; - for (entry=(TABLE*) my_hash_first(&open_cache,(uchar*) key,key_length, &state); - entry; - entry= (TABLE*) my_hash_next(&open_cache,(uchar*) key,key_length, &state)) - entry->s->mysql_version= MYSQL_VERSION_ID; + table->s->mysql_version= MYSQL_VERSION_ID; } err: if (file >= 0) @@ -3507,7 +3460,7 @@ int ha_enable_transaction(THD *thd, bool on) So, let's commit an open transaction (if any) now. */ if (!(error= ha_commit_trans(thd, 0))) - error= end_trans(thd, COMMIT); + error= trans_commit_implicit(thd); } DBUG_RETURN(error); } @@ -4498,9 +4451,7 @@ static bool check_table_binlog_row_based(THD *thd, TABLE *table) DESCRIPTION This function will generate and write table maps for all tables - that are locked by the thread 'thd'. Either manually locked - (stored in THD::locked_tables) and automatically locked (stored - in THD::lock) are considered. + that are locked by the thread 'thd'. RETURN VALUE 0 All OK @@ -4508,25 +4459,22 @@ static bool check_table_binlog_row_based(THD *thd, TABLE *table) SEE ALSO THD::lock - THD::locked_tables */ static int write_locked_table_maps(THD *thd) { DBUG_ENTER("write_locked_table_maps"); - DBUG_PRINT("enter", ("thd: 0x%lx thd->lock: 0x%lx thd->locked_tables: 0x%lx " + DBUG_PRINT("enter", ("thd: 0x%lx thd->lock: 0x%lx " "thd->extra_lock: 0x%lx", - (long) thd, (long) thd->lock, - (long) thd->locked_tables, (long) thd->extra_lock)); + (long) thd, (long) thd->lock, (long) thd->extra_lock)); DBUG_PRINT("debug", ("get_binlog_table_maps(): %d", thd->get_binlog_table_maps())); if (thd->get_binlog_table_maps() == 0) { - MYSQL_LOCK *locks[3]; + MYSQL_LOCK *locks[2]; locks[0]= thd->extra_lock; locks[1]= thd->lock; - locks[2]= thd->locked_tables; for (uint i= 0 ; i < sizeof(locks)/sizeof(*locks) ; ++i ) { MYSQL_LOCK const *const lock= locks[i]; diff --git a/sql/handler.h b/sql/handler.h index 711cc823f96..db824091500 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -2054,10 +2054,6 @@ extern TYPELIB tx_isolation_typelib; extern const char *myisam_stats_method_names[]; extern ulong total_ha, total_ha_2pc; - /* Wrapper functions */ -#define ha_commit(thd) (ha_commit_trans((thd), TRUE)) -#define ha_rollback(thd) (ha_rollback_trans((thd), TRUE)) - /* lookups */ handlerton *ha_default_handlerton(THD *thd); plugin_ref ha_resolve_by_name(THD *thd, const LEX_STRING *name); @@ -2133,13 +2129,12 @@ int ha_release_temporary_latches(THD *thd); int ha_start_consistent_snapshot(THD *thd); int ha_commit_or_rollback_by_xid(XID *xid, bool commit); int ha_commit_one_phase(THD *thd, bool all); +int ha_commit_trans(THD *thd, bool all); int ha_rollback_trans(THD *thd, bool all); int ha_prepare(THD *thd); int ha_recover(HASH *commit_list); /* transactions: these functions never call handlerton functions directly */ -int ha_commit_trans(THD *thd, bool all); -int ha_autocommit_or_rollback(THD *thd, int error); int ha_enable_transaction(THD *thd, bool on); /* savepoints */ diff --git a/sql/lex.h b/sql/lex.h index a12cf0c4b3e..91ef5287a1e 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -416,7 +416,7 @@ static SYMBOL symbols[] = { { "PREV", SYM(PREV_SYM)}, { "PRIMARY", SYM(PRIMARY_SYM)}, { "PRIVILEGES", SYM(PRIVILEGES)}, - { "PROCEDURE", SYM(PROCEDURE)}, + { "PROCEDURE", SYM(PROCEDURE_SYM)}, { "PROCESS" , SYM(PROCESS)}, { "PROCESSLIST", SYM(PROCESSLIST_SYM)}, { "PROFILE", SYM(PROFILE_SYM)}, diff --git a/sql/lock.cc b/sql/lock.cc index 0235c4ae881..08d854c4926 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -74,6 +74,7 @@ */ #include "mysql_priv.h" +#include "debug_sync.h" #include <hash.h> #include <assert.h> @@ -94,31 +95,6 @@ static int lock_external(THD *thd, TABLE **table,uint count); static int unlock_external(THD *thd, TABLE **table,uint count); static void print_lock_error(int error, const char *); -/* - Lock tables. - - SYNOPSIS - mysql_lock_tables() - thd The current thread. - tables An array of pointers to the tables to lock. - count The number of tables to lock. - flags Options: - MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK Ignore a global read lock - MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY Ignore SET GLOBAL READ_ONLY - MYSQL_LOCK_IGNORE_FLUSH Ignore a flush tables. - MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN Instead of reopening altered - or dropped tables by itself, - mysql_lock_tables() should - notify upper level and rely - on caller doing this. - need_reopen Out parameter, TRUE if some tables were altered - or deleted and should be reopened by caller. - - RETURN - A lock structure pointer on success. - NULL on error or if some tables should be reopen. -*/ - /* Map the return value of thr_lock to an error from errmsg.txt */ static int thr_lock_errno_to_mysql[]= { 0, 1, ER_LOCK_WAIT_TIMEOUT, ER_LOCK_DEADLOCK }; @@ -193,9 +169,8 @@ int mysql_lock_tables_check(THD *thd, TABLE **tables, uint count, uint flags) DBUG_RETURN(0); } - /** - Reset lock type in lock data and free. + Reset lock type in lock data @param mysql_lock Lock structures to reset. @@ -247,6 +222,28 @@ static void reset_lock_data_and_free(MYSQL_LOCK **mysql_lock) } +/** + Lock tables. + + @param thd The current thread. + @param tables An array of pointers to the tables to lock. + @param count The number of tables to lock. + @param flags Options: + MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK Ignore a global read lock + MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY Ignore SET GLOBAL READ_ONLY + MYSQL_LOCK_IGNORE_FLUSH Ignore a flush tables. + @param need_reopen Out parameter, TRUE if some tables were altered + or deleted and should be reopened by caller. + + @note Caller of this function should always be ready to handle request to + reopen table unless there are external invariants which guarantee + that such thing won't be needed (for example we are obtaining lock + on table on which we already have exclusive metadata lock). + + @retval A lock structure pointer on success. + @retval NULL on error or if some tables should be reopen. +*/ + MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags, bool *need_reopen) { @@ -274,7 +271,7 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, Someone has issued LOCK ALL TABLES FOR READ and we want a write lock Wait until the lock is gone */ - if (wait_if_global_read_lock(thd, 1, 1)) + if (thd->global_read_lock.wait_if_global_read_lock(thd, 1, 1)) { /* Clear the lock type of all lock data to avoid reusage. */ reset_lock_data_and_free(&sql_lock); @@ -313,7 +310,6 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, break; } DBUG_PRINT("info", ("thd->proc_info %s", thd->proc_info)); - thd_proc_info(thd, "Locked"); /* Copy the lock data array. thr_multi_lock() reorders its contens. */ memcpy(sql_lock->locks + sql_lock->lock_count, sql_lock->locks, sql_lock->lock_count * sizeof(*sql_lock->locks)); @@ -330,24 +326,21 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, my_error(rc, MYF(0)); break; } - else if (rc == 1) /* aborted */ - { - thd->some_tables_deleted=1; // Try again - sql_lock->lock_count= 0; // Locks are already freed - // Fall through: unlock, reset lock data, free and retry - } - else if (!thd->some_tables_deleted || (flags & MYSQL_LOCK_IGNORE_FLUSH)) + else if (rc == 1) /* aborted or killed */ { /* - Thread was killed or lock aborted. Let upper level close all - used tables and retry or give error. + reset_lock_data is required here. If thr_multi_lock fails it + resets lock type for tables, which were locked before (and + including) one that caused error. Lock type for other tables + preserved. */ - break; + reset_lock_data(sql_lock); + sql_lock->lock_count= 0; // Locks are already freed + // Fall through: unlock, reset lock data, free and retry } - else if (!thd->open_tables) + else { - // Only using temporary tables, no need to unlock - thd->some_tables_deleted=0; + /* Success */ break; } thd_proc_info(thd, 0); @@ -366,13 +359,9 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, */ reset_lock_data_and_free(&sql_lock); retry: - if (flags & MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN) - { - *need_reopen= TRUE; - break; - } - if (wait_for_tables(thd)) - break; // Couldn't open tables + /* Let upper level close all used tables and retry or give error. */ + *need_reopen= TRUE; + break; } thd_proc_info(thd, 0); if (thd->killed) @@ -518,28 +507,15 @@ void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock) /** Try to find the table in the list of locked tables. In case of success, unlock the table and remove it from this list. - - @note This function has a legacy side effect: the table is - unlocked even if it is not found in the locked list. - It's not clear if this side effect is intentional or still - desirable. It might lead to unmatched calls to - unlock_external(). Moreover, a discrepancy can be left - unnoticed by the storage engine, because in - unlock_external() we call handler::external_lock(F_UNLCK) only - if table->current_lock is not F_UNLCK. + If a table has more than one lock instance, removes them all. @param thd thread context @param locked list of locked tables @param table the table to unlock - @param always_unlock specify explicitly if the legacy side - effect is desired. */ -void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table, - bool always_unlock) +void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table) { - if (always_unlock == TRUE) - mysql_unlock_some_tables(thd, &table, /* table count */ 1); if (locked) { reg1 uint i; @@ -553,9 +529,8 @@ void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table, DBUG_ASSERT(table->lock_position == i); - /* Unlock if not yet unlocked */ - if (always_unlock == FALSE) - mysql_unlock_some_tables(thd, &table, /* table count */ 1); + /* Unlock the table. */ + mysql_unlock_some_tables(thd, &table, /* table count */ 1); /* Decrement table_count in advance, making below expressions easier */ old_tables= --locked->table_count; @@ -707,6 +682,8 @@ MYSQL_LOCK *mysql_lock_merge(MYSQL_LOCK *a,MYSQL_LOCK *b) /* Delete old, not needed locks */ my_free((uchar*) a,MYF(0)); my_free((uchar*) b,MYF(0)); + + thr_lock_merge_status(sql_lock->locks, sql_lock->lock_count); DBUG_RETURN(sql_lock); } @@ -760,7 +737,7 @@ TABLE_LIST *mysql_lock_have_duplicate(THD *thd, TABLE_LIST *needle, goto end; /* Get command lock or LOCK TABLES lock. Maybe empty for INSERT DELAYED. */ - if (! (mylock= thd->lock ? thd->lock : thd->locked_tables)) + if (! (mylock= thd->lock)) goto end; /* If we have less than two tables, we cannot have duplicates. */ @@ -955,362 +932,115 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, *****************************************************************************/ /** - Lock and wait for the named lock. + Obtain exclusive metadata locks on the list of tables. - @param thd Thread handler - @param table_list Lock first table in this list + @param thd Thread handle + @param table_list List of tables to lock + @note This function assumes that no metadata locks were acquired + before calling it. Also it cannot be called while holding + LOCK_open mutex. Both these invariants are enforced by asserts + in MDL_context::acquire_locks(). - @note - Works together with global read lock. - - @retval - 0 ok - @retval - 1 error -*/ - -int lock_and_wait_for_table_name(THD *thd, TABLE_LIST *table_list) -{ - int lock_retcode; - int error= -1; - DBUG_ENTER("lock_and_wait_for_table_name"); - - if (wait_if_global_read_lock(thd, 0, 1)) - DBUG_RETURN(1); - mysql_mutex_lock(&LOCK_open); - if ((lock_retcode = lock_table_name(thd, table_list, TRUE)) < 0) - goto end; - if (lock_retcode && wait_for_locked_table_names(thd, table_list)) - { - unlock_table_name(thd, table_list); - goto end; - } - error=0; - -end: - mysql_mutex_unlock(&LOCK_open); - start_waiting_global_read_lock(thd); - DBUG_RETURN(error); -} - - -/** - Put a not open table with an old refresh version in the table cache. - - @param thd Thread handler - @param table_list Lock first table in this list - @param check_in_use Do we need to check if table already in use by us - - @note - One must have a lock on LOCK_open! - - @warning - If you are going to update the table, you should use - lock_and_wait_for_table_name instead of this function as this works - together with 'FLUSH TABLES WITH READ LOCK' - - @note - This will force any other threads that uses the table to release it - as soon as possible. - - @return - < 0 error - @return - == 0 table locked - @return - > 0 table locked, but someone is using it -*/ - -int lock_table_name(THD *thd, TABLE_LIST *table_list, bool check_in_use) -{ - TABLE *table; - char key[MAX_DBKEY_LENGTH]; - char *db= table_list->db; - uint key_length; - bool found_locked_table= FALSE; - HASH_SEARCH_STATE state; - DBUG_ENTER("lock_table_name"); - DBUG_PRINT("enter",("db: %s name: %s", db, table_list->table_name)); - - key_length= create_table_def_key(thd, key, table_list, 0); - - if (check_in_use) - { - /* Only insert the table if we haven't insert it already */ - for (table=(TABLE*) my_hash_first(&open_cache, (uchar*)key, - key_length, &state); - table ; - table = (TABLE*) my_hash_next(&open_cache,(uchar*) key, - key_length, &state)) - { - if (table->reginfo.lock_type < TL_WRITE) - { - if (table->in_use == thd) - found_locked_table= TRUE; - continue; - } - - if (table->in_use == thd) - { - DBUG_PRINT("info", ("Table is in use")); - table->s->version= 0; // Ensure no one can use this - table->locked_by_name= 1; - DBUG_RETURN(0); - } - } - } - - if (thd->locked_tables && thd->locked_tables->table_count && - ! find_temporary_table(thd, table_list->db, table_list->table_name)) - { - if (found_locked_table) - my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_list->alias); - else - my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_list->alias); - - DBUG_RETURN(-1); - } - - if (!(table= table_cache_insert_placeholder(thd, key, key_length))) - DBUG_RETURN(-1); - - table_list->table=table; - - /* Return 1 if table is in use */ - DBUG_RETURN(test(remove_table_from_cache(thd, db, table_list->table_name, - check_in_use ? RTFC_NO_FLAG : RTFC_WAIT_OTHER_THREAD_FLAG))); -} - - -void unlock_table_name(THD *thd, TABLE_LIST *table_list) -{ - if (table_list->table) - { - my_hash_delete(&open_cache, (uchar*) table_list->table); - broadcast_refresh(); - } -} - - -static bool locked_named_table(THD *thd, TABLE_LIST *table_list) -{ - for (; table_list ; table_list=table_list->next_local) - { - TABLE *table= table_list->table; - if (table) - { - TABLE *save_next= table->next; - bool result; - table->next= 0; - result= table_is_used(table_list->table, 0); - table->next= save_next; - if (result) - return 1; - } - } - return 0; // All tables are locked -} - - -bool wait_for_locked_table_names(THD *thd, TABLE_LIST *table_list) -{ - bool result=0; - DBUG_ENTER("wait_for_locked_table_names"); - - mysql_mutex_assert_owner(&LOCK_open); - - while (locked_named_table(thd,table_list)) - { - if (thd->killed) - { - result=1; - break; - } - wait_for_condition(thd, &LOCK_open, &COND_refresh); - mysql_mutex_lock(&LOCK_open); - } - DBUG_RETURN(result); -} - - -/** - Lock all tables in list with a name lock. - - REQUIREMENTS - - One must have a lock on LOCK_open when calling this - - @param thd Thread handle - @param table_list Names of tables to lock - - @note - If you are just locking one table, you should use - lock_and_wait_for_table_name(). - - @retval - 0 ok - @retval - 1 Fatal error (end of memory ?) + @retval FALSE Success. + @retval TRUE Failure (OOM or thread was killed). */ bool lock_table_names(THD *thd, TABLE_LIST *table_list) { - bool got_all_locks=1; + MDL_request_list mdl_requests; + MDL_request global_request; TABLE_LIST *lock_table; + global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); + for (lock_table= table_list; lock_table; lock_table= lock_table->next_local) { - int got_lock; - if ((got_lock=lock_table_name(thd,lock_table, TRUE)) < 0) - goto end; // Fatal error - if (got_lock) - got_all_locks=0; // Someone is using table + lock_table->mdl_request.init(MDL_key::TABLE, + lock_table->db, lock_table->table_name, + MDL_EXCLUSIVE); + mdl_requests.push_front(&lock_table->mdl_request); } - /* If some table was in use, wait until we got the lock */ - if (!got_all_locks && wait_for_locked_table_names(thd, table_list)) - goto end; - return 0; - -end: - unlock_table_names(thd, table_list, lock_table); - return 1; -} - - -/** - Unlock all tables in list with a name lock. - - @param thd Thread handle. - @param table_list Names of tables to lock. + mdl_requests.push_front(&global_request); - @note - This function needs to be protected by LOCK_open. If we're - under LOCK TABLES, this function does not work as advertised. Namely, - it does not exclude other threads from using this table and does not - put an exclusive name lock on this table into the table cache. + if (thd->mdl_context.acquire_locks(&mdl_requests)) + return 1; - @see lock_table_names - @see unlock_table_names - - @retval TRUE An error occured. - @retval FALSE Name lock successfully acquired. -*/ - -bool lock_table_names_exclusively(THD *thd, TABLE_LIST *table_list) -{ - if (lock_table_names(thd, table_list)) - return TRUE; - - /* - Upgrade the table name locks from semi-exclusive to exclusive locks. - */ - for (TABLE_LIST *table= table_list; table; table= table->next_global) - { - if (table->table) - table->table->open_placeholder= 1; - } - return FALSE; + return 0; } /** - Test is 'table' is protected by an exclusive name lock. + Release all metadata locks previously obtained by lock_table_names(). - @param[in] thd The current thread handler - @param[in] table_list Table container containing the single table to be - tested + @param thd Thread handle. - @note Needs to be protected by LOCK_open mutex. - - @return Error status code - @retval TRUE Table is protected - @retval FALSE Table is not protected + @note Cannot be called while holding LOCK_open mutex. */ -bool -is_table_name_exclusively_locked_by_this_thread(THD *thd, - TABLE_LIST *table_list) +void unlock_table_names(THD *thd) { - char key[MAX_DBKEY_LENGTH]; - uint key_length; - - key_length= create_table_def_key(thd, key, table_list, 0); - - return is_table_name_exclusively_locked_by_this_thread(thd, (uchar *)key, - key_length); + DBUG_ENTER("unlock_table_names"); + thd->mdl_context.release_transactional_locks(); + DBUG_VOID_RETURN; } /** - Test is 'table key' is protected by an exclusive name lock. - - @param[in] thd The current thread handler. - @param[in] key - @param[in] key_length - - @note Needs to be protected by LOCK_open mutex - - @retval TRUE Table is protected - @retval FALSE Table is not protected - */ + Obtain an exclusive metadata lock on the stored routine name. + + @param thd Thread handle. + @param is_function Stored routine type (only functions or procedures + are name-locked. + @param db The schema the routine belongs to. + @param name Routine name. + + This function assumes that no metadata locks were acquired + before calling it. Additionally, it cannot be called while + holding LOCK_open mutex. Both these invariants are enforced by + asserts in MDL_context::acquire_locks(). + To avoid deadlocks, we do not try to obtain exclusive metadata + locks in LOCK TABLES mode, since in this mode there may be + other metadata locks already taken by the current connection, + and we must not wait for MDL locks while holding locks. + + @retval FALSE Success. + @retval TRUE Failure: we're in LOCK TABLES mode, or out of memory, + or this connection was killed. +*/ -bool -is_table_name_exclusively_locked_by_this_thread(THD *thd, uchar *key, - int key_length) +bool lock_routine_name(THD *thd, bool is_function, + const char *db, const char *name) { - HASH_SEARCH_STATE state; - TABLE *table; - - for (table= (TABLE*) my_hash_first(&open_cache, key, - key_length, &state); - table ; - table= (TABLE*) my_hash_next(&open_cache, key, - key_length, &state)) + MDL_key::enum_mdl_namespace mdl_type= (is_function ? + MDL_key::FUNCTION : + MDL_key::PROCEDURE); + MDL_request_list mdl_requests; + MDL_request global_request; + MDL_request mdl_request; + + if (thd->locked_tables_mode) { - if (table->in_use == thd && - table->open_placeholder == 1 && - table->s->version == 0) - return TRUE; + my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, + ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); + return TRUE; } - return FALSE; -} + DBUG_ASSERT(name); + DEBUG_SYNC(thd, "before_wait_locked_pname"); -/** - Unlock all tables in list with a name lock. + global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); + mdl_request.init(mdl_type, db, name, MDL_EXCLUSIVE); - @param - thd Thread handle - @param - table_list Names of tables to unlock - @param - last_table Don't unlock any tables after this one. - (default 0, which will unlock all tables) + mdl_requests.push_front(&mdl_request); + mdl_requests.push_front(&global_request); - @note - One must have a lock on LOCK_open when calling this. - - @note - This function will broadcast refresh signals to inform other threads - that the name locks are removed. - - @retval - 0 ok - @retval - 1 Fatal error (end of memory ?) -*/ + if (thd->mdl_context.acquire_locks(&mdl_requests)) + return TRUE; -void unlock_table_names(THD *thd, TABLE_LIST *table_list, - TABLE_LIST *last_table) -{ - DBUG_ENTER("unlock_table_names"); - for (TABLE_LIST *table= table_list; - table != last_table; - table= table->next_local) - unlock_table_name(thd,table); - broadcast_refresh(); - DBUG_VOID_RETURN; + DEBUG_SYNC(thd, "after_wait_locked_pname"); + return FALSE; } @@ -1426,19 +1156,54 @@ volatile uint global_read_lock_blocks_commit=0; static volatile uint protect_against_global_read_lock=0; static volatile uint waiting_for_read_lock=0; -#define GOT_GLOBAL_READ_LOCK 1 -#define MADE_GLOBAL_READ_LOCK_BLOCK_COMMIT 2 +/** + Take global read lock, wait if there is protection against lock. -bool lock_global_read_lock(THD *thd) + If the global read lock is already taken by this thread, then nothing is done. + + See also "Handling of global read locks" above. + + @param thd Reference to thread. + + @retval False Success, global read lock set, commits are NOT blocked. + @retval True Failure, thread was killed. +*/ + +bool Global_read_lock::lock_global_read_lock(THD *thd) { DBUG_ENTER("lock_global_read_lock"); - if (!thd->global_read_lock) + if (!m_state) { + MDL_request mdl_request; const char *old_message; - mysql_mutex_lock(&LOCK_global_read_lock); + const char *new_message= "Waiting to get readlock"; + (void) mysql_mutex_lock(&LOCK_global_read_lock); + +#if defined(ENABLED_DEBUG_SYNC) + /* + The below sync point fires if we have to wait for + protect_against_global_read_lock. + + WARNING: Beware to use WAIT_FOR with this sync point. We hold + LOCK_global_read_lock here. + + Call the sync point before calling enter_cond() as it does use + enter_cond() and exit_cond() itself if a WAIT_FOR action is + executed in spite of the above warning. + + Pre-set proc_info so that it is available immediately after the + sync point sends a SIGNAL. This makes tests more reliable. + */ + if (protect_against_global_read_lock) + { + thd_proc_info(thd, new_message); + DEBUG_SYNC(thd, "wait_lock_global_read_lock"); + } +#endif /* defined(ENABLED_DEBUG_SYNC) */ + old_message=thd->enter_cond(&COND_global_read_lock, &LOCK_global_read_lock, - "Waiting to get readlock"); + new_message); DBUG_PRINT("info", ("waiting_for: %d protect_against: %d", waiting_for_read_lock, protect_against_global_read_lock)); @@ -1452,9 +1217,43 @@ bool lock_global_read_lock(THD *thd) thd->exit_cond(old_message); DBUG_RETURN(1); } - thd->global_read_lock= GOT_GLOBAL_READ_LOCK; + m_state= GRL_ACQUIRED; global_read_lock++; thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock + /* + When we perform FLUSH TABLES or ALTER TABLE under LOCK TABLES, + tables being reopened are protected only by meta-data locks at + some point. To avoid sneaking in with our global read lock at + this moment we have to take global shared meta data lock. + + TODO: We should change this code to acquire global shared metadata + lock before acquiring global read lock. But in order to do + this we have to get rid of all those places in which + wait_if_global_read_lock() is called before acquiring + metadata locks first. Also long-term we should get rid of + redundancy between metadata locks, global read lock and DDL + blocker (see WL#4399 and WL#4400). + */ + + DBUG_ASSERT(! thd->mdl_context.is_lock_owner(MDL_key::GLOBAL, "", "", + MDL_SHARED)); + mdl_request.init(MDL_key::GLOBAL, "", "", MDL_SHARED); + + if (thd->mdl_context.acquire_lock(&mdl_request)) + { + /* Our thread was killed -- return back to initial state. */ + mysql_mutex_lock(&LOCK_global_read_lock); + if (!(--global_read_lock)) + { + DBUG_PRINT("signal", ("Broadcasting COND_global_read_lock")); + mysql_cond_broadcast(&COND_global_read_lock); + } + mysql_mutex_unlock(&LOCK_global_read_lock); + m_state= GRL_NONE; + DBUG_RETURN(1); + } + thd->mdl_context.move_ticket_after_trans_sentinel(mdl_request.ticket); + m_mdl_global_shared_lock= mdl_request.ticket; } /* We DON'T set global_read_lock_blocks_commit now, it will be set after @@ -1468,7 +1267,17 @@ bool lock_global_read_lock(THD *thd) } -void unlock_global_read_lock(THD *thd) +/** + Unlock global read lock. + + Commits may or may not be blocked when this function is called. + + See also "Handling of global read locks" above. + + @param thd Reference to thread. +*/ + +void Global_read_lock::unlock_global_read_lock(THD *thd) { uint tmp; DBUG_ENTER("unlock_global_read_lock"); @@ -1476,9 +1285,14 @@ void unlock_global_read_lock(THD *thd) ("global_read_lock: %u global_read_lock_blocks_commit: %u", global_read_lock, global_read_lock_blocks_commit)); + DBUG_ASSERT(m_mdl_global_shared_lock && m_state); + + thd->mdl_context.release_lock(m_mdl_global_shared_lock); + m_mdl_global_shared_lock= NULL; + mysql_mutex_lock(&LOCK_global_read_lock); tmp= --global_read_lock; - if (thd->global_read_lock == MADE_GLOBAL_READ_LOCK_BLOCK_COMMIT) + if (m_state == GRL_ACQUIRED_AND_BLOCKS_COMMIT) --global_read_lock_blocks_commit; mysql_mutex_unlock(&LOCK_global_read_lock); /* Send the signal outside the mutex to avoid a context switch */ @@ -1487,23 +1301,53 @@ void unlock_global_read_lock(THD *thd) DBUG_PRINT("signal", ("Broadcasting COND_global_read_lock")); mysql_cond_broadcast(&COND_global_read_lock); } - thd->global_read_lock= 0; + m_state= GRL_NONE; DBUG_VOID_RETURN; } +/** + Wait if the global read lock is set, and optionally seek protection against + global read lock. + + See also "Handling of global read locks" above. + + @param thd Reference to thread. + @param abort_on_refresh If True, abort waiting if a refresh occurs, + do NOT seek protection against GRL. + If False, wait until the GRL is released and seek + protection against GRL. + @param is_not_commit If False, called from a commit operation, + wait only if commit blocking is also enabled. + + @retval False Success, protection against global read lock is set + (if !abort_on_refresh) + @retval True Failure, wait was aborted or thread was killed. +*/ + #define must_wait (global_read_lock && \ (is_not_commit || \ global_read_lock_blocks_commit)) -bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh, - bool is_not_commit) +bool Global_read_lock:: +wait_if_global_read_lock(THD *thd, bool abort_on_refresh, + bool is_not_commit) { const char *UNINIT_VAR(old_message); bool result= 0, need_exit_cond; DBUG_ENTER("wait_if_global_read_lock"); /* + If we already have protection against global read lock, + just increment the counter. + */ + if (unlikely(m_protection_count > 0)) + { + if (!abort_on_refresh) + m_protection_count++; + DBUG_RETURN(FALSE); + } + /* Assert that we do not own LOCK_open. If we would own it, other threads could not close their tables. This would make a pretty deadlock. @@ -1513,7 +1357,7 @@ bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh, mysql_mutex_lock(&LOCK_global_read_lock); if ((need_exit_cond= must_wait)) { - if (thd->global_read_lock) // This thread had the read locks + if (m_state) // This thread had the read locks { if (is_not_commit) my_message(ER_CANT_UPDATE_WITH_READLOCK, @@ -1539,7 +1383,12 @@ bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh, result=1; } if (!abort_on_refresh && !result) + { + m_protection_count++; protect_against_global_read_lock++; + DBUG_PRINT("sql_lock", ("protect_against_global_read_lock incr: %u", + protect_against_global_read_lock)); + } /* The following is only true in case of a global read locks (which is rare) and if old_message is set @@ -1552,11 +1401,32 @@ bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh, } -void start_waiting_global_read_lock(THD *thd) +/** + Release protection against global read lock and restart + global read lock waiters. + + Should only be called if we have protection against global read lock. + + See also "Handling of global read locks" above. + + @param thd Reference to thread. +*/ + +void Global_read_lock::start_waiting_global_read_lock(THD *thd) { bool tmp; DBUG_ENTER("start_waiting_global_read_lock"); - if (unlikely(thd->global_read_lock)) + /* + Ignore request if we do not have protection against global read lock. + (Note that this is a violation of the interface contract, hence the assert). + */ + DBUG_ASSERT(m_protection_count > 0); + if (unlikely(m_protection_count == 0)) + DBUG_VOID_RETURN; + /* Decrement local read lock protection counter, return if we still have it */ + if (unlikely(--m_protection_count > 0)) + DBUG_VOID_RETURN; + if (unlikely(m_state)) DBUG_VOID_RETURN; mysql_mutex_lock(&LOCK_global_read_lock); DBUG_ASSERT(protect_against_global_read_lock); @@ -1569,7 +1439,22 @@ void start_waiting_global_read_lock(THD *thd) } -bool make_global_read_lock_block_commit(THD *thd) +/** + Make global read lock also block commits. + + The scenario is: + - This thread has the global read lock. + - Global read lock blocking of commits is not set. + + See also "Handling of global read locks" above. + + @param thd Reference to thread. + + @retval False Success, global read lock set, commits are blocked. + @retval True Failure, thread was killed. +*/ + +bool Global_read_lock::make_global_read_lock_block_commit(THD *thd) { bool error; const char *old_message; @@ -1578,7 +1463,7 @@ bool make_global_read_lock_block_commit(THD *thd) If we didn't succeed lock_global_read_lock(), or if we already suceeded make_global_read_lock_block_commit(), do nothing. */ - if (thd->global_read_lock != GOT_GLOBAL_READ_LOCK) + if (m_state != GRL_ACQUIRED) DBUG_RETURN(0); mysql_mutex_lock(&LOCK_global_read_lock); /* increment this BEFORE waiting on cond (otherwise race cond) */ @@ -1595,7 +1480,7 @@ bool make_global_read_lock_block_commit(THD *thd) if ((error= test(thd->killed))) global_read_lock_blocks_commit--; // undo what we did else - thd->global_read_lock= MADE_GLOBAL_READ_LOCK_BLOCK_COMMIT; + m_state= GRL_ACQUIRED_AND_BLOCKS_COMMIT; thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock DBUG_RETURN(error); } diff --git a/sql/log.cc b/sql/log.cc index 3680398f068..a63b4638b95 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -414,7 +414,7 @@ bool Log_to_csv_event_handler:: bool need_rnd_end= FALSE; uint field_index; Silence_log_table_errors error_handler; - Open_tables_state open_tables_backup; + Open_tables_backup open_tables_backup; ulonglong save_thd_options; bool save_time_zone_used; @@ -427,14 +427,10 @@ bool Log_to_csv_event_handler:: save_thd_options= thd->variables.option_bits; thd->variables.option_bits&= ~OPTION_BIN_LOG; - bzero(& table_list, sizeof(TABLE_LIST)); - table_list.alias= table_list.table_name= GENERAL_LOG_NAME.str; - table_list.table_name_length= GENERAL_LOG_NAME.length; - - table_list.lock_type= TL_WRITE_CONCURRENT_INSERT; - - table_list.db= MYSQL_SCHEMA_NAME.str; - table_list.db_length= MYSQL_SCHEMA_NAME.length; + table_list.init_one_table(MYSQL_SCHEMA_NAME.str, MYSQL_SCHEMA_NAME.length, + GENERAL_LOG_NAME.str, GENERAL_LOG_NAME.length, + GENERAL_LOG_NAME.str, + TL_WRITE_CONCURRENT_INSERT); /* 1) open_log_table generates an error of the @@ -579,7 +575,7 @@ bool Log_to_csv_event_handler:: bool need_close= FALSE; bool need_rnd_end= FALSE; Silence_log_table_errors error_handler; - Open_tables_state open_tables_backup; + Open_tables_backup open_tables_backup; CHARSET_INFO *client_cs= thd->variables.character_set_client; bool save_time_zone_used; DBUG_ENTER("Log_to_csv_event_handler::log_slow"); @@ -591,14 +587,10 @@ bool Log_to_csv_event_handler:: */ save_time_zone_used= thd->time_zone_used; - bzero(& table_list, sizeof(TABLE_LIST)); - table_list.alias= table_list.table_name= SLOW_LOG_NAME.str; - table_list.table_name_length= SLOW_LOG_NAME.length; - - table_list.lock_type= TL_WRITE_CONCURRENT_INSERT; - - table_list.db= MYSQL_SCHEMA_NAME.str; - table_list.db_length= MYSQL_SCHEMA_NAME.length; + table_list.init_one_table(MYSQL_SCHEMA_NAME.str, MYSQL_SCHEMA_NAME.length, + SLOW_LOG_NAME.str, SLOW_LOG_NAME.length, + SLOW_LOG_NAME.str, + TL_WRITE_CONCURRENT_INSERT); if (!(table= open_log_table(thd, &table_list, &open_tables_backup))) goto err; @@ -735,29 +727,25 @@ int Log_to_csv_event_handler:: { TABLE_LIST table_list; TABLE *table; + LEX_STRING *UNINIT_VAR(log_name); int result; - Open_tables_state open_tables_backup; + Open_tables_backup open_tables_backup; DBUG_ENTER("Log_to_csv_event_handler::activate_log"); - bzero(& table_list, sizeof(TABLE_LIST)); - if (log_table_type == QUERY_LOG_GENERAL) { - table_list.alias= table_list.table_name= GENERAL_LOG_NAME.str; - table_list.table_name_length= GENERAL_LOG_NAME.length; + log_name= &GENERAL_LOG_NAME; } else { DBUG_ASSERT(log_table_type == QUERY_LOG_SLOW); - table_list.alias= table_list.table_name= SLOW_LOG_NAME.str; - table_list.table_name_length= SLOW_LOG_NAME.length; - } - table_list.lock_type= TL_WRITE_CONCURRENT_INSERT; - - table_list.db= MYSQL_SCHEMA_NAME.str; - table_list.db_length= MYSQL_SCHEMA_NAME.length; + log_name= &SLOW_LOG_NAME; + } + table_list.init_one_table(MYSQL_SCHEMA_NAME.str, MYSQL_SCHEMA_NAME.length, + log_name->str, log_name->length, log_name->str, + TL_WRITE_CONCURRENT_INSERT); table= open_log_table(thd, &table_list, &open_tables_backup); if (table) @@ -1504,10 +1492,10 @@ binlog_end_trans(THD *thd, binlog_trx_data *trx_data, transaction cache to remove the statement. */ thd->binlog_remove_pending_rows_event(TRUE); - if (all || !(thd->variables.option_bits & (OPTION_BEGIN | OPTION_NOT_AUTOCOMMIT))) + if (all || !thd->in_multi_stmt_transaction()) { if (trx_data->has_incident()) - error= mysql_bin_log.write_incident(thd, TRUE); + mysql_bin_log.write_incident(thd, TRUE); trx_data->reset(); } else // ...statement @@ -1572,8 +1560,7 @@ static int binlog_commit(handlerton *hton, THD *thd, bool all) Otherwise, we accumulate the statement */ - ulonglong const in_transaction= - thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN); + bool const in_transaction= thd->in_multi_stmt_transaction(); DBUG_PRINT("debug", ("all: %d, empty: %s, in_transaction: %s, all.modified_non_trans_table: %s, stmt.modified_non_trans_table: %s", all, @@ -4125,7 +4112,7 @@ THD::binlog_start_trans_and_stmt() trx_data->before_stmt_pos == MY_OFF_T_UNDEF) { this->binlog_set_stmt_begin(); - if (variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) + if (in_multi_stmt_transaction()) trans_register_ha(this, TRUE, binlog_hton); trans_register_ha(this, FALSE, binlog_hton); /* @@ -4366,7 +4353,7 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info) this will close all tables on the slave. */ bool const end_stmt= - thd->prelocked_mode && thd->lex->requires_prelocking(); + thd->locked_tables_mode && thd->lex->requires_prelocking(); if (thd->binlog_flush_pending_rows_event(end_stmt)) DBUG_RETURN(error); @@ -4822,7 +4809,7 @@ bool MYSQL_BIN_LOG::write_incident(THD *thd, bool lock) Incident_log_event ev(thd, incident, write_error_msg); if (lock) mysql_mutex_lock(&LOCK_log); - error= ev.write(&log_file); + ev.write(&log_file); if (lock) { if (!error && !(error= flush_and_sync(0))) diff --git a/sql/log_event.cc b/sql/log_event.cc index 985d2110c9b..740b2d7e886 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -31,6 +31,7 @@ #include "rpl_filter.h" #include "rpl_utility.h" #include "rpl_record.h" +#include "transaction.h" #include <my_dir.h> #endif /* MYSQL_CLIENT */ @@ -3032,7 +3033,7 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli, } else { - const_cast<Relay_log_info*>(rli)->clear_tables_to_lock(); + const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); } /* @@ -3224,7 +3225,7 @@ Default database: '%s'. Query: '%s'", them back here. */ if (expected_error && expected_error == actual_error) - ha_autocommit_or_rollback(thd, TRUE); + trans_rollback_stmt(thd); } /* If we expected a non-zero error code and get nothing and, it is a concurrency @@ -3233,7 +3234,8 @@ Default database: '%s'. Query: '%s'", else if (expected_error && !actual_error && (concurrency_error_code(expected_error) || ignored_error_code(expected_error))) - ha_autocommit_or_rollback(thd, TRUE); + trans_rollback_stmt(thd); + /* Other cases: mostly we expected no error and get one. */ @@ -5307,10 +5309,16 @@ void Xid_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) #if defined(HAVE_REPLICATION) && !defined(MYSQL_CLIENT) int Xid_log_event::do_apply_event(Relay_log_info const *rli) { + bool res; /* For a slave Xid_log_event is COMMIT */ general_log_print(thd, COM_QUERY, "COMMIT /* implicit, from Xid_log_event */"); - return end_trans(thd, COMMIT); + if (!(res= trans_commit(thd))) + { + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + } + return res; } Log_event::enum_skip_reason @@ -5869,7 +5877,7 @@ Slave_log_event::Slave_log_event(const char* buf, uint event_len) int Slave_log_event::do_apply_event(Relay_log_info const *rli) { if (mysql_bin_log.is_open()) - return mysql_bin_log.write(this); + mysql_bin_log.write(this); return 0; } #endif /* !MYSQL_CLIENT */ @@ -7246,8 +7254,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) */ DBUG_ASSERT(get_flags(STMT_END_F)); - const_cast<Relay_log_info*>(rli)->clear_tables_to_lock(); - close_thread_tables(thd); + const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); thd->clear_error(); DBUG_RETURN(0); } @@ -7330,7 +7337,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) "unexpected success or fatal error")); thd->is_slave_error= 1; } - const_cast<Relay_log_info*>(rli)->clear_tables_to_lock(); + const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); DBUG_RETURN(actual_error); } @@ -7355,7 +7362,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) mysql_unlock_tables(thd, thd->lock); thd->lock= 0; thd->is_slave_error= 1; - const_cast<Relay_log_info*>(rli)->clear_tables_to_lock(); + const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); DBUG_RETURN(ERR_BAD_TABLE_DEF); } } @@ -7537,12 +7544,6 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) } } // if (table) - /* - We need to delay this clear until here bacause unpack_current_row() uses - master-side table definitions stored in rli. - */ - if (rli->tables_to_lock && get_flags(STMT_END_F)) - const_cast<Relay_log_info*>(rli)->clear_tables_to_lock(); if (error) { @@ -7610,7 +7611,7 @@ static int rows_event_stmt_cleanup(Relay_log_info const *rli, THD * thd) (assume the last master's transaction is ignored by the slave because of replicate-ignore rules). */ - error= thd->binlog_flush_pending_rows_event(true); + thd->binlog_flush_pending_rows_event(true); /* If this event is not in a transaction, the call below will, if some @@ -7621,7 +7622,7 @@ static int rows_event_stmt_cleanup(Relay_log_info const *rli, THD * thd) are involved, commit the transaction and flush the pending event to the binlog. */ - error|= ha_autocommit_or_rollback(thd, error); + error= trans_commit_stmt(thd); /* Now what if this is not a transactional engine? we still need to @@ -8087,15 +8088,15 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) NullS))) DBUG_RETURN(HA_ERR_OUT_OF_MEM); - bzero(table_list, sizeof(*table_list)); - table_list->db = db_mem; - table_list->alias= table_list->table_name = tname_mem; - table_list->lock_type= TL_WRITE; - table_list->next_global= table_list->next_local= 0; + strmov(db_mem, rpl_filter->get_rewrite_db(m_dbnam, &dummy_len)); + strmov(tname_mem, m_tblnam); + + table_list->init_one_table(db_mem, strlen(db_mem), + tname_mem, strlen(tname_mem), + tname_mem, TL_WRITE); + table_list->table_id= m_table_id; table_list->updating= 1; - strmov(table_list->db, rpl_filter->get_rewrite_db(m_dbnam, &dummy_len)); - strmov(table_list->table_name, m_tblnam); int error= 0; diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc index 3ebd4b1864a..f76e0f6d05a 100644 --- a/sql/log_event_old.cc +++ b/sql/log_event_old.cc @@ -6,6 +6,7 @@ #endif #include "log_event_old.h" #include "rpl_record_old.h" +#include "transaction.h" #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) @@ -32,8 +33,7 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info */ DBUG_ASSERT(ev->get_flags(Old_rows_log_event::STMT_END_F)); - const_cast<Relay_log_info*>(rli)->clear_tables_to_lock(); - close_thread_tables(thd); + const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); thd->clear_error(); DBUG_RETURN(0); } @@ -91,7 +91,7 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info "unexpected success or fatal error")); thd->is_slave_error= 1; } - const_cast<Relay_log_info*>(rli)->clear_tables_to_lock(); + const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); DBUG_RETURN(actual_error); } @@ -109,10 +109,8 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info { if (ptr->m_tabledef.compatible_with(rli, ptr->table)) { - mysql_unlock_tables(thd, thd->lock); - thd->lock= 0; thd->is_slave_error= 1; - const_cast<Relay_log_info*>(rli)->clear_tables_to_lock(); + const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); DBUG_RETURN(Old_rows_log_event::ERR_BAD_TABLE_DEF); } } @@ -236,13 +234,6 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info } } - /* - We need to delay this clear until the table def is no longer needed. - The table def is needed in unpack_row(). - */ - if (rli->tables_to_lock && ev->get_flags(Old_rows_log_event::STMT_END_F)) - const_cast<Relay_log_info*>(rli)->clear_tables_to_lock(); - if (error) { /* error has occured during the transaction */ rli->report(ERROR_LEVEL, thd->stmt_da->sql_errno(), @@ -1440,8 +1431,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) */ DBUG_ASSERT(get_flags(STMT_END_F)); - const_cast<Relay_log_info*>(rli)->clear_tables_to_lock(); - close_thread_tables(thd); + const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); thd->clear_error(); DBUG_RETURN(0); } @@ -1472,7 +1462,8 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) lex_start(thd); while ((error= lock_tables(thd, rli->tables_to_lock, - rli->tables_to_lock_count, &need_reopen))) + rli->tables_to_lock_count, 0, + &need_reopen))) { if (!need_reopen) { @@ -1496,7 +1487,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) "Error in %s event: when locking tables", get_type_str()); } - const_cast<Relay_log_info*>(rli)->clear_tables_to_lock(); + const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); DBUG_RETURN(error); } @@ -1513,17 +1504,9 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) NOTE: For this new scheme there should be no pending event: need to add code to assert that is the case. */ - error= thd->binlog_flush_pending_rows_event(false); - if (error) - { - rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR, - ER(ER_SLAVE_FATAL_ERROR), - "call to binlog_flush_pending_rows_event() failed"); - thd->is_slave_error= 1; - DBUG_RETURN(error); - } + thd->binlog_flush_pending_rows_event(false); TABLE_LIST *tables= rli->tables_to_lock; - close_tables_for_reopen(thd, &tables); + close_tables_for_reopen(thd, &tables, NULL); uint tables_count= rli->tables_to_lock_count; if ((error= open_tables(thd, &tables, &tables_count, 0))) @@ -1541,7 +1524,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) "unexpected success or fatal error")); thd->is_slave_error= 1; } - const_cast<Relay_log_info*>(rli)->clear_tables_to_lock(); + const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); DBUG_RETURN(error); } } @@ -1560,10 +1543,8 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) { if (ptr->m_tabledef.compatible_with(rli, ptr->table)) { - mysql_unlock_tables(thd, thd->lock); - thd->lock= 0; thd->is_slave_error= 1; - const_cast<Relay_log_info*>(rli)->clear_tables_to_lock(); + const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd); DBUG_RETURN(ERR_BAD_TABLE_DEF); } } @@ -1734,13 +1715,6 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) } } // if (table) - /* - We need to delay this clear until here bacause unpack_current_row() uses - master-side table definitions stored in rli. - */ - if (rli->tables_to_lock && get_flags(STMT_END_F)) - const_cast<Relay_log_info*>(rli)->clear_tables_to_lock(); - if (error) { /* error has occured during the transaction */ rli->report(ERROR_LEVEL, thd->net.last_errno, @@ -1811,7 +1785,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) (assume the last master's transaction is ignored by the slave because of replicate-ignore rules). */ - int binlog_error= thd->binlog_flush_pending_rows_event(true); + thd->binlog_flush_pending_rows_event(true); /* If this event is not in a transaction, the call below will, if some @@ -1822,13 +1796,12 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) are involved, commit the transaction and flush the pending event to the binlog. */ - if ((error= ha_autocommit_or_rollback(thd, binlog_error))) + if ((error= trans_commit_stmt(thd))) rli->report(ERROR_LEVEL, error, "Error in %s event: commit of row events failed, " "table `%s`.`%s`", get_type_str(), m_table->s->db.str, m_table->s->table_name.str); - error|= binlog_error; /* Now what if this is not a transactional engine? we still need to diff --git a/sql/mdl.cc b/sql/mdl.cc new file mode 100644 index 00000000000..5744467f576 --- /dev/null +++ b/sql/mdl.cc @@ -0,0 +1,2231 @@ +/* Copyright (C) 2007-2008 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#include "mdl.h" +#include "debug_sync.h" +#include <hash.h> +#include <mysqld_error.h> + + +void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket); + + +static bool mdl_initialized= 0; + + +/** + A collection of all MDL locks. A singleton, + there is only one instance of the map in the server. + Maps MDL_key to MDL_lock instances. +*/ + +class MDL_map +{ +public: + void init(); + void destroy(); + MDL_lock *find(const MDL_key *key); + MDL_lock *find_or_insert(const MDL_key *key); + void remove(MDL_lock *lock); +private: + bool move_from_hash_to_lock_mutex(MDL_lock *lock); +private: + /** All acquired locks in the server. */ + HASH m_locks; + /* Protects access to m_locks hash. */ + pthread_mutex_t m_mutex; +}; + + +enum enum_deadlock_weight +{ + MDL_DEADLOCK_WEIGHT_DML= 0, + MDL_DEADLOCK_WEIGHT_DDL= 100 +}; + + + +/** + A context of the recursive traversal through all contexts + in all sessions in search for deadlock. +*/ + +class Deadlock_detection_context +{ +public: + Deadlock_detection_context(MDL_context *start_arg) + : start(start_arg), + victim(NULL), + current_search_depth(0) + { } + MDL_context *start; + MDL_context *victim; + uint current_search_depth; + static const uint MAX_SEARCH_DEPTH= 1000; +}; + + +/** + Get a bit corresponding to enum_mdl_type value in a granted/waiting bitmaps + and compatibility matrices. +*/ + +#define MDL_BIT(A) static_cast<MDL_lock::bitmap_t>(1U << A) + +/** + The lock context. Created internally for an acquired lock. + For a given name, there exists only one MDL_lock instance, + and it exists only when the lock has been granted. + Can be seen as an MDL subsystem's version of TABLE_SHARE. + + This is an abstract class which lacks information about + compatibility rules for lock types. They should be specified + in its descendants. +*/ + +class MDL_lock +{ +public: + typedef uchar bitmap_t; + + class Ticket_list + { + public: + typedef I_P_List<MDL_ticket, + I_P_List_adapter<MDL_ticket, + &MDL_ticket::next_in_lock, + &MDL_ticket::prev_in_lock> > + List; + operator const List &() const { return m_list; } + Ticket_list() :m_bitmap(0) {} + + void add_ticket(MDL_ticket *ticket); + void remove_ticket(MDL_ticket *ticket); + bool is_empty() const { return m_list.is_empty(); } + bitmap_t bitmap() const { return m_bitmap; } + private: + void clear_bit_if_not_in_list(enum_mdl_type type); + private: + /** List of tickets. */ + List m_list; + /** Bitmap of types of tickets in this list. */ + bitmap_t m_bitmap; + }; + + typedef Ticket_list::List::Iterator Ticket_iterator; + +public: + /** The key of the object (data) being protected. */ + MDL_key key; + void *cached_object; + mdl_cached_object_release_hook cached_object_release_hook; + /** + Read-write lock protecting this lock context. + + TODO/FIXME: Replace with RW-lock which will prefer readers + on all platforms and not only on Linux. + */ + rw_lock_t m_rwlock; + + bool is_empty() const + { + return (m_granted.is_empty() && m_waiting.is_empty()); + } + + virtual const bitmap_t *incompatible_granted_types_bitmap() const = 0; + virtual const bitmap_t *incompatible_waiting_types_bitmap() const = 0; + + bool has_pending_conflicting_lock(enum_mdl_type type); + + bool can_grant_lock(enum_mdl_type type, MDL_context *requstor_ctx) const; + + inline static MDL_lock *create(const MDL_key *key); + + void notify_shared_locks(MDL_context *ctx) + { + Ticket_iterator it(m_granted); + MDL_ticket *conflicting_ticket; + + while ((conflicting_ticket= it++)) + { + if (conflicting_ticket->get_ctx() != ctx) + notify_shared_lock(ctx->get_thd(), conflicting_ticket); + } + } + + /** + Wake up contexts which are waiting to acquire lock on the object and + which may succeed now, when we released some lock on it or removed + some pending request from its waiters list (the latter can happen, + for example, when context trying to acquire exclusive on the object + lock is killed). + */ + void wake_up_waiters() + { + MDL_lock::Ticket_iterator it(m_waiting); + MDL_ticket *awake_ticket; + + while ((awake_ticket= it++)) + awake_ticket->get_ctx()->awake(MDL_context::NORMAL_WAKE_UP); + } + void remove_ticket(Ticket_list MDL_lock::*queue, MDL_ticket *ticket); + + bool find_deadlock(MDL_ticket *waiting_ticket, + Deadlock_detection_context *deadlock_ctx); + + /** List of granted tickets for this lock. */ + Ticket_list m_granted; + /** Tickets for contexts waiting to acquire a lock. */ + Ticket_list m_waiting; +public: + + MDL_lock(const MDL_key *key_arg) + : key(key_arg), + cached_object(NULL), + cached_object_release_hook(NULL), + m_ref_usage(0), + m_ref_release(0), + m_is_destroyed(FALSE) + { + my_rwlock_init(&m_rwlock, NULL); + } + + virtual ~MDL_lock() + { + rwlock_destroy(&m_rwlock); + } + inline static void destroy(MDL_lock *lock); +public: + /** + These three members are used to make it possible to separate + the mdl_locks.m_mutex mutex and MDL_lock::m_rwlock in + MDL_map::find_or_insert() for increased scalability. + The 'm_is_destroyed' member is only set by destroyers that + have both the mdl_locks.m_mutex and MDL_lock::m_rwlock, thus + holding any of the mutexes is sufficient to read it. + The 'm_ref_usage; is incremented under protection by + mdl_locks.m_mutex, but when 'm_is_destroyed' is set to TRUE, this + member is moved to be protected by the MDL_lock::m_rwlock. + This means that the MDL_map::find_or_insert() which only + holds the MDL_lock::m_rwlock can compare it to 'm_ref_release' + without acquiring mdl_locks.m_mutex again and if equal it can also + destroy the lock object safely. + The 'm_ref_release' is incremented under protection by + MDL_lock::m_rwlock. + Note since we are only interested in equality of these two + counters we don't have to worry about overflows as long as + their size is big enough to hold maximum number of concurrent + threads on the system. + */ + uint m_ref_usage; + uint m_ref_release; + bool m_is_destroyed; +}; + + +/** + An implementation of the global metadata lock. The only locking modes + which are supported at the moment are SHARED and INTENTION EXCLUSIVE. +*/ + +class MDL_global_lock : public MDL_lock +{ +public: + MDL_global_lock(const MDL_key *key_arg) + : MDL_lock(key_arg) + { } + + virtual const bitmap_t *incompatible_granted_types_bitmap() const + { + return m_granted_incompatible; + } + virtual const bitmap_t *incompatible_waiting_types_bitmap() const + { + return m_waiting_incompatible; + } + +private: + static const bitmap_t m_granted_incompatible[MDL_TYPE_END]; + static const bitmap_t m_waiting_incompatible[MDL_TYPE_END]; +}; + + +/** + An implementation of a per-object lock. Supports SHARED, SHARED_UPGRADABLE, + SHARED HIGH PRIORITY and EXCLUSIVE locks. +*/ + +class MDL_object_lock : public MDL_lock +{ +public: + MDL_object_lock(const MDL_key *key_arg) + : MDL_lock(key_arg) + { } + + virtual const bitmap_t *incompatible_granted_types_bitmap() const + { + return m_granted_incompatible; + } + virtual const bitmap_t *incompatible_waiting_types_bitmap() const + { + return m_waiting_incompatible; + } + +private: + static const bitmap_t m_granted_incompatible[MDL_TYPE_END]; + static const bitmap_t m_waiting_incompatible[MDL_TYPE_END]; +}; + + +static MDL_map mdl_locks; + +extern "C" +{ +static uchar * +mdl_locks_key(const uchar *record, size_t *length, + my_bool not_used __attribute__((unused))) +{ + MDL_lock *lock=(MDL_lock*) record; + *length= lock->key.length(); + return (uchar*) lock->key.ptr(); +} +} /* extern "C" */ + + +/** + Initialize the metadata locking subsystem. + + This function is called at server startup. + + In particular, initializes the new global mutex and + the associated condition variable: LOCK_mdl and COND_mdl. + These locking primitives are implementation details of the MDL + subsystem and are private to it. + + Note, that even though the new implementation adds acquisition + of a new global mutex to the execution flow of almost every SQL + statement, the design capitalizes on that to later save on + look ups in the table definition cache. This leads to reduced + contention overall and on LOCK_open in particular. + Please see the description of MDL_context::acquire_shared_lock() + for details. +*/ + +void mdl_init() +{ + DBUG_ASSERT(! mdl_initialized); + mdl_initialized= TRUE; + mdl_locks.init(); +} + + +/** + Release resources of metadata locking subsystem. + + Destroys the global mutex and the condition variable. + Called at server shutdown. +*/ + +void mdl_destroy() +{ + if (mdl_initialized) + { + mdl_initialized= FALSE; + mdl_locks.destroy(); + } +} + + +/** Initialize the global hash containing all MDL locks. */ + +void MDL_map::init() +{ + pthread_mutex_init(&m_mutex, NULL); + my_hash_init(&m_locks, &my_charset_bin, 16 /* FIXME */, 0, 0, + mdl_locks_key, 0, 0); +} + + +/** + Destroy the global hash containing all MDL locks. + @pre It must be empty. +*/ + +void MDL_map::destroy() +{ + DBUG_ASSERT(!m_locks.records); + pthread_mutex_destroy(&m_mutex); + my_hash_free(&m_locks); +} + + +/** + Find MDL_lock object corresponding to the key, create it + if it does not exist. + + @retval non-NULL - Success. MDL_lock instance for the key with + locked MDL_lock::m_rwlock. + @retval NULL - Failure (OOM). +*/ + +MDL_lock* MDL_map::find_or_insert(const MDL_key *mdl_key) +{ + MDL_lock *lock; + +retry: + pthread_mutex_lock(&m_mutex); + if (!(lock= (MDL_lock*) my_hash_search(&m_locks, + mdl_key->ptr(), + mdl_key->length()))) + { + lock= MDL_lock::create(mdl_key); + if (!lock || my_hash_insert(&m_locks, (uchar*)lock)) + { + pthread_mutex_unlock(&m_mutex); + MDL_lock::destroy(lock); + return NULL; + } + } + + if (move_from_hash_to_lock_mutex(lock)) + goto retry; + + return lock; +} + + +/** + Find MDL_lock object corresponding to the key. + + @retval non-NULL - MDL_lock instance for the key with locked + MDL_lock::m_rwlock. + @retval NULL - There was no MDL_lock for the key. +*/ + +MDL_lock* MDL_map::find(const MDL_key *mdl_key) +{ + MDL_lock *lock; + +retry: + pthread_mutex_lock(&m_mutex); + if (!(lock= (MDL_lock*) my_hash_search(&m_locks, + mdl_key->ptr(), + mdl_key->length()))) + { + pthread_mutex_unlock(&m_mutex); + return NULL; + } + + if (move_from_hash_to_lock_mutex(lock)) + goto retry; + + return lock; +} + + +/** + Release mdl_locks.m_mutex mutex and lock MDL_lock::m_rwlock for lock + object from the hash. Handle situation when object was released + while the held no mutex. + + @retval FALSE - Success. + @retval TRUE - Object was released while we held no mutex, caller + should re-try looking up MDL_lock object in the hash. +*/ + +bool MDL_map::move_from_hash_to_lock_mutex(MDL_lock *lock) +{ + DBUG_ASSERT(! lock->m_is_destroyed); + safe_mutex_assert_owner(&m_mutex); + + /* + We increment m_ref_usage which is a reference counter protected by + mdl_locks.m_mutex under the condition it is present in the hash and + m_is_destroyed is FALSE. + */ + lock->m_ref_usage++; + pthread_mutex_unlock(&m_mutex); + + rw_wrlock(&lock->m_rwlock); + lock->m_ref_release++; + if (unlikely(lock->m_is_destroyed)) + { + /* + Object was released while we held no mutex, we need to + release it if no others hold references to it, while our own + reference count ensured that the object as such haven't got + its memory released yet. We can also safely compare + m_ref_usage and m_ref_release since the object is no longer + present in the hash so no one will be able to find it and + increment m_ref_usage anymore. + */ + uint ref_usage= lock->m_ref_usage; + uint ref_release= lock->m_ref_release; + rw_unlock(&lock->m_rwlock); + if (ref_usage == ref_release) + MDL_lock::destroy(lock); + return TRUE; + } + return FALSE; +} + + +/** + Destroy MDL_lock object or delegate this responsibility to + whatever thread that holds the last outstanding reference to + it. +*/ + +void MDL_map::remove(MDL_lock *lock) +{ + uint ref_usage, ref_release; + + if (lock->cached_object) + (*lock->cached_object_release_hook)(lock->cached_object); + + /* + Destroy the MDL_lock object, but ensure that anyone that is + holding a reference to the object is not remaining, if so he + has the responsibility to release it. + + Setting of m_is_destroyed to TRUE while holding _both_ + mdl_locks.m_mutex and MDL_lock::m_rwlock mutexes transfers the + protection of m_ref_usage from mdl_locks.m_mutex to + MDL_lock::m_rwlock while removal of object from the hash makes + it read-only. Therefore whoever acquires MDL_lock::m_rwlock next + will see most up to date version of m_ref_usage. + + This means that when m_is_destroyed is TRUE and we hold the + MDL_lock::m_rwlock we can safely read the m_ref_usage + member. + */ + pthread_mutex_lock(&m_mutex); + my_hash_delete(&m_locks, (uchar*) lock); + lock->m_is_destroyed= TRUE; + ref_usage= lock->m_ref_usage; + ref_release= lock->m_ref_release; + rw_unlock(&lock->m_rwlock); + pthread_mutex_unlock(&m_mutex); + if (ref_usage == ref_release) + MDL_lock::destroy(lock); +} + + +/** + Initialize a metadata locking context. + + This is to be called when a new server connection is created. +*/ + +MDL_context::MDL_context() + :m_trans_sentinel(NULL), + m_thd(NULL), + m_needs_thr_lock_abort(FALSE), + m_waiting_for(NULL), + m_deadlock_weight(0), + m_signal(NO_WAKE_UP) +{ + my_rwlock_init(&m_waiting_for_lock, NULL); + pthread_mutex_init(&m_signal_lock, NULL); + pthread_cond_init(&m_signal_cond, NULL); +} + + +/** + Destroy metadata locking context. + + Assumes and asserts that there are no active or pending locks + associated with this context at the time of the destruction. + + Currently does nothing. Asserts that there are no pending + or satisfied lock requests. The pending locks must be released + prior to destruction. This is a new way to express the assertion + that all tables are closed before a connection is destroyed. +*/ + +void MDL_context::destroy() +{ + DBUG_ASSERT(m_tickets.is_empty()); + + rwlock_destroy(&m_waiting_for_lock); + pthread_mutex_destroy(&m_signal_lock); + pthread_cond_destroy(&m_signal_cond); +} + + +/** + Initialize a lock request. + + This is to be used for every lock request. + + Note that initialization and allocation are split into two + calls. This is to allow flexible memory management of lock + requests. Normally a lock request is stored in statement memory + (e.g. is a member of struct TABLE_LIST), but we would also like + to allow allocation of lock requests in other memory roots, + for example in the grant subsystem, to lock privilege tables. + + The MDL subsystem does not own or manage memory of lock requests. + + @param mdl_namespace Id of namespace of object to be locked + @param db Name of database to which the object belongs + @param name Name of of the object + @param mdl_type The MDL lock type for the request. +*/ + +void MDL_request::init(MDL_key::enum_mdl_namespace mdl_namespace, + const char *db_arg, + const char *name_arg, + enum enum_mdl_type mdl_type_arg) +{ + key.mdl_key_init(mdl_namespace, db_arg, name_arg); + type= mdl_type_arg; + ticket= NULL; +} + + +/** + Initialize a lock request using pre-built MDL_key. + + @sa MDL_request::init(namespace, db, name, type). + + @param key_arg The pre-built MDL key for the request. + @param mdl_type_arg The MDL lock type for the request. +*/ + +void MDL_request::init(const MDL_key *key_arg, + enum enum_mdl_type mdl_type_arg) +{ + key.mdl_key_init(key_arg); + type= mdl_type_arg; + ticket= NULL; +} + + +/** + Allocate and initialize one lock request. + + Same as mdl_init_lock(), but allocates the lock and the key buffer + on a memory root. Necessary to lock ad-hoc tables, e.g. + mysql.* tables of grant and data dictionary subsystems. + + @param mdl_namespace Id of namespace of object to be locked + @param db Name of database to which object belongs + @param name Name of of object + @param root MEM_ROOT on which object should be allocated + + @note The allocated lock request will have MDL_SHARED type. + + @retval 0 Error if out of memory + @retval non-0 Pointer to an object representing a lock request +*/ + +MDL_request * +MDL_request::create(MDL_key::enum_mdl_namespace mdl_namespace, const char *db, + const char *name, enum_mdl_type mdl_type, + MEM_ROOT *root) +{ + MDL_request *mdl_request; + + if (!(mdl_request= (MDL_request*) alloc_root(root, sizeof(MDL_request)))) + return NULL; + + mdl_request->init(mdl_namespace, db, name, mdl_type); + + return mdl_request; +} + + +uint MDL_request::get_deadlock_weight() const +{ + return key.mdl_namespace() == MDL_key::GLOBAL || + type > MDL_SHARED_NO_WRITE ? + MDL_DEADLOCK_WEIGHT_DDL : MDL_DEADLOCK_WEIGHT_DML; +} + +/** + Auxiliary functions needed for creation/destruction of MDL_lock objects. + + @note Also chooses an MDL_lock descendant appropriate for object namespace. + + @todo This naive implementation should be replaced with one that saves + on memory allocation by reusing released objects. +*/ + +inline MDL_lock *MDL_lock::create(const MDL_key *mdl_key) +{ + switch (mdl_key->mdl_namespace()) + { + case MDL_key::GLOBAL: + return new MDL_global_lock(mdl_key); + default: + return new MDL_object_lock(mdl_key); + } +} + + +void MDL_lock::destroy(MDL_lock *lock) +{ + delete lock; +} + + +/** + Auxiliary functions needed for creation/destruction of MDL_ticket + objects. + + @todo This naive implementation should be replaced with one that saves + on memory allocation by reusing released objects. +*/ + +MDL_ticket *MDL_ticket::create(MDL_context *ctx_arg, enum_mdl_type type_arg) +{ + return new MDL_ticket(ctx_arg, type_arg); +} + + +void MDL_ticket::destroy(MDL_ticket *ticket) +{ + delete ticket; +} + + +/** + Helper functions and macros to be used for killable waiting in metadata + locking subsystem. + + @sa THD::enter_cond()/exit_cond()/killed. + + @note We can't use THD::enter_cond()/exit_cond()/killed directly here + since this will make metadata subsystem dependent on THD class + and thus prevent us from writing unit tests for it. And usage of + wrapper functions to access THD::killed/enter_cond()/exit_cond() + will probably introduce too much overhead. +*/ + +#define MDL_ENTER_COND(A, B, C, D) \ + mdl_enter_cond(A, B, C, D, __func__, __FILE__, __LINE__) + +static inline const char *mdl_enter_cond(THD *thd, + st_my_thread_var *mysys_var, + pthread_cond_t *cond, + pthread_mutex_t *mutex, + const char *calling_func, + const char *calling_file, + const unsigned int calling_line) +{ + safe_mutex_assert_owner(mutex); + + mysys_var->current_mutex= (mysql_mutex_t*) mutex; + mysys_var->current_cond= (mysql_cond_t*) cond; + + DEBUG_SYNC(thd, "mdl_enter_cond"); + + return set_thd_proc_info(thd, "Waiting for table", + calling_func, calling_file, calling_line); +} + +#define MDL_EXIT_COND(A, B, C, D) \ + mdl_exit_cond(A, B, C, D, __func__, __FILE__, __LINE__) + +static inline void mdl_exit_cond(THD *thd, + st_my_thread_var *mysys_var, + pthread_mutex_t *mutex, + const char* old_msg, + const char *calling_func, + const char *calling_file, + const unsigned int calling_line) +{ + DBUG_ASSERT(mutex == (pthread_mutex_t*) mysys_var->current_mutex); + + pthread_mutex_unlock(mutex); + mysql_mutex_lock(&mysys_var->mutex); + mysys_var->current_mutex= 0; + mysys_var->current_cond= 0; + mysql_mutex_unlock(&mysys_var->mutex); + + DEBUG_SYNC(thd, "mdl_exit_cond"); + + (void) set_thd_proc_info(thd, old_msg, calling_func, + calling_file, calling_line); +} + + +MDL_context::mdl_signal_type MDL_context::wait() +{ + const char *old_msg; + st_my_thread_var *mysys_var= my_thread_var; + mdl_signal_type result; + + pthread_mutex_lock(&m_signal_lock); + + old_msg= MDL_ENTER_COND(m_thd, mysys_var, &m_signal_cond, &m_signal_lock); + + while (! m_signal && !mysys_var->abort) + pthread_cond_wait(&m_signal_cond, &m_signal_lock); + + result= m_signal; + + MDL_EXIT_COND(m_thd, mysys_var, &m_signal_lock, old_msg); + + return result; +} + + +MDL_context::mdl_signal_type MDL_context::timed_wait(ulong timeout) +{ + struct timespec abstime; + const char *old_msg; + mdl_signal_type result; + st_my_thread_var *mysys_var= my_thread_var; + + pthread_mutex_lock(&m_signal_lock); + + old_msg= MDL_ENTER_COND(m_thd, mysys_var, &m_signal_cond, &m_signal_lock); + + if (! m_signal) + { + set_timespec(abstime, timeout); + pthread_cond_timedwait(&m_signal_cond, &m_signal_lock, &abstime); + } + + result= (m_signal != NO_WAKE_UP) ? m_signal : TIMEOUT_WAKE_UP; + + MDL_EXIT_COND(m_thd, mysys_var, &m_signal_lock, old_msg); + + return result; +} + + +/** + Clear bit corresponding to the type of metadata lock in bitmap representing + set of such types if list of tickets does not contain ticket with such type. + + @param[in,out] bitmap Bitmap representing set of types of locks. + @param[in] list List to inspect. + @param[in] type Type of metadata lock to look up in the list. +*/ + +void MDL_lock::Ticket_list::clear_bit_if_not_in_list(enum_mdl_type type) +{ + MDL_lock::Ticket_iterator it(m_list); + const MDL_ticket *ticket; + + while ((ticket= it++)) + if (ticket->get_type() == type) + return; + m_bitmap&= ~ MDL_BIT(type); +} + + +/** + Add ticket to MDL_lock's list of waiting requests and + update corresponding bitmap of lock types. +*/ + +void MDL_lock::Ticket_list::add_ticket(MDL_ticket *ticket) +{ + /* + Ticket being added to the list must have MDL_ticket::m_lock set, + since for such tickets methods accessing this member might be + called by other threads. + */ + DBUG_ASSERT(ticket->get_lock()); + m_list.push_front(ticket); + m_bitmap|= MDL_BIT(ticket->get_type()); +} + + +/** + Remove ticket from MDL_lock's list of requests and + update corresponding bitmap of lock types. +*/ + +void MDL_lock::Ticket_list::remove_ticket(MDL_ticket *ticket) +{ + m_list.remove(ticket); + /* + Check if waiting queue has another ticket with the same type as + one which was removed. If there is no such ticket, i.e. we have + removed last ticket of particular type, then we need to update + bitmap of waiting ticket's types. + Note that in most common case, i.e. when shared lock is removed + from waiting queue, we are likely to find ticket of the same + type early without performing full iteration through the list. + So this method should not be too expensive. + */ + clear_bit_if_not_in_list(ticket->get_type()); +} + + +/** + Compatibility (or rather "incompatibility") matrices for global metadata + lock. Arrays of bitmaps which elements specify which granted/waiting locks + are incompatible with type of lock being requested. + + Here is how types of individual locks are translated to type of global lock: + + ----------------+-------------+ + Type of request | Correspond. | + for indiv. lock | global lock | + ----------------+-------------+ + S, SH, SR, SW | IS | + SNW, SNRW, X | IX | + SNW, SNRW -> X | IX (*) | + + The first array specifies if particular type of request can be satisfied + if there is granted global lock of certain type. + + | Type of active | + Request | global lock | + type | IS(**) IX S | + ---------+----------------+ + IS | + + + | + IX | + + - | + S | + - + | + + The second array specifies if particular type of request can be satisfied + if there is already waiting request for the global lock of certain type. + I.e. it specifies what is the priority of different lock types. + + | Pending | + Request | global lock | + type | IS(**) IX S | + ---------+--------------+ + IS | + + + | + IX | + + - | + S | + + + | + + Here: "+" -- means that request can be satisfied + "-" -- means that request can't be satisfied and should wait + + (*) Since for upgradable locks we always take intention exclusive global + lock at the same time when obtaining the shared lock, there is no + need to obtain such lock during the upgrade itself. + (**) Since intention shared global locks are compatible with all other + type of locks we don't even have any accounting for them. +*/ + +const MDL_lock::bitmap_t MDL_global_lock::m_granted_incompatible[MDL_TYPE_END] = +{ + MDL_BIT(MDL_SHARED), MDL_BIT(MDL_INTENTION_EXCLUSIVE), 0, 0, 0, 0, 0, 0 +}; + +const MDL_lock::bitmap_t MDL_global_lock::m_waiting_incompatible[MDL_TYPE_END] = +{ + MDL_BIT(MDL_SHARED), 0, 0, 0, 0, 0, 0, 0 +}; + + +/** + Compatibility (or rather "incompatibility") matrices for per-object + metadata lock. Arrays of bitmaps which elements specify which granted/ + waiting locks are incompatible with type of lock being requested. + + The first array specifies if particular type of request can be satisfied + if there is granted lock of certain type. + + Request | Granted requests for lock | + type | S SH SR SW SNW SNRW X | + ----------+------------------------------+ + S | + + + + + + - | + SH | + + + + + + - | + SR | + + + + + - - | + SW | + + + + - - - | + SNW | + + + - - - - | + SNRW | + + - - - - - | + X | - - - - - - - | + SNW -> X | - - - 0 0 0 0 | + SNRW -> X | - - 0 0 0 0 0 | + + The second array specifies if particular type of request can be satisfied + if there is waiting request for the same lock of certain type. In other + words it specifies what is the priority of different lock types. + + Request | Pending requests for lock | + type | S SH SR SW SNW SNRW X | + ----------+-----------------------------+ + S | + + + + + + - | + SH | + + + + + + + | + SR | + + + + + - - | + SW | + + + + - - - | + SNW | + + + + + + - | + SNRW | + + + + + + - | + X | + + + + + + + | + SNW -> X | + + + + + + + | + SNRW -> X | + + + + + + + | + + Here: "+" -- means that request can be satisfied + "-" -- means that request can't be satisfied and should wait + "0" -- means impossible situation which will trigger assert + + @note In cases then current context already has "stronger" type + of lock on the object it will be automatically granted + thanks to usage of the MDL_context::find_ticket() method. +*/ + +const MDL_lock::bitmap_t +MDL_object_lock::m_granted_incompatible[MDL_TYPE_END] = +{ + 0, + MDL_BIT(MDL_EXCLUSIVE), + MDL_BIT(MDL_EXCLUSIVE), + MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE), + MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) | + MDL_BIT(MDL_SHARED_NO_WRITE), + MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) | + MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_WRITE), + MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) | + MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_WRITE) | + MDL_BIT(MDL_SHARED_READ), + MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) | + MDL_BIT(MDL_SHARED_NO_WRITE) | MDL_BIT(MDL_SHARED_WRITE) | + MDL_BIT(MDL_SHARED_READ) | MDL_BIT(MDL_SHARED_HIGH_PRIO) | + MDL_BIT(MDL_SHARED) +}; + + +const MDL_lock::bitmap_t +MDL_object_lock::m_waiting_incompatible[MDL_TYPE_END] = +{ + 0, + MDL_BIT(MDL_EXCLUSIVE), + 0, + MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE), + MDL_BIT(MDL_EXCLUSIVE) | MDL_BIT(MDL_SHARED_NO_READ_WRITE) | + MDL_BIT(MDL_SHARED_NO_WRITE), + MDL_BIT(MDL_EXCLUSIVE), + MDL_BIT(MDL_EXCLUSIVE), + 0 +}; + + +/** + Check if request for the metadata lock can be satisfied given its + current state. + + @param type_arg The requested lock type. + @param requestor_ctx The MDL context of the requestor. + + @retval TRUE Lock request can be satisfied + @retval FALSE There is some conflicting lock. + + @note In cases then current context already has "stronger" type + of lock on the object it will be automatically granted + thanks to usage of the MDL_context::find_ticket() method. +*/ + +bool +MDL_lock::can_grant_lock(enum_mdl_type type_arg, + MDL_context *requestor_ctx) const +{ + bool can_grant= FALSE; + bitmap_t waiting_incompat_map= incompatible_waiting_types_bitmap()[type_arg]; + bitmap_t granted_incompat_map= incompatible_granted_types_bitmap()[type_arg]; + /* + New lock request can be satisfied iff: + - There are no incompatible types of satisfied requests + in other contexts + - There are no waiting requests which have higher priority + than this request. + */ + if (! (m_waiting.bitmap() & waiting_incompat_map)) + { + if (! (m_granted.bitmap() & granted_incompat_map)) + can_grant= TRUE; + else + { + Ticket_iterator it(m_granted); + MDL_ticket *ticket; + + /* Check that the incompatible lock belongs to some other context. */ + while ((ticket= it++)) + { + if (ticket->get_ctx() != requestor_ctx && + ticket->is_incompatible_when_granted(type_arg)) + break; + } + if (ticket == NULL) /* Incompatible locks are our own. */ + can_grant= TRUE; + } + } + return can_grant; +} + + +/** Remove a ticket from waiting or pending queue and wakeup up waiters. */ + +void MDL_lock::remove_ticket(Ticket_list MDL_lock::*list, MDL_ticket *ticket) +{ + rw_wrlock(&m_rwlock); + (this->*list).remove_ticket(ticket); + if (is_empty()) + mdl_locks.remove(this); + else + { + /* + There can be some contexts waiting to acquire a lock + which now might be able to do it. Wake them up! + */ + wake_up_waiters(); + rw_unlock(&m_rwlock); + } +} + + +/** + Check if we have any pending locks which conflict with existing + shared lock. + + @pre The ticket must match an acquired lock. + + @return TRUE if there is a conflicting lock request, FALSE otherwise. +*/ + +bool MDL_lock::has_pending_conflicting_lock(enum_mdl_type type) +{ + bool result; + + mysql_mutex_assert_not_owner(&LOCK_open); + + rw_rdlock(&m_rwlock); + result= (m_waiting.bitmap() & incompatible_granted_types_bitmap()[type]); + rw_unlock(&m_rwlock); + return result; +} + + +/** + Check if ticket represents metadata lock of "stronger" or equal type + than specified one. I.e. if metadata lock represented by ticket won't + allow any of locks which are not allowed by specified type of lock. + + @return TRUE if ticket has stronger or equal type + FALSE otherwise. +*/ + +bool MDL_ticket::has_stronger_or_equal_type(enum_mdl_type type) const +{ + const MDL_lock::bitmap_t * + granted_incompat_map= m_lock->incompatible_granted_types_bitmap(); + + return ! (granted_incompat_map[type] & ~(granted_incompat_map[m_type])); +} + + +bool MDL_ticket::is_incompatible_when_granted(enum_mdl_type type) const +{ + return (MDL_BIT(m_type) & + m_lock->incompatible_granted_types_bitmap()[type]); +} + + +bool MDL_ticket::is_incompatible_when_waiting(enum_mdl_type type) const +{ + return (MDL_BIT(m_type) & + m_lock->incompatible_waiting_types_bitmap()[type]); +} + + +/** + Acquire global intention exclusive lock. + + @param[in] mdl_request Lock request object for lock to be acquired + + @retval FALSE Success. The lock has been acquired. + @retval TRUE Error. +*/ + +bool +MDL_context::acquire_global_intention_exclusive_lock(MDL_request *mdl_request) +{ + DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::GLOBAL && + mdl_request->type == MDL_INTENTION_EXCLUSIVE); + + /* + If this is a non-recursive attempt to acquire global intention + exclusive lock we might have to wait until active global shared + lock or pending requests will go away. Since we won't hold any + resources (except associated with open HANDLERs) while doing it + deadlocks are not possible. + */ + DBUG_ASSERT(is_lock_owner(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE) || + ! has_locks() || + (m_trans_sentinel && m_tickets.front() == m_trans_sentinel)); + + return acquire_lock(mdl_request); +} + + +/** + Check whether the context already holds a compatible lock ticket + on an object. + Start searching the transactional locks. If not + found in the list of transactional locks, look at LOCK TABLES + and HANDLER locks. + + @param mdl_request Lock request object for lock to be acquired + @param[out] is_transactional FALSE if we pass beyond m_trans_sentinel + while searching for ticket, otherwise TRUE. + + @note Tickets which correspond to lock types "stronger" than one + being requested are also considered compatible. + + @return A pointer to the lock ticket for the object or NULL otherwise. +*/ + +MDL_ticket * +MDL_context::find_ticket(MDL_request *mdl_request, + bool *is_transactional) +{ + MDL_ticket *ticket; + Ticket_iterator it(m_tickets); + + *is_transactional= TRUE; + + while ((ticket= it++)) + { + if (ticket == m_trans_sentinel) + *is_transactional= FALSE; + + if (mdl_request->key.is_equal(&ticket->m_lock->key) && + ticket->has_stronger_or_equal_type(mdl_request->type)) + break; + } + + return ticket; +} + + +/** + Acquire one lock with waiting for conflicting locks to go away if needed. + + @note This is an internal method which should not be used outside of MDL + subsystem as in most cases simply waiting for conflicting locks to + go away will lead to deadlock. + + @param mdl_request [in/out] Lock request object for lock to be acquired + + @retval FALSE Success. MDL_request::ticket points to the ticket + for the lock. + @retval TRUE Failure (Out of resources or waiting is aborted), +*/ + +bool +MDL_context::acquire_lock(MDL_request *mdl_request) +{ + return acquire_lock_impl(mdl_request); +} + + +/** + Try to acquire one lock. + + Unlike exclusive locks, shared locks are acquired one by + one. This is interface is chosen to simplify introduction of + the new locking API to the system. MDL_context::try_acquire_lock() + is currently used from open_table(), and there we have only one + table to work with. + + This function may also be used to try to acquire an exclusive + lock on a destination table, by ALTER TABLE ... RENAME. + + Returns immediately without any side effect if encounters a lock + conflict. Otherwise takes the lock. + + FIXME: Compared to lock_table_name_if_not_cached() (from 5.1) + it gives slightly more false negatives. + + @param mdl_request [in/out] Lock request object for lock to be acquired + + @retval FALSE Success. The lock may have not been acquired. + Check the ticket, if it's NULL, a conflicting lock + exists and another attempt should be made after releasing + all current locks and waiting for conflicting lock go + away (using MDL_context::wait_for_lock()). + @retval TRUE Out of resources, an error has been reported. +*/ + +bool +MDL_context::try_acquire_lock(MDL_request *mdl_request) +{ + MDL_lock *lock; + MDL_key *key= &mdl_request->key; + MDL_ticket *ticket; + bool is_transactional; + + DBUG_ASSERT(mdl_request->type < MDL_SHARED_NO_WRITE || + (is_lock_owner(MDL_key::GLOBAL, "", "", + MDL_INTENTION_EXCLUSIVE))); + DBUG_ASSERT(mdl_request->ticket == NULL); + + /* Don't take chances in production. */ + mdl_request->ticket= NULL; + mysql_mutex_assert_not_owner(&LOCK_open); + + /* + Check whether the context already holds a shared lock on the object, + and if so, grant the request. + */ + if ((ticket= find_ticket(mdl_request, &is_transactional))) + { + DBUG_ASSERT(ticket->m_lock); + DBUG_ASSERT(ticket->m_type >= mdl_request->type); + /* + If the request is for a transactional lock, and we found + a transactional lock, just reuse the found ticket. + + It's possible that we found a transactional lock, + but the request is for a HANDLER lock. In that case HANDLER + code will clone the ticket (see below why it's needed). + + If the request is for a transactional lock, and we found + a HANDLER lock, create a copy, to make sure that when user + does HANDLER CLOSE, the transactional lock is not released. + + If the request is for a handler lock, and we found a + HANDLER lock, also do the clone. HANDLER CLOSE for one alias + should not release the lock on the table HANDLER opened through + a different alias. + */ + mdl_request->ticket= ticket; + if (!is_transactional && clone_ticket(mdl_request)) + { + /* Clone failed. */ + mdl_request->ticket= NULL; + return TRUE; + } + return FALSE; + } + + if (!(ticket= MDL_ticket::create(this, mdl_request->type))) + return TRUE; + + /* The below call implicitly locks MDL_lock::m_rwlock on success. */ + if (!(lock= mdl_locks.find_or_insert(key))) + { + MDL_ticket::destroy(ticket); + return TRUE; + } + + if (lock->can_grant_lock(mdl_request->type, this)) + { + ticket->m_lock= lock; + lock->m_granted.add_ticket(ticket); + rw_unlock(&lock->m_rwlock); + + m_tickets.push_front(ticket); + + mdl_request->ticket= ticket; + } + else + { + /* We can't get here if we allocated a new lock. */ + DBUG_ASSERT(! lock->is_empty()); + rw_unlock(&lock->m_rwlock); + MDL_ticket::destroy(ticket); + } + + return FALSE; +} + + +/** + Create a copy of a granted ticket. + This is used to make sure that HANDLER ticket + is never shared with a ticket that belongs to + a transaction, so that when we HANDLER CLOSE, + we don't release a transactional ticket, and + vice versa -- when we COMMIT, we don't mistakenly + release a ticket for an open HANDLER. + + @retval TRUE Out of memory. + @retval FALSE Success. +*/ + +bool +MDL_context::clone_ticket(MDL_request *mdl_request) +{ + MDL_ticket *ticket; + + mysql_mutex_assert_not_owner(&LOCK_open); + /* + By submitting mdl_request->type to MDL_ticket::create() + we effectively downgrade the cloned lock to the level of + the request. + */ + if (!(ticket= MDL_ticket::create(this, mdl_request->type))) + return TRUE; + + /* clone() is not supposed to be used to get a stronger lock. */ + DBUG_ASSERT(ticket->m_type <= mdl_request->ticket->m_type); + + ticket->m_lock= mdl_request->ticket->m_lock; + mdl_request->ticket= ticket; + + rw_wrlock(&ticket->m_lock->m_rwlock); + ticket->m_lock->m_granted.add_ticket(ticket); + rw_unlock(&ticket->m_lock->m_rwlock); + + m_tickets.push_front(ticket); + + return FALSE; +} + + +/** + Notify a thread holding a shared metadata lock which + conflicts with a pending exclusive lock. + + @param thd Current thread context + @param conflicting_ticket Conflicting metadata lock +*/ + +void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket) +{ + /* Only try to abort locks on which we back off. */ + if (conflicting_ticket->get_type() < MDL_SHARED_NO_WRITE) + { + MDL_context *conflicting_ctx= conflicting_ticket->get_ctx(); + THD *conflicting_thd= conflicting_ctx->get_thd(); + DBUG_ASSERT(thd != conflicting_thd); /* Self-deadlock */ + + /* + If thread which holds conflicting lock is waiting on table-level + lock or some other non-MDL resource we might need to wake it up + by calling code outside of MDL. + */ + mysql_notify_thread_having_shared_lock(thd, conflicting_thd, + conflicting_ctx->get_needs_thr_lock_abort()); + } +} + + +/** + Auxiliary method for acquiring an exclusive lock. + + @param mdl_request Request for the lock to be acqured. + + @note Should not be used outside of MDL subsystem. Instead one + should call acquire_lock() or acquire_locks() + methods which ensure that conditions for deadlock-free + lock acquisition are fulfilled. + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool MDL_context::acquire_lock_impl(MDL_request *mdl_request) +{ + MDL_lock *lock; + MDL_ticket *ticket; + bool not_used; + st_my_thread_var *mysys_var= my_thread_var; + MDL_key *key= &mdl_request->key; + + mysql_mutex_assert_not_owner(&LOCK_open); + + DBUG_ASSERT(mdl_request->ticket == NULL); + /* Don't take chances in production. */ + mdl_request->ticket= NULL; + + /* + Check whether the context already holds an exclusive lock on the object, + and if so, grant the request. + */ + if ((ticket= find_ticket(mdl_request, ¬_used))) + { + DBUG_ASSERT(ticket->m_lock); + mdl_request->ticket= ticket; + return FALSE; + } + + DBUG_ASSERT(mdl_request->type < MDL_SHARED_NO_WRITE || + is_lock_owner(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE)); + + /* Early allocation: ticket will be needed in any case. */ + if (!(ticket= MDL_ticket::create(this, mdl_request->type))) + return TRUE; + + /* The below call implicitly locks MDL_lock::m_rwlock on success. */ + if (!(lock= mdl_locks.find_or_insert(key))) + { + MDL_ticket::destroy(ticket); + return TRUE; + } + + ticket->m_lock= lock; + + lock->m_waiting.add_ticket(ticket); + + while (!lock->can_grant_lock(mdl_request->type, this)) + { + wait_reset(); + + if (ticket->is_upgradable_or_exclusive()) + lock->notify_shared_locks(this); + + rw_unlock(&lock->m_rwlock); + + set_deadlock_weight(mdl_request->get_deadlock_weight()); + will_wait_for(ticket); + + /* There is a shared or exclusive lock on the object. */ + DEBUG_SYNC(m_thd, "mdl_acquire_lock_wait"); + + bool is_deadlock= (find_deadlock() || timed_wait(1) == VICTIM_WAKE_UP); + + stop_waiting(); + + if (is_deadlock || mysys_var->abort) + { + lock->remove_ticket(&MDL_lock::m_waiting, ticket); + MDL_ticket::destroy(ticket); + if (is_deadlock) + my_error(ER_LOCK_DEADLOCK, MYF(0)); + return TRUE; + } + rw_wrlock(&lock->m_rwlock); + } + + lock->m_waiting.remove_ticket(ticket); + lock->m_granted.add_ticket(ticket); + + if (ticket->get_type() == MDL_EXCLUSIVE && lock->cached_object) + (*lock->cached_object_release_hook)(lock->cached_object); + lock->cached_object= NULL; + + rw_unlock(&lock->m_rwlock); + + m_tickets.push_front(ticket); + + mdl_request->ticket= ticket; + + return FALSE; +} + + +extern "C" int mdl_request_ptr_cmp(const void* ptr1, const void* ptr2) +{ + MDL_request *req1= *(MDL_request**)ptr1; + MDL_request *req2= *(MDL_request**)ptr2; + return req1->key.cmp(&req2->key); +} + + +/** + Acquire exclusive locks. There must be no granted locks in the + context. + + This is a replacement of lock_table_names(). It is used in + RENAME, DROP and other DDL SQL statements. + + @param mdl_requests List of requests for locks to be acquired. + + @note The list of requests should not contain non-exclusive lock requests. + There should not be any acquired locks in the context. + + @note Assumes that one already owns global intention exclusive lock. + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool MDL_context::acquire_locks(MDL_request_list *mdl_requests) +{ + MDL_request_list::Iterator it(*mdl_requests); + MDL_request **sort_buf, **p_req; + ssize_t req_count= static_cast<ssize_t>(mdl_requests->elements()); + + if (req_count == 0) + return FALSE; + + /* + To reduce deadlocks, the server acquires all exclusive + locks at once. For shared locks, try_acquire_lock() is + used instead. + */ + DBUG_ASSERT(m_tickets.is_empty() || m_tickets.front() == m_trans_sentinel); + + /* Sort requests according to MDL_key. */ + if (! (sort_buf= (MDL_request **)my_malloc(req_count * + sizeof(MDL_request*), + MYF(MY_WME)))) + return TRUE; + + for (p_req= sort_buf; p_req < sort_buf + req_count; p_req++) + *p_req= it++; + + my_qsort(sort_buf, req_count, sizeof(MDL_request*), + mdl_request_ptr_cmp); + + for (p_req= sort_buf; p_req < sort_buf + req_count; p_req++) + { + if (acquire_lock_impl(*p_req)) + goto err; + } + my_free(sort_buf, MYF(0)); + return FALSE; + +err: + /* Release locks we have managed to acquire so far. */ + for (req_count= p_req - sort_buf, p_req= sort_buf; + p_req < sort_buf + req_count; p_req++) + { + release_lock((*p_req)->ticket); + /* Reset lock request back to its initial state. */ + (*p_req)->ticket= NULL; + } + my_free(sort_buf, MYF(0)); + return TRUE; +} + + +/** + Upgrade a shared metadata lock to exclusive. + + Used in ALTER TABLE, when a copy of the table with the + new definition has been constructed. + + @note In case of failure to upgrade lock (e.g. because upgrader + was killed) leaves lock in its original state (locked in + shared mode). + + @note There can be only one upgrader for a lock or we will have deadlock. + This invariant is ensured by code outside of metadata subsystem usually + by obtaining some sort of exclusive table-level lock (e.g. TL_WRITE, + TL_WRITE_ALLOW_READ) before performing upgrade of metadata lock. + + @retval FALSE Success + @retval TRUE Failure (thread was killed) +*/ + +bool +MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket) +{ + MDL_request mdl_xlock_request; + MDL_ticket *mdl_svp= mdl_savepoint(); + bool is_new_ticket; + + DBUG_ENTER("MDL_ticket::upgrade_shared_lock_to_exclusive"); + DEBUG_SYNC(get_thd(), "mdl_upgrade_shared_lock_to_exclusive"); + + /* + Do nothing if already upgraded. Used when we FLUSH TABLE under + LOCK TABLES and a table is listed twice in LOCK TABLES list. + */ + if (mdl_ticket->m_type == MDL_EXCLUSIVE) + DBUG_RETURN(FALSE); + + /* Only allow upgrades from MDL_SHARED_NO_WRITE/NO_READ_WRITE */ + DBUG_ASSERT(mdl_ticket->m_type == MDL_SHARED_NO_WRITE || + mdl_ticket->m_type == MDL_SHARED_NO_READ_WRITE); + + mdl_xlock_request.init(&mdl_ticket->m_lock->key, MDL_EXCLUSIVE); + + if (acquire_lock_impl(&mdl_xlock_request)) + DBUG_RETURN(TRUE); + + is_new_ticket= ! has_lock(mdl_svp, mdl_xlock_request.ticket); + + /* Merge the acquired and the original lock. @todo: move to a method. */ + rw_wrlock(&mdl_ticket->m_lock->m_rwlock); + if (is_new_ticket) + mdl_ticket->m_lock->m_granted.remove_ticket(mdl_xlock_request.ticket); + /* + Set the new type of lock in the ticket. To update state of + MDL_lock object correctly we need to temporarily exclude + ticket from the granted queue and then include it back. + */ + mdl_ticket->m_lock->m_granted.remove_ticket(mdl_ticket); + mdl_ticket->m_type= MDL_EXCLUSIVE; + mdl_ticket->m_lock->m_granted.add_ticket(mdl_ticket); + + rw_unlock(&mdl_ticket->m_lock->m_rwlock); + + if (is_new_ticket) + { + m_tickets.remove(mdl_xlock_request.ticket); + MDL_ticket::destroy(mdl_xlock_request.ticket); + } + + DBUG_RETURN(FALSE); +} + + +bool MDL_lock::find_deadlock(MDL_ticket *waiting_ticket, + Deadlock_detection_context *deadlock_ctx) +{ + MDL_ticket *ticket; + bool result= FALSE; + + rw_rdlock(&m_rwlock); + + Ticket_iterator granted_it(m_granted); + Ticket_iterator waiting_it(m_waiting); + + while ((ticket= granted_it++)) + { + if (ticket->is_incompatible_when_granted(waiting_ticket->get_type()) && + ticket->get_ctx() != waiting_ticket->get_ctx() && + ticket->get_ctx() == deadlock_ctx->start) + { + result= TRUE; + goto end; + } + } + + while ((ticket= waiting_it++)) + { + if (ticket->is_incompatible_when_waiting(waiting_ticket->get_type()) && + ticket->get_ctx() != waiting_ticket->get_ctx() && + ticket->get_ctx() == deadlock_ctx->start) + { + result= TRUE; + goto end; + } + } + + granted_it.rewind(); + while ((ticket= granted_it++)) + { + if (ticket->is_incompatible_when_granted(waiting_ticket->get_type()) && + ticket->get_ctx() != waiting_ticket->get_ctx() && + ticket->get_ctx()->find_deadlock(deadlock_ctx)) + { + result= TRUE; + goto end; + } + } + + waiting_it.rewind(); + while ((ticket= waiting_it++)) + { + if (ticket->is_incompatible_when_waiting(waiting_ticket->get_type()) && + ticket->get_ctx() != waiting_ticket->get_ctx() && + ticket->get_ctx()->find_deadlock(deadlock_ctx)) + { + result= TRUE; + goto end; + } + } + +end: + rw_unlock(&m_rwlock); + return result; +} + + +bool MDL_context::find_deadlock(Deadlock_detection_context *deadlock_ctx) +{ + bool result= FALSE; + + rw_rdlock(&m_waiting_for_lock); + + if (m_waiting_for) + { + /* + QQ: should we rather be checking for NO_WAKE_UP ? + + We want to do check signal only when m_waiting_for is set + to avoid reading left-overs from previous kills. + */ + if (peek_signal() != VICTIM_WAKE_UP) + { + + if (++deadlock_ctx->current_search_depth > + deadlock_ctx->MAX_SEARCH_DEPTH) + result= TRUE; + else + result= m_waiting_for->m_lock->find_deadlock(m_waiting_for, + deadlock_ctx); + --deadlock_ctx->current_search_depth; + } + } + + if (result) + { + if (! deadlock_ctx->victim) + deadlock_ctx->victim= this; + else if (deadlock_ctx->victim->m_deadlock_weight >= m_deadlock_weight) + { + rw_unlock(&deadlock_ctx->victim->m_waiting_for_lock); + deadlock_ctx->victim= this; + } + else + rw_unlock(&m_waiting_for_lock); + } + else + rw_unlock(&m_waiting_for_lock); + + return result; +} + + +bool MDL_context::find_deadlock() +{ + Deadlock_detection_context deadlock_ctx(this); + + while (1) + { + if (! find_deadlock(&deadlock_ctx)) + { + /* No deadlocks are found! */ + break; + } + + if (deadlock_ctx.victim != this) + { + deadlock_ctx.victim->awake(VICTIM_WAKE_UP); + rw_unlock(&deadlock_ctx.victim->m_waiting_for_lock); + /* + After adding new arc to waiting graph we found that it participates + in some loop (i.e. there is a deadlock). We decided to destroy this + loop by removing some arc other than newly added. Since this doesn't + guarantee that all loops created by addition of this arc are + destroyed we have to repeat search. + */ + continue; + } + else + { + DBUG_ASSERT(&deadlock_ctx.victim->m_waiting_for_lock == &m_waiting_for_lock); + rw_unlock(&deadlock_ctx.victim->m_waiting_for_lock); + return TRUE; + } + } + return FALSE; +} + + +/** + Wait until there will be no locks that conflict with lock requests + in the given list. + + This is a part of the locking protocol and must be used by the + acquirer of shared locks after a back-off. + + Does not acquire the locks! + + @retval FALSE Success. One can try to obtain metadata locks. + @retval TRUE Failure (thread was killed or deadlock is possible). +*/ + +bool +MDL_context::wait_for_lock(MDL_request *mdl_request) +{ + MDL_lock *lock; + st_my_thread_var *mysys_var= my_thread_var; + + mysql_mutex_assert_not_owner(&LOCK_open); + + DBUG_ASSERT(mdl_request->ticket == NULL); + + while (!mysys_var->abort) + { + /* + We have to check if there are some HANDLERs open by this thread + which conflict with some pending exclusive locks. Otherwise we + might have a deadlock in situations when we are waiting for + pending writer to go away, which in its turn waits for HANDLER + open by our thread. + + TODO: investigate situations in which we need to broadcast on + COND_mdl because of above scenario. + */ + mysql_ha_flush(m_thd); + + MDL_key *key= &mdl_request->key; + + /* The below call implicitly locks MDL_lock::m_rwlock on success. */ + if (! (lock= mdl_locks.find(key))) + return FALSE; + + if (lock->can_grant_lock(mdl_request->type, this)) + { + rw_unlock(&lock->m_rwlock); + return FALSE; + } + + MDL_ticket *pending_ticket; + if (! (pending_ticket= MDL_ticket::create(this, mdl_request->type))) + { + rw_unlock(&lock->m_rwlock); + return TRUE; + } + + pending_ticket->m_lock= lock; + + lock->m_waiting.add_ticket(pending_ticket); + + wait_reset(); + rw_unlock(&lock->m_rwlock); + + set_deadlock_weight(MDL_DEADLOCK_WEIGHT_DML); + will_wait_for(pending_ticket); + + bool is_deadlock= (find_deadlock() || wait() == VICTIM_WAKE_UP); + + stop_waiting(); + + lock->remove_ticket(&MDL_lock::m_waiting, pending_ticket); + MDL_ticket::destroy(pending_ticket); + if (is_deadlock) + { + my_error(ER_LOCK_DEADLOCK, MYF(0)); + return TRUE; + } + } + return mysys_var->abort; +} + + +/** + Release lock. + + @param ticket Ticket for lock to be released. +*/ + +void MDL_context::release_lock(MDL_ticket *ticket) +{ + MDL_lock *lock= ticket->m_lock; + DBUG_ENTER("MDL_context::release_lock"); + DBUG_PRINT("enter", ("db=%s name=%s", lock->key.db_name(), + lock->key.name())); + + DBUG_ASSERT(this == ticket->get_ctx()); + mysql_mutex_assert_not_owner(&LOCK_open); + + if (ticket == m_trans_sentinel) + m_trans_sentinel= ++Ticket_list::Iterator(m_tickets, ticket); + + lock->remove_ticket(&MDL_lock::m_granted, ticket); + + m_tickets.remove(ticket); + MDL_ticket::destroy(ticket); + + DBUG_VOID_RETURN; +} + + +/** + Release all locks associated with the context. If the sentinel + is not NULL, do not release locks stored in the list after and + including the sentinel. + + Transactional locks are added to the beginning of the list, i.e. + stored in reverse temporal order. This allows to employ this + function to: + - back off in case of a lock conflict. + - release all locks in the end of a transaction + - rollback to a savepoint. + + The sentinel semantics is used to support LOCK TABLES + mode and HANDLER statements: locks taken by these statements + survive COMMIT, ROLLBACK, ROLLBACK TO SAVEPOINT. +*/ + +void MDL_context::release_locks_stored_before(MDL_ticket *sentinel) +{ + MDL_ticket *ticket; + Ticket_iterator it(m_tickets); + DBUG_ENTER("MDL_context::release_locks_stored_before"); + + if (m_tickets.is_empty()) + DBUG_VOID_RETURN; + + while ((ticket= it++) && ticket != sentinel) + { + DBUG_PRINT("info", ("found lock to release ticket=%p", ticket)); + release_lock(ticket); + } + /* + If all locks were released, then the sentinel was not present + in the list. It must never happen because the sentinel was + bogus, i.e. pointed to a ticket that no longer exists. + */ + DBUG_ASSERT(! m_tickets.is_empty() || sentinel == NULL); + + DBUG_VOID_RETURN; +} + + +/** + Release all locks in the context which correspond to the same name/ + object as this lock request. + + @param ticket One of the locks for the name/object for which all + locks should be released. +*/ + +void MDL_context::release_all_locks_for_name(MDL_ticket *name) +{ + /* Use MDL_ticket::m_lock to identify other locks for the same object. */ + MDL_lock *lock= name->m_lock; + + /* Remove matching lock tickets from the context. */ + MDL_ticket *ticket; + Ticket_iterator it_ticket(m_tickets); + + while ((ticket= it_ticket++)) + { + DBUG_ASSERT(ticket->m_lock); + /* + We rarely have more than one ticket in this loop, + let's not bother saving on pthread_cond_broadcast(). + */ + if (ticket->m_lock == lock) + release_lock(ticket); + } +} + + +/** + Downgrade an exclusive lock to shared metadata lock. + + @param type Type of lock to which exclusive lock should be downgraded. +*/ + +void MDL_ticket::downgrade_exclusive_lock(enum_mdl_type type) +{ + mysql_mutex_assert_not_owner(&LOCK_open); + + /* + Do nothing if already downgraded. Used when we FLUSH TABLE under + LOCK TABLES and a table is listed twice in LOCK TABLES list. + */ + if (m_type != MDL_EXCLUSIVE) + return; + + rw_wrlock(&m_lock->m_rwlock); + /* + To update state of MDL_lock object correctly we need to temporarily + exclude ticket from the granted queue and then include it back. + */ + m_lock->m_granted.remove_ticket(this); + m_type= type; + m_lock->m_granted.add_ticket(this); + m_lock->wake_up_waiters(); + rw_unlock(&m_lock->m_rwlock); +} + + +/** + Auxiliary function which allows to check if we have some kind of lock on + a object. Returns TRUE if we have a lock of a given or stronger type. + + @param mdl_namespace Id of object namespace + @param db Name of the database + @param name Name of the object + @param mdl_type Lock type. Pass in the weakest type to find + out if there is at least some lock. + + @return TRUE if current context contains satisfied lock for the object, + FALSE otherwise. +*/ + +bool +MDL_context::is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace, + const char *db, const char *name, + enum_mdl_type mdl_type) +{ + MDL_request mdl_request; + bool is_transactional_unused; + mdl_request.init(mdl_namespace, db, name, mdl_type); + MDL_ticket *ticket= find_ticket(&mdl_request, &is_transactional_unused); + + DBUG_ASSERT(ticket == NULL || ticket->m_lock); + + return ticket; +} + + +/** + Check if we have any pending locks which conflict with existing shared lock. + + @pre The ticket must match an acquired lock. + + @return TRUE if there is a conflicting lock request, FALSE otherwise. +*/ + +bool MDL_ticket::has_pending_conflicting_lock() const +{ + return m_lock->has_pending_conflicting_lock(m_type); +} + + +/** + Associate pointer to an opaque object with a lock. + + @param cached_object Pointer to the object + @param release_hook Cleanup function to be called when MDL subsystem + decides to remove lock or associate another object. + + This is used to cache a pointer to TABLE_SHARE in the lock + structure. Such caching can save one acquisition of LOCK_open + and one table definition cache lookup for every table. + + Since the pointer may be stored only inside an acquired lock, + the caching is only effective when there is more than one lock + granted on a given table. + + This function has the following usage pattern: + - try to acquire an MDL lock + - when done, call for mdl_get_cached_object(). If it returns NULL, our + thread has the only lock on this table. + - look up TABLE_SHARE in the table definition cache + - call mdl_set_cache_object() to assign the share to the opaque pointer. + + The release hook is invoked when the last shared metadata + lock on this name is released. +*/ + +void +MDL_ticket::set_cached_object(void *cached_object, + mdl_cached_object_release_hook release_hook) +{ + DBUG_ENTER("mdl_set_cached_object"); + DBUG_PRINT("enter", ("db=%s name=%s cached_object=%p", + m_lock->key.db_name(), m_lock->key.name(), + cached_object)); + /* + TODO: This assumption works now since we do get_cached_object() + and set_cached_object() in the same critical section. Once + this becomes false we will have to call release_hook here and + use additional mutex protecting 'cached_object' member. + */ + DBUG_ASSERT(!m_lock->cached_object); + + m_lock->cached_object= cached_object; + m_lock->cached_object_release_hook= release_hook; + + DBUG_VOID_RETURN; +} + + +/** + Get a pointer to an opaque object that associated with the lock. + + @param ticket Lock ticket for the lock which the object is associated to. + + @return Pointer to an opaque object associated with the lock. +*/ + +void *MDL_ticket::get_cached_object() +{ + return m_lock->cached_object; +} + + +/** + Releases metadata locks that were acquired after a specific savepoint. + + @note Used to release tickets acquired during a savepoint unit. + @note It's safe to iterate and unlock any locks after taken after this + savepoint because other statements that take other special locks + cause a implicit commit (ie LOCK TABLES). + + @param mdl_savepont The last acquired MDL lock when the + savepoint was set. +*/ + +void MDL_context::rollback_to_savepoint(MDL_ticket *mdl_savepoint) +{ + DBUG_ENTER("MDL_context::rollback_to_savepoint"); + + /* If savepoint is NULL, it is from the start of the transaction. */ + release_locks_stored_before(mdl_savepoint ? + mdl_savepoint : m_trans_sentinel); + + DBUG_VOID_RETURN; +} + + +/** + Release locks acquired by normal statements (SELECT, UPDATE, + DELETE, etc) in the course of a transaction. Do not release + HANDLER locks, if there are any. + + This method is used at the end of a transaction, in + implementation of COMMIT (implicit or explicit) and ROLLBACK. +*/ + +void MDL_context::release_transactional_locks() +{ + DBUG_ENTER("MDL_context::release_transactional_locks"); + release_locks_stored_before(m_trans_sentinel); + DBUG_VOID_RETURN; +} + + +/** + Does this savepoint have this lock? + + @retval TRUE The ticket is older than the savepoint and + is not LT, HA or GLR ticket. Thus it belongs + to the savepoint. + @retval FALSE The ticket is newer than the savepoint + or is an LT, HA or GLR ticket. +*/ + +bool MDL_context::has_lock(MDL_ticket *mdl_savepoint, + MDL_ticket *mdl_ticket) +{ + MDL_ticket *ticket; + /* Start from the beginning, most likely mdl_ticket's been just acquired. */ + MDL_context::Ticket_iterator it(m_tickets); + bool found_savepoint= FALSE; + + while ((ticket= it++) && ticket != m_trans_sentinel) + { + /* + First met the savepoint. The ticket must be + somewhere after it. + */ + if (ticket == mdl_savepoint) + found_savepoint= TRUE; + /* + Met the ticket. If we haven't yet met the savepoint, + the ticket is newer than the savepoint. + */ + if (ticket == mdl_ticket) + return found_savepoint; + } + /* Reached m_trans_sentinel. The ticket must be LT, HA or GRL ticket. */ + return FALSE; +} + + +/** + Rearrange the ticket to reside in the part of the list that's + beyond m_trans_sentinel. This effectively changes the ticket + life cycle, from automatic to manual: i.e. the ticket is no + longer released by MDL_context::release_transactional_locks() or + MDL_context::rollback_to_savepoint(), it must be released manually. +*/ + +void MDL_context::move_ticket_after_trans_sentinel(MDL_ticket *mdl_ticket) +{ + m_tickets.remove(mdl_ticket); + if (m_trans_sentinel == NULL) + { + m_trans_sentinel= mdl_ticket; + /* sic: linear from the number of transactional tickets acquired so-far! */ + m_tickets.push_back(mdl_ticket); + } + else + m_tickets.insert_after(m_trans_sentinel, mdl_ticket); +} diff --git a/sql/mdl.h b/sql/mdl.h new file mode 100644 index 00000000000..5eb86d2488b --- /dev/null +++ b/sql/mdl.h @@ -0,0 +1,722 @@ +#ifndef MDL_H +#define MDL_H +/* Copyright (C) 2007-2008 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#include "sql_plist.h" +#include <my_sys.h> +#include <m_string.h> +#include <mysql_com.h> + +class THD; + +class MDL_context; +class MDL_lock; +class MDL_ticket; +class Deadlock_detection_context; + +/** + Type of metadata lock request. + + @sa Comments for MDL_object_lock::can_grant_lock() and + MDL_global_lock::can_grant_lock() for details. +*/ + +enum enum_mdl_type { + /* + An intention exclusive metadata lock. Used only for global locks. + Owner of this type of lock can acquire upgradable exclusive locks on + individual objects. + Compatible with other IX locks, but is incompatible with global S lock. + */ + MDL_INTENTION_EXCLUSIVE= 0, + /* + A shared metadata lock. + To be used in cases when we are interested in object metadata only + and there is no intention to access object data (e.g. for stored + routines or during preparing prepared statements). + We also mis-use this type of lock for open HANDLERs, since lock + acquired by this statement has to be compatible with lock acquired + by LOCK TABLES ... WRITE statement, i.e. SNRW (We can't get by by + acquiring S lock at HANDLER ... OPEN time and upgrading it to SR + lock for HANDLER ... READ as it doesn't solve problem with need + to abort DML statements which wait on table level lock while having + open HANDLER in the same connection). + To avoid deadlock which may occur when SNRW lock is being upgraded to + X lock for table on which there is an active S lock which is owned by + thread which waits in its turn for table-level lock owned by thread + performing upgrade we have to use thr_abort_locks_for_thread() + facility in such situation. + This problem does not arise for locks on stored routines as we don't + use SNRW locks for them. It also does not arise when S locks are used + during PREPARE calls as table-level locks are not acquired in this + case. + */ + MDL_SHARED, + /* + A high priority shared metadata lock. + Used for cases when there is no intention to access object data (i.e. + data in the table). + "High priority" means that, unlike other shared locks, it is granted + ignoring pending requests for exclusive locks. Intended for use in + cases when we only need to access metadata and not data, e.g. when + filling an INFORMATION_SCHEMA table. + Since SH lock is compatible with SNRW lock, the connection that + holds SH lock lock should not try to acquire any kind of table-level + or row-level lock, as this can lead to a deadlock. Moreover, after + acquiring SH lock, the connection should not wait for any other + resource, as it might cause starvation for X locks and a potential + deadlock during upgrade of SNW or SNRW to X lock (e.g. if the + upgrading connection holds the resource that is being waited for). + */ + MDL_SHARED_HIGH_PRIO, + /* + A shared metadata lock for cases when there is an intention to read data + from table. + A connection holding this kind of lock can read table metadata and read + table data (after acquiring appropriate table and row-level locks). + This means that one can only acquire TL_READ, TL_READ_NO_INSERT, and + similar table-level locks on table if one holds SR MDL lock on it. + To be used for tables in SELECTs, subqueries, and LOCK TABLE ... READ + statements. + */ + MDL_SHARED_READ, + /* + A shared metadata lock for cases when there is an intention to modify + (and not just read) data in the table. + A connection holding SW lock can read table metadata and modify or read + table data (after acquiring appropriate table and row-level locks). + To be used for tables to be modified by INSERT, UPDATE, DELETE + statements, but not LOCK TABLE ... WRITE or DDL). Also taken by + SELECT ... FOR UPDATE. + */ + MDL_SHARED_WRITE, + /* + An upgradable shared metadata lock which blocks all attempts to update + table data, allowing reads. + A connection holding this kind of lock can read table metadata and read + table data. + Can be upgraded to X metadata lock. + Note, that since this type of lock is not compatible with SNRW or SW + lock types, acquiring appropriate engine-level locks for reading + (TL_READ* for MyISAM, shared row locks in InnoDB) should be + contention-free. + To be used for the first phase of ALTER TABLE, when copying data between + tables, to allow concurrent SELECTs from the table, but not UPDATEs. + */ + MDL_SHARED_NO_WRITE, + /* + An upgradable shared metadata lock which allows other connections + to access table metadata, but not data. + It blocks all attempts to read or update table data, while allowing + INFORMATION_SCHEMA and SHOW queries. + A connection holding this kind of lock can read table metadata modify and + read table data. + Can be upgraded to X metadata lock. + To be used for LOCK TABLES WRITE statement. + Not compatible with any other lock type except S and SH. + */ + MDL_SHARED_NO_READ_WRITE, + /* + An exclusive metadata lock. + A connection holding this lock can modify both table's metadata and data. + No other type of metadata lock can be granted while this lock is held. + To be used for CREATE/DROP/RENAME TABLE statements and for execution of + certain phases of other DDL statements. + */ + MDL_EXCLUSIVE, + /* This should be the last !!! */ + MDL_TYPE_END}; + + +/** Maximal length of key for metadata locking subsystem. */ +#define MAX_MDLKEY_LENGTH (1 + NAME_LEN + 1 + NAME_LEN + 1) + + +/** + Metadata lock object key. + + A lock is requested or granted based on a fully qualified name and type. + E.g. They key for a table consists of <0 (=table)>+<database>+<table name>. + Elsewhere in the comments this triple will be referred to simply as "key" + or "name". +*/ + +class MDL_key +{ +public: + /** + Object namespaces + + Different types of objects exist in different namespaces + - TABLE is for tables and views. + - FUNCTION is for stored functions. + - PROCEDURE is for stored procedures. + - TRIGGER is for triggers. + Note that although there isn't metadata locking on triggers, + it's necessary to have a separate namespace for them since + MDL_key is also used outside of the MDL subsystem. + */ + enum enum_mdl_namespace { GLOBAL=0, + TABLE, + FUNCTION, + PROCEDURE, + TRIGGER }; + + const uchar *ptr() const { return (uchar*) m_ptr; } + uint length() const { return m_length; } + + const char *db_name() const { return m_ptr + 1; } + uint db_name_length() const { return m_db_name_length; } + + const char *name() const { return m_ptr + m_db_name_length + 2; } + uint name_length() const { return m_length - m_db_name_length - 3; } + + enum_mdl_namespace mdl_namespace() const + { return (enum_mdl_namespace)(m_ptr[0]); } + + /** + Construct a metadata lock key from a triplet (mdl_namespace, + database and name). + + @remark The key for a table is <mdl_namespace>+<database name>+<table name> + + @param mdl_namespace Id of namespace of object to be locked + @param db Name of database to which the object belongs + @param name Name of of the object + @param key Where to store the the MDL key. + */ + void mdl_key_init(enum_mdl_namespace mdl_namespace, + const char *db, const char *name) + { + m_ptr[0]= (char) mdl_namespace; + m_db_name_length= (uint16) (strmov(m_ptr + 1, db) - m_ptr - 1); + m_length= (uint16) (strmov(m_ptr + m_db_name_length + 2, name) - m_ptr + 1); + } + void mdl_key_init(const MDL_key *rhs) + { + memcpy(m_ptr, rhs->m_ptr, rhs->m_length); + m_length= rhs->m_length; + m_db_name_length= rhs->m_db_name_length; + } + bool is_equal(const MDL_key *rhs) const + { + return (m_length == rhs->m_length && + memcmp(m_ptr, rhs->m_ptr, m_length) == 0); + } + /** + Compare two MDL keys lexicographically. + */ + int cmp(const MDL_key *rhs) const + { + /* + The key buffer is always '\0'-terminated. Since key + character set is utf-8, we can safely assume that no + character starts with a zero byte. + */ + return memcmp(m_ptr, rhs->m_ptr, min(m_length, rhs->m_length)+1); + } + + MDL_key(const MDL_key *rhs) + { + mdl_key_init(rhs); + } + MDL_key(enum_mdl_namespace namespace_arg, + const char *db_arg, const char *name_arg) + { + mdl_key_init(namespace_arg, db_arg, name_arg); + } + MDL_key() {} /* To use when part of MDL_request. */ + +private: + uint16 m_length; + uint16 m_db_name_length; + char m_ptr[MAX_MDLKEY_LENGTH]; +private: + MDL_key(const MDL_key &); /* not implemented */ + MDL_key &operator=(const MDL_key &); /* not implemented */ +}; + + + +/** + Hook class which via its methods specifies which members + of T should be used for participating in MDL lists. +*/ + +template <typename T, T* T::*next, T** T::*prev> +struct I_P_List_adapter +{ + static inline T **next_ptr(T *el) { return &(el->*next); } + + static inline T ***prev_ptr(T *el) { return &(el->*prev); } +}; + + +/** + A pending metadata lock request. + + A lock request and a granted metadata lock are represented by + different classes because they have different allocation + sites and hence different lifetimes. The allocation of lock requests is + controlled from outside of the MDL subsystem, while allocation of granted + locks (tickets) is controlled within the MDL subsystem. + + MDL_request is a C structure, you don't need to call a constructor + or destructor for it. +*/ + +class MDL_request +{ +public: + /** Type of metadata lock. */ + enum enum_mdl_type type; + + /** + Pointers for participating in the list of lock requests for this context. + */ + MDL_request *next_in_list; + MDL_request **prev_in_list; + /** + Pointer to the lock ticket object for this lock request. + Valid only if this lock request is satisfied. + */ + MDL_ticket *ticket; + + /** A lock is requested based on a fully qualified name and type. */ + MDL_key key; + +public: + void init(MDL_key::enum_mdl_namespace namespace_arg, + const char *db_arg, const char *name_arg, + enum_mdl_type mdl_type_arg); + void init(const MDL_key *key_arg, enum_mdl_type mdl_type_arg); + /** Set type of lock request. Can be only applied to pending locks. */ + inline void set_type(enum_mdl_type type_arg) + { + DBUG_ASSERT(ticket == NULL); + type= type_arg; + } + uint get_deadlock_weight() const; + + static MDL_request *create(MDL_key::enum_mdl_namespace mdl_namespace, + const char *db, const char *name, + enum_mdl_type mdl_type, MEM_ROOT *root); + + /* + This is to work around the ugliness of TABLE_LIST + compiler-generated assignment operator. It is currently used + in several places to quickly copy "most" of the members of the + table list. These places currently never assume that the mdl + request is carried over to the new TABLE_LIST, or shared + between lists. + + This method does not initialize the instance being assigned! + Use of init() for initialization after this assignment operator + is mandatory. Can only be used before the request has been + granted. + */ + MDL_request& operator=(const MDL_request &rhs) + { + ticket= NULL; + /* Do nothing, in particular, don't try to copy the key. */ + return *this; + } + /* Another piece of ugliness for TABLE_LIST constructor */ + MDL_request() {} + + MDL_request(const MDL_request *rhs) + :type(rhs->type), + ticket(NULL), + key(&rhs->key) + {} +}; + + +typedef void (*mdl_cached_object_release_hook)(void *); + +/** + A granted metadata lock. + + @warning MDL_ticket members are private to the MDL subsystem. + + @note Multiple shared locks on a same object are represented by a + single ticket. The same does not apply for other lock types. + + @note There are two groups of MDL_ticket members: + - "Externally accessible". These members can be accessed from + threads/contexts different than ticket owner in cases when + ticket participates in some list of granted or waiting tickets + for a lock. Therefore one should change these members before + including then to waiting/granted lists or while holding lock + protecting those lists. + - "Context private". Such members are private to thread/context + owning this ticket. I.e. they should not be accessed from other + threads/contexts. +*/ + +class MDL_ticket +{ +public: + /** + Pointers for participating in the list of lock requests for this context. + Context private. + */ + MDL_ticket *next_in_context; + MDL_ticket **prev_in_context; + /** + Pointers for participating in the list of satisfied/pending requests + for the lock. Externally accessible. + */ + MDL_ticket *next_in_lock; + MDL_ticket **prev_in_lock; +public: + bool has_pending_conflicting_lock() const; + + void *get_cached_object(); + void set_cached_object(void *cached_object, + mdl_cached_object_release_hook release_hook); + MDL_context *get_ctx() const { return m_ctx; } + bool is_upgradable_or_exclusive() const + { + return m_type == MDL_SHARED_NO_WRITE || + m_type == MDL_SHARED_NO_READ_WRITE || + m_type == MDL_EXCLUSIVE; + } + enum_mdl_type get_type() const { return m_type; } + MDL_lock *get_lock() const { return m_lock; } + void downgrade_exclusive_lock(enum_mdl_type type); + + bool has_stronger_or_equal_type(enum_mdl_type type) const; + + bool is_incompatible_when_granted(enum_mdl_type type) const; + bool is_incompatible_when_waiting(enum_mdl_type type) const; + +private: + friend class MDL_context; + + MDL_ticket(MDL_context *ctx_arg, enum_mdl_type type_arg) + : m_type(type_arg), + m_ctx(ctx_arg), + m_lock(NULL) + {} + + static MDL_ticket *create(MDL_context *ctx_arg, enum_mdl_type type_arg); + static void destroy(MDL_ticket *ticket); +private: + /** Type of metadata lock. Externally accessible. */ + enum enum_mdl_type m_type; + /** + Context of the owner of the metadata lock ticket. Externally accessible. + */ + MDL_context *m_ctx; + + /** + Pointer to the lock object for this lock ticket. Externally accessible. + */ + MDL_lock *m_lock; + +private: + MDL_ticket(const MDL_ticket &); /* not implemented */ + MDL_ticket &operator=(const MDL_ticket &); /* not implemented */ +}; + + +typedef I_P_List<MDL_request, I_P_List_adapter<MDL_request, + &MDL_request::next_in_list, + &MDL_request::prev_in_list>, + I_P_List_counter> + MDL_request_list; + +/** + Context of the owner of metadata locks. I.e. each server + connection has such a context. +*/ + +class MDL_context +{ +public: + typedef I_P_List<MDL_ticket, + I_P_List_adapter<MDL_ticket, + &MDL_ticket::next_in_context, + &MDL_ticket::prev_in_context> > + Ticket_list; + + typedef Ticket_list::Iterator Ticket_iterator; + + enum mdl_signal_type { NO_WAKE_UP = 0, + NORMAL_WAKE_UP, + VICTIM_WAKE_UP, + TIMEOUT_WAKE_UP }; + + MDL_context(); + void destroy(); + + bool try_acquire_lock(MDL_request *mdl_request); + bool acquire_lock(MDL_request *mdl_request); + bool acquire_locks(MDL_request_list *requests); + bool upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket); + + bool clone_ticket(MDL_request *mdl_request); + + bool wait_for_lock(MDL_request *mdl_request); + + void release_all_locks_for_name(MDL_ticket *ticket); + void release_lock(MDL_ticket *ticket); + + bool is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace, + const char *db, const char *name, + enum_mdl_type mdl_type); + + bool has_lock(MDL_ticket *mdl_savepoint, MDL_ticket *mdl_ticket); + + inline bool has_locks() const + { + return !m_tickets.is_empty(); + } + + MDL_ticket *mdl_savepoint() + { + /* + NULL savepoint represents the start of the transaction. + Checking for m_trans_sentinel also makes sure we never + return a pointer to HANDLER ticket as a savepoint. + */ + return m_tickets.front() == m_trans_sentinel ? NULL : m_tickets.front(); + } + + void set_trans_sentinel() + { + m_trans_sentinel= mdl_savepoint(); + } + MDL_ticket *trans_sentinel() const { return m_trans_sentinel; } + + void reset_trans_sentinel(MDL_ticket *sentinel_arg) + { + m_trans_sentinel= sentinel_arg; + } + void move_ticket_after_trans_sentinel(MDL_ticket *mdl_ticket); + + void release_transactional_locks(); + void rollback_to_savepoint(MDL_ticket *mdl_savepoint); + + inline THD *get_thd() const { return m_thd; } + + bool acquire_global_intention_exclusive_lock(MDL_request *mdl_request); + + /** + Wake up context which is waiting for a change of MDL_lock state. + */ + void awake(mdl_signal_type signal) + { + pthread_mutex_lock(&m_signal_lock); + m_signal= signal; + pthread_cond_signal(&m_signal_cond); + pthread_mutex_unlock(&m_signal_lock); + } + + void init(THD *thd_arg) { m_thd= thd_arg; } + + void set_needs_thr_lock_abort(bool needs_thr_lock_abort) + { + /* + @note In theory, this member should be modified under protection + of some lock since it can be accessed from different threads. + In practice, this is not necessary as code which reads this + value and so might miss the fact that value was changed will + always re-try reading it after small timeout and therefore + will see the new value eventually. + */ + m_needs_thr_lock_abort= TRUE; + } + bool get_needs_thr_lock_abort() const + { + return m_needs_thr_lock_abort; + } + + bool find_deadlock(Deadlock_detection_context *deadlock_ctx); +private: + /** + All MDL tickets acquired by this connection. + + The order of tickets in m_tickets list. + --------------------------------------- + The entire set of locks acquired by a connection + can be separated in two subsets: transactional and + non-transactional locks. + + Transactional locks are locks with automatic scope. They + are accumulated in the course of a transaction, and + released only on COMMIT, ROLLBACK or ROLLBACK TO SAVEPOINT. + They must not be (and never are) released manually, + i.e. with release_lock() call. + + Non-transactional locks are taken for locks that span + multiple transactions or savepoints. + These are: HANDLER SQL locks (HANDLER SQL is + transaction-agnostic), LOCK TABLES locks (you can COMMIT/etc + under LOCK TABLES, and the locked tables stay locked), and + SET GLOBAL READ_ONLY=1 global shared lock. + + Transactional locks are always prepended to the beginning + of the list. In other words, they are stored in reverse + temporal order. Thus, when we rollback to a savepoint, + we start popping and releasing tickets from the front + until we reach the last ticket acquired after the + savepoint. + + Non-transactional locks are always stored after + transactional ones, and among each other can be + split into three sets: + + [LOCK TABLES locks] [HANDLER locks] [GLOBAL READ LOCK locks] + + The following is known about these sets: + + * we can never have both HANDLER and LOCK TABLES locks + together -- HANDLER statements are prohibited under LOCK + TABLES, entering LOCK TABLES implicitly closes all open + HANDLERs. + * GLOBAL READ LOCK locks are always stored after LOCK TABLES + locks and after HANDLER locks. This is because one can't say + SET GLOBAL read_only=1 or FLUSH TABLES WITH READ LOCK + if one has locked tables. One can, however, LOCK TABLES + after having entered the read only mode. Note, that + subsequent LOCK TABLES statement will unlock the previous + set of tables, but not the GRL! + There are no HANDLER locks after GRL locks because + SET GLOBAL read_only performs a FLUSH TABLES WITH + READ LOCK internally, and FLUSH TABLES, in turn, implicitly + closes all open HANDLERs. + However, one can open a few HANDLERs after entering the + read only mode. + */ + Ticket_list m_tickets; + /** + Separates transactional and non-transactional locks + in m_tickets list, @sa m_tickets. + */ + MDL_ticket *m_trans_sentinel; + THD *m_thd; + /** + TRUE - if for this context we will break protocol and try to + acquire table-level locks while having only S lock on + some table. + To avoid deadlocks which might occur during concurrent + upgrade of SNRW lock on such object to X lock we have to + abort waits for table-level locks for such connections. + FALSE - Otherwise. + */ + bool m_needs_thr_lock_abort; + + /** + Read-write lock protecting m_waiting_for member. + + TODO/FIXME: Replace with RW-lock which will prefer readers + on all platforms and not only on Linux. + */ + rw_lock_t m_waiting_for_lock; + MDL_ticket *m_waiting_for; + uint m_deadlock_weight; + /** + Condvar which is used for waiting until this context's pending + request can be satisfied or this thread has to perform actions + to resolve a potential deadlock (we subscribe to such + notification by adding a ticket corresponding to the request + to an appropriate queue of waiters). + */ + pthread_mutex_t m_signal_lock; + pthread_cond_t m_signal_cond; + mdl_signal_type m_signal; + +private: + MDL_ticket *find_ticket(MDL_request *mdl_req, + bool *is_transactional); + void release_locks_stored_before(MDL_ticket *sentinel); + bool acquire_lock_impl(MDL_request *mdl_request); + + bool find_deadlock(); + + void will_wait_for(MDL_ticket *pending_ticket) + { + rw_wrlock(&m_waiting_for_lock); + m_waiting_for= pending_ticket; + rw_unlock(&m_waiting_for_lock); + } + + void set_deadlock_weight(uint weight) + { + /* + m_deadlock_weight should not be modified while m_waiting_for is + non-NULL as in this case this context might participate in deadlock + and so m_deadlock_weight can be accessed from other threads. + */ + DBUG_ASSERT(m_waiting_for == NULL); + m_deadlock_weight= weight; + } + + void stop_waiting() + { + rw_wrlock(&m_waiting_for_lock); + m_waiting_for= NULL; + rw_unlock(&m_waiting_for_lock); + } + + void wait_reset() + { + pthread_mutex_lock(&m_signal_lock); + m_signal= NO_WAKE_UP; + pthread_mutex_unlock(&m_signal_lock); + } + + mdl_signal_type wait(); + mdl_signal_type timed_wait(ulong timeout); + + mdl_signal_type peek_signal() + { + mdl_signal_type result; + pthread_mutex_lock(&m_signal_lock); + result= m_signal; + pthread_mutex_unlock(&m_signal_lock); + return result; + } + +private: + MDL_context(const MDL_context &rhs); /* not implemented */ + MDL_context &operator=(MDL_context &rhs); /* not implemented */ +}; + + +void mdl_init(); +void mdl_destroy(); + + +/* + Functions in the server's kernel used by metadata locking subsystem. +*/ + +extern bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use, + bool needs_thr_lock_abort); +extern void mysql_ha_flush(THD *thd); +extern "C" const char *set_thd_proc_info(THD *thd, const char *info, + const char *calling_function, + const char *calling_file, + const unsigned int calling_line); +#ifndef DBUG_OFF +extern mysql_mutex_t LOCK_open; +#endif + +#endif diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 33ca7eee89b..759807b3ac3 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -932,15 +932,6 @@ bool parse_sql(THD *thd, Parser_state *parser_state, Object_creation_ctx *creation_ctx); -enum enum_mysql_completiontype { - ROLLBACK_RELEASE=-2, ROLLBACK=1, ROLLBACK_AND_CHAIN=7, - COMMIT_RELEASE=-1, COMMIT=0, COMMIT_AND_CHAIN=6 -}; - -bool begin_trans(THD *thd); -bool end_active_trans(THD *thd); -int end_trans(THD *thd, enum enum_mysql_completiontype completion); - Item *negate_expression(THD *thd, Item *expr); /* log.cc */ @@ -1036,8 +1027,8 @@ struct Query_cache_query_flags #define query_cache_is_cacheable_query(L) 0 #endif /*HAVE_QUERY_CACHE*/ -int write_bin_log(THD *thd, bool clear_error, - char const *query, ulong query_length); +void write_bin_log(THD *thd, bool clear_error, + char const *query, ulong query_length); /* sql_connect.cc */ int check_user(THD *thd, enum enum_server_command command, @@ -1065,7 +1056,6 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, bool drop_temporary, bool drop_view, bool log_query); bool quick_rm_table(handlerton *base,const char *db, const char *table_name, uint flags); -void close_cached_table(THD *thd, TABLE *table); bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent); bool do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db, char *new_table_name, char *new_table_alias, @@ -1093,6 +1083,7 @@ bool mysql_new_select(LEX *lex, bool move_down); void create_select_for_variable(const char *var_name); void mysql_init_multi_delete(LEX *lex); bool multi_delete_set_locks_and_link_aux_tables(LEX *lex); +void create_table_set_open_action_and_adjust_tables(LEX *lex); void init_max_user_conn(void); void init_update_queries(void); void free_max_user_conn(void); @@ -1107,11 +1098,8 @@ bool check_dup(const char *db, const char *name, TABLE_LIST *tables); bool compare_record(TABLE *table); bool append_file_to_dir(THD *thd, const char **filename_ptr, const char *table_name); -void wait_while_table_is_used(THD *thd, TABLE *table, - enum ha_extra_function function); -bool table_cache_init(void); -void table_cache_free(void); bool table_def_init(void); +void table_def_start_shutdown(void); void table_def_free(void); void assign_new_table_id(TABLE_SHARE *share); uint cached_open_tables(void); @@ -1236,10 +1224,9 @@ int prepare_create_field(Create_field *sql_field, longlong table_flags); CHARSET_INFO* get_sql_field_charset(Create_field *sql_field, HA_CREATE_INFO *create_info); -bool mysql_create_table(THD *thd,const char *db, const char *table_name, +bool mysql_create_table(THD *thd, TABLE_LIST *create_table, HA_CREATE_INFO *create_info, - Alter_info *alter_info, - bool tmp_table, uint select_field_count); + Alter_info *alter_info); bool mysql_create_table_no_lock(THD *thd, const char *db, const char *table_name, HA_CREATE_INFO *create_info, @@ -1293,33 +1280,22 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create); uint create_table_def_key(THD *thd, char *key, TABLE_LIST *table_list, bool tmp_table); TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key, - uint key_length, uint db_flags, int *error); -void release_table_share(TABLE_SHARE *share, enum release_type type); + uint key_length, uint db_flags, int *error, + my_hash_value_type hash_value); +void release_table_share(TABLE_SHARE *share); TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name); TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update, uint lock_flags); -TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem, - bool *refresh, uint flags); -bool name_lock_locked_table(THD *thd, TABLE_LIST *tables); -bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in); -TABLE *table_cache_insert_placeholder(THD *thd, const char *key, - uint key_length); -bool lock_table_name_if_not_cached(THD *thd, const char *db, - const char *table_name, TABLE **table); -TABLE *find_locked_table(THD *thd, const char *db,const char *table_name); -void detach_merge_children(TABLE *table, bool clear_refs); -bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last, - TABLE_LIST *new_child_list, TABLE_LIST **new_last); -bool reopen_table(TABLE *table); -bool reopen_tables(THD *thd,bool get_locks,bool in_refresh); +bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem, + Open_table_context *backoff, uint flags); +bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, + char *cache_key, uint cache_key_length, + MEM_ROOT *mem_root, uint flags); +TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name); +TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db, + const char *table_name, + bool no_error); thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table); -void close_data_files_and_morph_locks(THD *thd, const char *db, - const char *table_name); -void close_handle_and_leave_table_as_lock(TABLE *table); -bool wait_for_tables(THD *thd); -bool table_is_used(TABLE *table, bool wait_for_name_lock); -TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name); -void abort_locked_tables(THD *thd,const char *db, const char *table_name); void execute_init_command(THD *thd, LEX_STRING *init_command, mysql_rwlock_t *var_mutex); extern Field *not_found_field; @@ -1443,7 +1419,7 @@ bool mysql_ha_close(THD *thd, TABLE_LIST *tables); bool mysql_ha_read(THD *, TABLE_LIST *,enum enum_ha_read_modes,char *, List<Item> *,enum ha_rkey_function,Item *,ha_rows,ha_rows); void mysql_ha_flush(THD *thd); -void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables, bool is_locked); +void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables); void mysql_ha_cleanup(THD *thd); /* sql_base.cc */ @@ -1473,9 +1449,12 @@ void add_join_on(TABLE_LIST *b,Item *expr); void add_join_natural(TABLE_LIST *a,TABLE_LIST *b,List<String> *using_fields, SELECT_LEX *lex); bool add_proc_to_list(THD *thd, Item *item); -void unlink_open_table(THD *thd, TABLE *find, bool unlock); +bool wait_while_table_is_used(THD *thd, TABLE *table, + enum ha_extra_function function); void drop_open_table(THD *thd, TABLE *table, const char *db_name, const char *table_name); +void close_all_tables_for_name(THD *thd, TABLE_SHARE *share, + bool remove_from_locked_tables); void update_non_unique_table_error(TABLE_LIST *update, const char *operation, TABLE_LIST *duplicate); @@ -1554,25 +1533,44 @@ int setup_ftfuncs(SELECT_LEX* select); int init_ftfuncs(THD *thd, SELECT_LEX* select, bool no_order); void wait_for_condition(THD *thd, mysql_mutex_t *mutex, mysql_cond_t *cond); -int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags); +bool open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags, + Prelocking_strategy *prelocking_strategy); +inline bool +open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags) +{ + DML_prelocking_strategy prelocking_strategy; + + return open_tables(thd, tables, counter, flags, &prelocking_strategy); +} /* open_and_lock_tables with optional derived handling */ -int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived); +bool open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, + bool derived, uint flags, + Prelocking_strategy *prelocking_strategy); +inline bool open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, + bool derived, uint flags) +{ + DML_prelocking_strategy prelocking_strategy; + + return open_and_lock_tables_derived(thd, tables, derived, flags, + &prelocking_strategy); +} /* simple open_and_lock_tables without derived handling */ -inline int simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables) +inline bool simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables) { - return open_and_lock_tables_derived(thd, tables, FALSE); + return open_and_lock_tables_derived(thd, tables, FALSE, 0); } /* open_and_lock_tables with derived handling */ -inline int open_and_lock_tables(THD *thd, TABLE_LIST *tables) +inline bool open_and_lock_tables(THD *thd, TABLE_LIST *tables) { - return open_and_lock_tables_derived(thd, tables, TRUE); + return open_and_lock_tables_derived(thd, tables, TRUE, 0); } /* simple open_and_lock_tables without derived handling for single table */ TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l, - thr_lock_type lock_type); + thr_lock_type lock_type, uint flags); bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags); -int lock_tables(THD *thd, TABLE_LIST *tables, uint counter, bool *need_reopen); -int decide_logging_format(THD *thd, TABLE_LIST *tables); +bool lock_tables(THD *thd, TABLE_LIST *tables, uint counter, uint flags, + bool *need_reopen); +bool decide_logging_format(THD *thd, TABLE_LIST *tables); TABLE *open_temporary_table(THD *thd, const char *path, const char *db, const char *table_name, bool link_in_list); bool rm_temporary_table(handlerton *base, char *path); @@ -1580,7 +1578,8 @@ void free_io_cache(TABLE *entry); void intern_close_table(TABLE *entry); bool close_thread_table(THD *thd, TABLE **table_ptr); void close_temporary_tables(THD *thd); -void close_tables_for_reopen(THD *thd, TABLE_LIST **tables); +void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, + MDL_ticket *mdl_savepoint); TABLE_LIST *find_table_in_list(TABLE_LIST *table, TABLE_LIST *TABLE_LIST::*link, const char *db_name, @@ -1592,10 +1591,10 @@ TABLE *find_temporary_table(THD *thd, TABLE_LIST *table_list); int drop_temporary_table(THD *thd, TABLE_LIST *table_list); void close_temporary_table(THD *thd, TABLE *table, bool free_share, bool delete_table); +void mark_tmp_table_for_reuse(TABLE *table); void close_temporary(TABLE *table, bool free_share, bool delete_table); bool rename_temporary_table(THD* thd, TABLE *table, const char *new_db, const char *table_name); -void remove_db_from_cache(const char *db); void flush_tables(); bool is_equal(const LEX_STRING *a, const LEX_STRING *b); char *make_log_name(char *buff, const char *name, const char* log_ext); @@ -1623,13 +1622,10 @@ char *generate_partition_syntax(partition_info *part_info, Alter_info *alter_info); #endif -/* bits for last argument to remove_table_from_cache() */ -#define RTFC_NO_FLAG 0x0000 -#define RTFC_OWNED_BY_THD_FLAG 0x0001 -#define RTFC_WAIT_OTHER_THREAD_FLAG 0x0002 -#define RTFC_CHECK_KILLED_FLAG 0x0004 -bool remove_table_from_cache(THD *thd, const char *db, const char *table, - uint flags); +enum enum_tdc_remove_table_type {TDC_RT_REMOVE_ALL, TDC_RT_REMOVE_NOT_OWN, + TDC_RT_REMOVE_UNUSED}; +void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, + const char *db, const char *table_name); #define NORMAL_PART_NAME 0 #define TEMP_PART_NAME 1 @@ -1643,7 +1639,7 @@ void create_subpartition_name(char *out, const char *in1, typedef struct st_lock_param_type { - TABLE_LIST table_list; + TABLE_LIST *table_list; ulonglong copied; ulonglong deleted; THD *thd; @@ -1654,7 +1650,6 @@ typedef struct st_lock_param_type const char *db; const char *table_name; uchar *pack_frm_data; - enum thr_lock_type old_lock_type; uint key_count; uint db_options; size_t pack_frm_len; @@ -1748,19 +1743,18 @@ extern mysql_mutex_t LOCK_gdl; bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags); int abort_and_upgrade_lock(ALTER_PARTITION_PARAM_TYPE *lpt); void close_open_tables_and_downgrade(ALTER_PARTITION_PARAM_TYPE *lpt); -void mysql_wait_completed_table(ALTER_PARTITION_PARAM_TYPE *lpt, TABLE *my_table); /* Functions to work with system tables. */ bool open_system_tables_for_read(THD *thd, TABLE_LIST *table_list, - Open_tables_state *backup); -void close_system_tables(THD *thd, Open_tables_state *backup); + Open_tables_backup *backup); +void close_system_tables(THD *thd, Open_tables_backup *backup); TABLE *open_system_table_for_update(THD *thd, TABLE_LIST *one_table); -TABLE *open_log_table(THD *thd, TABLE_LIST *one_table, Open_tables_state *backup); -void close_log_table(THD *thd, Open_tables_state *backup); +TABLE *open_log_table(THD *thd, TABLE_LIST *one_table, Open_tables_backup *backup); +void close_log_table(THD *thd, Open_tables_backup *backup); bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, - bool wait_for_refresh, bool wait_for_placeholders); + bool wait_for_refresh); bool close_cached_connection_tables(THD *thd, bool wait_for_refresh, LEX_STRING *connect_string, bool have_lock = FALSE); @@ -2086,14 +2080,15 @@ extern DATE_TIME_FORMAT global_date_format, global_datetime_format, global_time_ extern KNOWN_DATE_TIME_FORMAT known_date_time_formats[]; extern String null_string; -extern HASH open_cache, lock_db_cache; +extern HASH table_def_cache, lock_db_cache; extern TABLE *unused_tables; +extern uint table_cache_count; extern const char* any_db; extern const LEX_STRING view_type; extern scheduler_functions thread_scheduler; extern TYPELIB thread_handling_typelib; -extern uint8 uc_update_queries[SQLCOM_END+1]; extern uint sql_command_flags[]; +extern uint server_command_flags[]; extern TYPELIB log_output_typelib; extern const char *log_output_names[]; @@ -2132,16 +2127,39 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, /* mysql_lock_tables() and open_table() flags bits */ #define MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK 0x0001 #define MYSQL_LOCK_IGNORE_FLUSH 0x0002 -#define MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN 0x0004 -#define MYSQL_OPEN_TEMPORARY_ONLY 0x0008 -#define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY 0x0010 -#define MYSQL_LOCK_PERF_SCHEMA 0x0020 +#define MYSQL_OPEN_TEMPORARY_ONLY 0x0004 +#define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY 0x0008 +#define MYSQL_LOCK_PERF_SCHEMA 0x0010 +#define MYSQL_OPEN_TAKE_UPGRADABLE_MDL 0x0020 +/** + Do not try to acquire a metadata lock on the table: we + already have one. +*/ +#define MYSQL_OPEN_HAS_MDL_LOCK 0x0040 +/** + If in locked tables mode, ignore the locked tables and get + a new instance of the table. +*/ +#define MYSQL_OPEN_GET_NEW_TABLE 0x0080 +/** Don't look up the table in the list of temporary tables. */ +#define MYSQL_OPEN_SKIP_TEMPORARY 0x0100 +/** Fail instead of waiting when conficting metadata lock is discovered. */ +#define MYSQL_OPEN_FAIL_ON_MDL_CONFLICT 0x0200 +/** Open tables using MDL_SHARED lock instead of one specified in parser. */ +#define MYSQL_OPEN_FORCE_SHARED_MDL 0x0400 + +/** Please refer to the internals manual. */ +#define MYSQL_OPEN_REOPEN (MYSQL_LOCK_IGNORE_FLUSH |\ + MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK |\ + MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY |\ + MYSQL_OPEN_GET_NEW_TABLE |\ + MYSQL_OPEN_SKIP_TEMPORARY |\ + MYSQL_OPEN_HAS_MDL_LOCK) void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock); void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock); void mysql_unlock_some_tables(THD *thd, TABLE **table,uint count); -void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table, - bool always_unlock); +void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table); void mysql_lock_abort(THD *thd, TABLE *table, bool upgrade_lock); void mysql_lock_downgrade_write(THD *thd, TABLE *table, thr_lock_type new_lock_type); @@ -2149,30 +2167,15 @@ bool mysql_lock_abort_for_thread(THD *thd, TABLE *table); MYSQL_LOCK *mysql_lock_merge(MYSQL_LOCK *a,MYSQL_LOCK *b); TABLE_LIST *mysql_lock_have_duplicate(THD *thd, TABLE_LIST *needle, TABLE_LIST *haystack); -bool lock_global_read_lock(THD *thd); -void unlock_global_read_lock(THD *thd); -bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh, - bool is_not_commit); -void start_waiting_global_read_lock(THD *thd); -bool make_global_read_lock_block_commit(THD *thd); -bool set_protect_against_global_read_lock(void); -void unset_protect_against_global_read_lock(void); void broadcast_refresh(void); /* Lock based on name */ -int lock_and_wait_for_table_name(THD *thd, TABLE_LIST *table_list); -int lock_table_name(THD *thd, TABLE_LIST *table_list, bool check_in_use); -void unlock_table_name(THD *thd, TABLE_LIST *table_list); -bool wait_for_locked_table_names(THD *thd, TABLE_LIST *table_list); bool lock_table_names(THD *thd, TABLE_LIST *table_list); -void unlock_table_names(THD *thd, TABLE_LIST *table_list, - TABLE_LIST *last_table); -bool lock_table_names_exclusively(THD *thd, TABLE_LIST *table_list); -bool is_table_name_exclusively_locked_by_this_thread(THD *thd, - TABLE_LIST *table_list); -bool is_table_name_exclusively_locked_by_this_thread(THD *thd, uchar *key, - int key_length); +void unlock_table_names(THD *thd); +/* Lock based on stored routine name */ +bool lock_routine_name(THD *thd, bool is_function, const char *db, + const char *name); /* old unireg functions */ diff --git a/sql/mysqld.cc b/sql/mysqld.cc index f07410e0d43..68d4d832c9a 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -1417,8 +1417,6 @@ void clean_up(bool print_message) grant_free(); #endif query_cache_destroy(); - table_cache_free(); - table_def_free(); hostname_cache_free(); item_user_lock_free(); lex_free(); /* Free some memory */ @@ -1429,12 +1427,15 @@ void clean_up(bool print_message) udf_free(); #endif } + table_def_start_shutdown(); plugin_shutdown(); ha_end(); if (tc_log) tc_log->close(); delegates_destroy(); xid_cache_free(); + table_def_free(); + mdl_destroy(); key_caches.delete_elements((void (*)(const char*, uchar*)) free_key_cache); multi_keycache_free(); free_status_vars(); @@ -3894,7 +3895,8 @@ static int init_server_components() We need to call each of these following functions to ensure that all things are initialized so that unireg_abort() doesn't fail */ - if (table_cache_init() | table_def_init() | hostname_cache_init()) + mdl_init(); + if (table_def_init() | hostname_cache_init()) unireg_abort(1); query_cache_set_min_res_unit(query_cache_min_res_unit); @@ -3936,6 +3938,9 @@ static int init_server_components() } } + proc_info_hook= (const char *(*)(void *, const char *, const char *, + const char *, const unsigned int)) + set_thd_proc_info; #ifdef WITH_PERFSCHEMA_STORAGE_ENGINE /* Parsing the performance schema command line option may have reported diff --git a/sql/rpl_injector.cc b/sql/rpl_injector.cc index 666622dbac4..d47c49ed515 100644 --- a/sql/rpl_injector.cc +++ b/sql/rpl_injector.cc @@ -15,6 +15,7 @@ #include "mysql_priv.h" #include "rpl_injector.h" +#include "transaction.h" /* injector::transaction - member definitions @@ -35,7 +36,7 @@ injector::transaction::transaction(MYSQL_BIN_LOG *log, THD *thd) m_start_pos.m_file_name= my_strdup(log_info.log_file_name, MYF(0)); m_start_pos.m_file_pos= log_info.pos; - begin_trans(m_thd); + trans_begin(m_thd); thd->set_current_stmt_binlog_row_based(); } @@ -58,14 +59,10 @@ injector::transaction::~transaction() my_free(the_memory, MYF(0)); } -/** - @retval 0 transaction committed - @retval 1 transaction rolled back - */ int injector::transaction::commit() { DBUG_ENTER("injector::transaction::commit()"); - int error= m_thd->binlog_flush_pending_rows_event(true); + m_thd->binlog_flush_pending_rows_event(true); /* Cluster replication does not preserve statement or transaction boundaries of the master. Instead, a new @@ -85,11 +82,16 @@ int injector::transaction::commit() is committed by committing the statement transaction explicitly. */ - error |= ha_autocommit_or_rollback(m_thd, error); - end_trans(m_thd, error ? ROLLBACK : COMMIT); - DBUG_RETURN(error); + trans_commit_stmt(m_thd); + if (!trans_commit(m_thd)) + { + close_thread_tables(m_thd); + m_thd->mdl_context.release_transactional_locks(); + } + DBUG_RETURN(0); } + int injector::transaction::use_table(server_id_type sid, table tbl) { DBUG_ENTER("injector::transaction::use_table"); @@ -113,17 +115,16 @@ int injector::transaction::write_row (server_id_type sid, table tbl, record_type record) { DBUG_ENTER("injector::transaction::write_row(...)"); - - int error= 0; - if (error= check_state(ROW_STATE)) + + if (int error= check_state(ROW_STATE)) DBUG_RETURN(error); server_id_type save_id= m_thd->server_id; m_thd->set_server_id(sid); - error= m_thd->binlog_write_row(tbl.get_table(), tbl.is_transactional(), - cols, colcnt, record); + m_thd->binlog_write_row(tbl.get_table(), tbl.is_transactional(), + cols, colcnt, record); m_thd->set_server_id(save_id); - DBUG_RETURN(error); + DBUG_RETURN(0); } @@ -133,16 +134,15 @@ int injector::transaction::delete_row(server_id_type sid, table tbl, { DBUG_ENTER("injector::transaction::delete_row(...)"); - int error= 0; - if (error= check_state(ROW_STATE)) + if (int error= check_state(ROW_STATE)) DBUG_RETURN(error); server_id_type save_id= m_thd->server_id; m_thd->set_server_id(sid); - error= m_thd->binlog_delete_row(tbl.get_table(), tbl.is_transactional(), - cols, colcnt, record); + m_thd->binlog_delete_row(tbl.get_table(), tbl.is_transactional(), + cols, colcnt, record); m_thd->set_server_id(save_id); - DBUG_RETURN(error); + DBUG_RETURN(0); } @@ -152,16 +152,15 @@ int injector::transaction::update_row(server_id_type sid, table tbl, { DBUG_ENTER("injector::transaction::update_row(...)"); - int error= 0; - if (error= check_state(ROW_STATE)) + if (int error= check_state(ROW_STATE)) DBUG_RETURN(error); server_id_type save_id= m_thd->server_id; m_thd->set_server_id(sid); - error= m_thd->binlog_update_row(tbl.get_table(), tbl.is_transactional(), - cols, colcnt, before, after); + m_thd->binlog_update_row(tbl.get_table(), tbl.is_transactional(), + cols, colcnt, before, after); m_thd->set_server_id(save_id); - DBUG_RETURN(error); + DBUG_RETURN(0); } diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index a982cfd25a4..54124ff30e4 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -20,6 +20,7 @@ #include <my_dir.h> // For MY_STAT #include "sql_repl.h" // For check_binlog_magic #include "rpl_utility.h" +#include "transaction.h" static int count_relay_log_space(Relay_log_info* rli); @@ -1216,12 +1217,13 @@ void Relay_log_info::cleanup_context(THD *thd, bool error) */ if (error) { - ha_autocommit_or_rollback(thd, 1); // if a "statement transaction" - end_trans(thd, ROLLBACK); // if a "real transaction" + trans_rollback_stmt(thd); // if a "statement transaction" + trans_rollback(thd); // if a "real transaction" } m_table_map.clear_tables(); - close_thread_tables(thd); - clear_tables_to_lock(); + slave_close_thread_tables(thd); + if (error) + thd->mdl_context.release_transactional_locks(); clear_flag(IN_STMT); /* Cleanup for the flags that have been set at do_apply_event. @@ -1249,4 +1251,9 @@ void Relay_log_info::clear_tables_to_lock() DBUG_ASSERT(tables_to_lock == NULL && tables_to_lock_count == 0); } +void Relay_log_info::slave_close_thread_tables(THD *thd) +{ + close_thread_tables(thd); + clear_tables_to_lock(); +} #endif diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h index c7bef125d40..838782e1de8 100644 --- a/sql/rpl_rli.h +++ b/sql/rpl_rli.h @@ -353,6 +353,7 @@ public: bool cached_charset_compare(char *charset) const; void cleanup_context(THD *, bool); + void slave_close_thread_tables(THD *); void clear_tables_to_lock(); /* diff --git a/sql/set_var.cc b/sql/set_var.cc index 73989b9c96f..a30fbdc1edd 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -21,6 +21,7 @@ #include "mysql_priv.h" #include "sys_vars_shared.h" +#include "transaction.h" static HASH system_variable_hash; static PolyLock_mutex PLock_global_system_variables(&LOCK_global_system_variables); diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index d19dd61bdde..f40c0e9bfe1 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -6242,6 +6242,8 @@ ER_SLAVE_IGNORE_SERVER_IDS eng "The requested server id %d clashes with the slave startup option --replicate-same-server-id" ER_QUERY_CACHE_DISABLED eng "Query cache is disabled; restart the server with query_cache_type=1 to enable it" +ER_WARN_I_S_SKIPPED_TABLE + eng "Table '%s'.'%s' was skipped since its definition is being modified by concurrent DDL statement" ER_SAME_NAME_PARTITION_FIELD eng "Duplicate partition field name '%-.192s'" ER_PARTITION_COLUMN_LIST_ERROR diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index 84cd6247493..97f4d71cd50 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -6242,6 +6242,8 @@ ER_UNKNOWN_LOCALE ER_SLAVE_IGNORE_SERVER_IDS eng "The requested server id %d clashes with the slave startup option --replicate-same-server-id" +ER_WARN_I_S_SKIPPED_TABLE + eng "Table '%s'.'%s' was skipped since its definition is being modified by concurrent DDL statement" ER_SAME_NAME_PARTITION_FIELD eng "Duplicate partition field name '%-.192s'" ER_PARTITION_COLUMN_LIST_ERROR diff --git a/sql/slave.cc b/sql/slave.cc index 654ccfe72b5..cbff1ba3fd2 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -34,6 +34,7 @@ #include "sql_repl.h" #include "rpl_filter.h" #include "repl_failsafe.h" +#include "transaction.h" #include <thr_alarm.h> #include <my_dir.h> #include <sql_common.h> @@ -2481,7 +2482,9 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli) else { exec_res= 0; - end_trans(thd, ROLLBACK); + trans_rollback(thd); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); /* chance for concurrent connection to get more locks */ safe_sleep(thd, min(rli->trans_retries, MAX_SLAVE_RETRY_PAUSE), (CHECK_KILLED_FUNC)sql_slave_killed, (void*)rli); diff --git a/sql/sp.cc b/sql/sp.cc index 0ed4855e5d3..840268e2e22 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -17,7 +17,6 @@ #include "sp.h" #include "sp_head.h" #include "sp_cache.h" -#include "sql_trigger.h" #include <my_user.h> @@ -421,12 +420,13 @@ static Proc_table_intact proc_table_intact; \# Pointer to TABLE object of mysql.proc */ -TABLE *open_proc_table_for_read(THD *thd, Open_tables_state *backup) +TABLE *open_proc_table_for_read(THD *thd, Open_tables_backup *backup) { + TABLE_LIST table; + DBUG_ENTER("open_proc_table_for_read"); - TABLE_LIST table; - table.init_one_table("mysql", "proc", TL_READ); + table.init_one_table("mysql", 5, "proc", 4, "proc", TL_READ); if (open_system_tables_for_read(thd, &table, backup)) DBUG_RETURN(NULL); @@ -456,11 +456,11 @@ TABLE *open_proc_table_for_read(THD *thd, Open_tables_state *backup) static TABLE *open_proc_table_for_update(THD *thd) { + TABLE_LIST table_list; + TABLE *table; DBUG_ENTER("open_proc_table_for_update"); - TABLE *table; - TABLE_LIST table_list; - table_list.init_one_table("mysql", "proc", TL_WRITE); + table_list.init_one_table("mysql", 5, "proc", 4, "proc", TL_WRITE); if (!(table= open_system_table_for_update(thd, &table_list))) DBUG_RETURN(NULL); @@ -471,6 +471,7 @@ static TABLE *open_proc_table_for_update(THD *thd) close_thread_tables(thd); DBUG_RETURN(NULL); + } @@ -556,7 +557,7 @@ db_find_routine(THD *thd, int type, sp_name *name, sp_head **sphp) String str(buff, sizeof(buff), &my_charset_bin); bool saved_time_zone_used= thd->time_zone_used; ulong sql_mode, saved_mode= thd->variables.sql_mode; - Open_tables_state open_tables_state_backup; + Open_tables_backup open_tables_state_backup; Stored_program_creation_ctx *creation_ctx; DBUG_ENTER("db_find_routine"); @@ -928,6 +929,11 @@ sp_create_routine(THD *thd, int type, sp_head *sp) */ thd->clear_current_stmt_binlog_row_based(); + /* Grab an exclusive MDL lock. */ + if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION, + sp->m_db.str, sp->m_name.str)) + DBUG_RETURN(SP_OPEN_TABLE_FAILED); + saved_count_cuted_fields= thd->count_cuted_fields; thd->count_cuted_fields= CHECK_FIELD_WARN; @@ -1094,7 +1100,10 @@ sp_create_routine(THD *thd, int type, sp_head *sp) ret= SP_OK; if (table->file->ha_write_row(table->record[0])) ret= SP_WRITE_ROW_FAILED; - else if (mysql_bin_log.is_open()) + if (ret == SP_OK) + sp_cache_invalidate(); + + if (ret == SP_OK && mysql_bin_log.is_open()) { thd->clear_error(); @@ -1118,13 +1127,11 @@ sp_create_routine(THD *thd, int type, sp_head *sp) /* restore sql_mode when binloging */ thd->variables.sql_mode= saved_mode; /* Such a statement can always go directly to binlog, no trans cache */ - if (thd->binlog_query(THD::MYSQL_QUERY_TYPE, - log_query.c_ptr(), log_query.length(), - FALSE, FALSE, 0)) - ret= SP_INTERNAL_ERROR; + thd->binlog_query(THD::MYSQL_QUERY_TYPE, + log_query.c_ptr(), log_query.length(), + FALSE, FALSE, 0); thd->variables.sql_mode= 0; } - } done: @@ -1170,6 +1177,11 @@ sp_drop_routine(THD *thd, int type, sp_name *name) */ thd->clear_current_stmt_binlog_row_based(); + /* Grab an exclusive MDL lock. */ + if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION, + name->m_db.str, name->m_name.str)) + DBUG_RETURN(SP_DELETE_ROW_FAILED); + if (!(table= open_proc_table_for_update(thd))) DBUG_RETURN(SP_OPEN_TABLE_FAILED); if ((ret= db_find_routine_aux(thd, type, name, table)) == SP_OK) @@ -1180,9 +1192,22 @@ sp_drop_routine(THD *thd, int type, sp_name *name) if (ret == SP_OK) { - if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) - ret= SP_INTERNAL_ERROR; + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); sp_cache_invalidate(); + + /* + A lame workaround for lack of cache flush: + make sure the routine is at least gone from the + local cache. + */ + { + sp_head *sp; + sp_cache **spc= (type == TYPE_ENUM_FUNCTION ? + &thd->sp_func_cache : &thd->sp_proc_cache); + sp= sp_cache_lookup(spc, name); + if (sp) + sp_cache_flush_obsolete(spc, &sp); + } } close_thread_tables(thd); @@ -1218,6 +1243,12 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics) DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE || type == TYPE_ENUM_FUNCTION); + + /* Grab an exclusive MDL lock. */ + if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION, + name->m_db.str, name->m_name.str)) + DBUG_RETURN(SP_OPEN_TABLE_FAILED); + /* This statement will be replicated as a statement, even when using row-based replication. The flag will be reset at the end of the @@ -1229,6 +1260,30 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics) DBUG_RETURN(SP_OPEN_TABLE_FAILED); if ((ret= db_find_routine_aux(thd, type, name, table)) == SP_OK) { + if (type == TYPE_ENUM_FUNCTION && ! trust_function_creators && + mysql_bin_log.is_open() && + (chistics->daccess == SP_CONTAINS_SQL || + chistics->daccess == SP_MODIFIES_SQL_DATA)) + { + char *ptr; + bool is_deterministic; + ptr= get_field(thd->mem_root, + table->field[MYSQL_PROC_FIELD_DETERMINISTIC]); + if (ptr == NULL) + { + ret= SP_INTERNAL_ERROR; + goto err; + } + is_deterministic= ptr[0] == 'N' ? FALSE : TRUE; + if (!is_deterministic) + { + my_message(ER_BINLOG_UNSAFE_ROUTINE, + ER(ER_BINLOG_UNSAFE_ROUTINE), MYF(0)); + ret= SP_INTERNAL_ERROR; + goto err; + } + } + store_record(table,record[1]); table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; ((Field_timestamp *)table->field[MYSQL_PROC_FIELD_MODIFIED])->set_time(); @@ -1251,11 +1306,10 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics) if (ret == SP_OK) { - if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) - ret= SP_INTERNAL_ERROR; + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); sp_cache_invalidate(); } - +err: close_thread_tables(thd); DBUG_RETURN(ret); } @@ -1339,10 +1393,7 @@ err: bool sp_show_create_routine(THD *thd, int type, sp_name *name) { - bool err_status= TRUE; sp_head *sp; - sp_cache **cache = type == TYPE_ENUM_PROCEDURE ? - &thd->sp_proc_cache : &thd->sp_func_cache; DBUG_ENTER("sp_show_create_routine"); DBUG_PRINT("enter", ("name: %.*s", @@ -1352,28 +1403,29 @@ sp_show_create_routine(THD *thd, int type, sp_name *name) DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE || type == TYPE_ENUM_FUNCTION); - if (type == TYPE_ENUM_PROCEDURE) + /* + @todo: Consider using prelocking for this code as well. Currently + SHOW CREATE PROCEDURE/FUNCTION is a dirty read of the data + dictionary, i.e. takes no metadata locks. + It is "safe" to do as long as it doesn't affect the results + of the binary log or the query cache, which currently it does not. + */ + if (sp_cache_routine(thd, type, name, FALSE, &sp)) + DBUG_RETURN(TRUE); + + if (sp == NULL || sp->show_create_routine(thd, type)) { /* - SHOW CREATE PROCEDURE may require two instances of one sp_head - object when SHOW CREATE PROCEDURE is called for the procedure that - is being executed. Basically, there is no actual recursion, so we - increase the recursion limit for this statement (kind of hack). - - SHOW CREATE FUNCTION does not require this because SHOW CREATE - statements are prohibitted within stored functions. - */ - - thd->variables.max_sp_recursion_depth++; + If we have insufficient privileges, pretend the routine + does not exist. + */ + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), + type == TYPE_ENUM_FUNCTION ? "FUNCTION" : "PROCEDURE", + name->m_name.str); + DBUG_RETURN(TRUE); } - if ((sp= sp_find_routine(thd, type, name, cache, FALSE))) - err_status= sp->show_create_routine(thd, type); - - if (type == TYPE_ENUM_PROCEDURE) - thd->variables.max_sp_recursion_depth--; - - DBUG_RETURN(err_status); + DBUG_RETURN(FALSE); } @@ -1557,7 +1609,7 @@ sp_routine_exists_in_table(THD *thd, int type, sp_name *name) { TABLE *table; int ret; - Open_tables_state open_tables_state_backup; + Open_tables_backup open_tables_state_backup; if (!(table= open_proc_table_for_read(thd, &open_tables_state_backup))) ret= SP_OPEN_TABLE_FAILED; @@ -1571,73 +1623,12 @@ sp_routine_exists_in_table(THD *thd, int type, sp_name *name) } -/** - Structure that represents element in the set of stored routines - used by statement or routine. -*/ -struct Sroutine_hash_entry; - -struct Sroutine_hash_entry -{ - /** - Set key consisting of one-byte routine type and quoted routine name. - */ - LEX_STRING key; - /** - Next element in list linking all routines in set. See also comments - for LEX::sroutine/sroutine_list and sp_head::m_sroutines. - */ - Sroutine_hash_entry *next; - /** - Uppermost view which directly or indirectly uses this routine. - 0 if routine is not used in view. Note that it also can be 0 if - statement uses routine both via view and directly. - */ - TABLE_LIST *belong_to_view; -}; - - extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen, my_bool first) { Sroutine_hash_entry *rn= (Sroutine_hash_entry *)ptr; - *plen= rn->key.length; - return (uchar *)rn->key.str; -} - - -/** - Check if - - current statement (the one in thd->lex) needs table prelocking - - first routine in thd->lex->sroutines_list needs to execute its body in - prelocked mode. - - @param thd Current thread, thd->lex is the statement to be - checked. - @param[out] need_prelocking TRUE - prelocked mode should be activated - before executing the statement; - FALSE - Don't activate prelocking - @param[out] first_no_prelocking TRUE - Tables used by first routine in - thd->lex->sroutines_list should be - prelocked. FALSE - Otherwise. - - @note - This function assumes that for any "CALL proc(...)" statement routines_list - will have 'proc' as first element (it may have several, consider e.g. - "proc(sp_func(...)))". This property is currently guaranted by the parser. -*/ - -void sp_get_prelocking_info(THD *thd, bool *need_prelocking, - bool *first_no_prelocking) -{ - Sroutine_hash_entry *routine; - routine= (Sroutine_hash_entry*)thd->lex->sroutines_list.first; - - DBUG_ASSERT(routine); - bool first_is_procedure= (routine->key.str[0] == TYPE_ENUM_PROCEDURE); - - *first_no_prelocking= first_is_procedure; - *need_prelocking= !first_is_procedure || test(routine->next); + *plen= rn->mdl_request.key.length(); + return (uchar *)rn->mdl_request.key.ptr(); } @@ -1647,11 +1638,11 @@ void sp_get_prelocking_info(THD *thd, bool *need_prelocking, In case when statement uses stored routines but does not need prelocking (i.e. it does not use any tables) we will access the - elements of LEX::sroutines set on prepared statement re-execution. - Because of this we have to allocate memory for both hash element - and copy of its key in persistent arena. + elements of Query_tables_list::sroutines set on prepared statement + re-execution. Because of this we have to allocate memory for both + hash element and copy of its key in persistent arena. - @param lex LEX representing statement + @param prelocking_ctx Prelocking context of the statement @param arena Arena in which memory for new element will be allocated @param key Key for the hash representing set @@ -1659,7 +1650,7 @@ void sp_get_prelocking_info(THD *thd, bool *need_prelocking, (0 if routine is not used by view) @note - Will also add element to end of 'LEX::sroutines_list' list. + Will also add element to end of 'Query_tables_list::sroutines_list' list. @todo When we will got rid of these accesses on re-executions we will be @@ -1674,28 +1665,25 @@ void sp_get_prelocking_info(THD *thd, bool *need_prelocking, the set). */ -static bool add_used_routine(LEX *lex, Query_arena *arena, - const LEX_STRING *key, - TABLE_LIST *belong_to_view) +bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, + const MDL_key *key, TABLE_LIST *belong_to_view) { - my_hash_init_opt(&lex->sroutines, system_charset_info, + my_hash_init_opt(&prelocking_ctx->sroutines, system_charset_info, Query_tables_list::START_SROUTINES_HASH_SIZE, 0, 0, sp_sroutine_key, 0, 0); - if (!my_hash_search(&lex->sroutines, (uchar *)key->str, key->length)) + if (!my_hash_search(&prelocking_ctx->sroutines, key->ptr(), key->length())) { Sroutine_hash_entry *rn= - (Sroutine_hash_entry *)arena->alloc(sizeof(Sroutine_hash_entry) + - key->length + 1); + (Sroutine_hash_entry *)arena->alloc(sizeof(Sroutine_hash_entry)); if (!rn) // OOM. Error will be reported using fatal_error(). return FALSE; - rn->key.length= key->length; - rn->key.str= (char *)rn + sizeof(Sroutine_hash_entry); - memcpy(rn->key.str, key->str, key->length + 1); - if (my_hash_insert(&lex->sroutines, (uchar *)rn)) + rn->mdl_request.init(key, MDL_SHARED); + if (my_hash_insert(&prelocking_ctx->sroutines, (uchar *)rn)) return FALSE; - lex->sroutines_list.link_in_list((uchar *)rn, (uchar **)&rn->next); + prelocking_ctx->sroutines_list.link_in_list((uchar *)rn, (uchar **)&rn->next); rn->belong_to_view= belong_to_view; + rn->m_sp_cache_version= 0; return TRUE; } return FALSE; @@ -1709,24 +1697,27 @@ static bool add_used_routine(LEX *lex, Query_arena *arena, To be friendly towards prepared statements one should pass persistent arena as second argument. - @param lex LEX representing statement - @param arena arena in which memory for new element of the set - will be allocated - @param rt routine name - @param rt_type routine type (one of TYPE_ENUM_PROCEDURE/...) + @param prelocking_ctx Prelocking context of the statement + @param arena Arena in which memory for new element of the set + will be allocated + @param rt Routine name + @param rt_type Routine type (one of TYPE_ENUM_PROCEDURE/...) @note - Will also add element to end of 'LEX::sroutines_list' list (and will - take into account that this is explicitly used routine). + Will also add element to end of 'Query_tables_list::sroutines_list' list + (and will take into account that this is an explicitly used routine). */ -void sp_add_used_routine(LEX *lex, Query_arena *arena, +void sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, sp_name *rt, char rt_type) { - rt->set_routine_type(rt_type); - (void)add_used_routine(lex, arena, &rt->m_sroutines_key, 0); - lex->sroutines_list_own_last= lex->sroutines_list.next; - lex->sroutines_list_own_elements= lex->sroutines_list.elements; + MDL_key key((rt_type == TYPE_ENUM_FUNCTION) ? MDL_key::FUNCTION : + MDL_key::PROCEDURE, + rt->m_db.str, rt->m_name.str); + (void)sp_add_used_routine(prelocking_ctx, arena, &key, 0); + prelocking_ctx->sroutines_list_own_last= prelocking_ctx->sroutines_list.next; + prelocking_ctx->sroutines_list_own_elements= + prelocking_ctx->sroutines_list.elements; } @@ -1734,13 +1725,14 @@ void sp_add_used_routine(LEX *lex, Query_arena *arena, Remove routines which are only indirectly used by statement from the set of routines used by this statement. - @param lex LEX representing statement + @param prelocking_ctx Prelocking context of the statement */ -void sp_remove_not_own_routines(LEX *lex) +void sp_remove_not_own_routines(Query_tables_list *prelocking_ctx) { Sroutine_hash_entry *not_own_rt, *next_rt; - for (not_own_rt= *(Sroutine_hash_entry **)lex->sroutines_list_own_last; + for (not_own_rt= + *(Sroutine_hash_entry **)prelocking_ctx->sroutines_list_own_last; not_own_rt; not_own_rt= next_rt) { /* @@ -1748,12 +1740,13 @@ void sp_remove_not_own_routines(LEX *lex) but we want to be more future-proof. */ next_rt= not_own_rt->next; - my_hash_delete(&lex->sroutines, (uchar *)not_own_rt); + my_hash_delete(&prelocking_ctx->sroutines, (uchar *)not_own_rt); } - *(Sroutine_hash_entry **)lex->sroutines_list_own_last= NULL; - lex->sroutines_list.next= lex->sroutines_list_own_last; - lex->sroutines_list.elements= lex->sroutines_list_own_elements; + *(Sroutine_hash_entry **)prelocking_ctx->sroutines_list_own_last= NULL; + prelocking_ctx->sroutines_list.next= prelocking_ctx->sroutines_list_own_last; + prelocking_ctx->sroutines_list.elements= + prelocking_ctx->sroutines_list_own_elements; } @@ -1782,7 +1775,8 @@ bool sp_update_sp_used_routines(HASH *dst, HASH *src) for (uint i=0 ; i < src->records ; i++) { Sroutine_hash_entry *rt= (Sroutine_hash_entry *)my_hash_element(src, i); - if (!my_hash_search(dst, (uchar *)rt->key.str, rt->key.length)) + if (!my_hash_search(dst, (uchar *)rt->mdl_request.key.ptr(), + rt->mdl_request.key.length())) { if (my_hash_insert(dst, (uchar *)rt)) return TRUE; @@ -1797,23 +1791,24 @@ bool sp_update_sp_used_routines(HASH *dst, HASH *src) routines used by statement. @param thd Thread context - @param lex LEX representing statement + @param prelocking_ctx Prelocking context of the statement @param src Hash representing set from which routines will be added @param belong_to_view Uppermost view which uses these routines, 0 if none - @note - It will also add elements to end of 'LEX::sroutines_list' list. + @note It will also add elements to end of + 'Query_tables_list::sroutines_list' list. */ -static void -sp_update_stmt_used_routines(THD *thd, LEX *lex, HASH *src, - TABLE_LIST *belong_to_view) +void +sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx, + HASH *src, TABLE_LIST *belong_to_view) { for (uint i=0 ; i < src->records ; i++) { Sroutine_hash_entry *rt= (Sroutine_hash_entry *)my_hash_element(src, i); - (void)add_used_routine(lex, thd->stmt_arena, &rt->key, belong_to_view); + (void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena, + &rt->mdl_request.key, belong_to_view); } } @@ -1823,242 +1818,140 @@ sp_update_stmt_used_routines(THD *thd, LEX *lex, HASH *src, routines used by statement. @param thd Thread context - @param lex LEX representing statement + @param prelocking_ctx Prelocking context of the statement @param src List representing set from which routines will be added @param belong_to_view Uppermost view which uses these routines, 0 if none - @note - It will also add elements to end of 'LEX::sroutines_list' list. + @note It will also add elements to end of + 'Query_tables_list::sroutines_list' list. */ -static void sp_update_stmt_used_routines(THD *thd, LEX *lex, SQL_LIST *src, - TABLE_LIST *belong_to_view) +void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx, + SQL_LIST *src, TABLE_LIST *belong_to_view) { for (Sroutine_hash_entry *rt= (Sroutine_hash_entry *)src->first; rt; rt= rt->next) - (void)add_used_routine(lex, thd->stmt_arena, &rt->key, belong_to_view); + (void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena, + &rt->mdl_request.key, belong_to_view); } /** - Cache sub-set of routines used by statement, add tables used by these - routines to statement table list. Do the same for all routines used - by these routines. - - @param thd thread context - @param lex LEX representing statement - @param start first routine from the list of routines to be cached - (this list defines mentioned sub-set). - @param first_no_prelock If true, don't add tables or cache routines used by - the body of the first routine (i.e. *start) - will be executed in non-prelocked mode. - @param tabs_changed Set to TRUE some tables were added, FALSE otherwise - - @note - If some function is missing this won't be reported here. - Instead this fact will be discovered during query execution. - - @retval - 0 success - @retval - non-0 failure + A helper wrapper around sp_cache_routine() to use from + prelocking until 'sp_name' is eradicated as a class. */ -static int -sp_cache_routines_and_add_tables_aux(THD *thd, LEX *lex, - Sroutine_hash_entry *start, - bool first_no_prelock) +int sp_cache_routine(THD *thd, Sroutine_hash_entry *rt, + bool lookup_only, sp_head **sp) { - int ret= 0; - bool first= TRUE; - DBUG_ENTER("sp_cache_routines_and_add_tables_aux"); - - for (Sroutine_hash_entry *rt= start; rt; rt= rt->next) - { - sp_name name(thd, rt->key.str, rt->key.length); - int type= rt->key.str[0]; - sp_head *sp; - - if (!(sp= sp_cache_lookup((type == TYPE_ENUM_FUNCTION ? - &thd->sp_func_cache : &thd->sp_proc_cache), - &name))) - { - switch ((ret= db_find_routine(thd, type, &name, &sp))) - { - case SP_OK: - { - if (type == TYPE_ENUM_FUNCTION) - sp_cache_insert(&thd->sp_func_cache, sp); - else - sp_cache_insert(&thd->sp_proc_cache, sp); - } - break; - case SP_KEY_NOT_FOUND: - ret= SP_OK; - break; - default: - /* Query might have been killed, don't set error. */ - if (thd->killed) - break; - /* - Any error when loading an existing routine is either some problem - with the mysql.proc table, or a parse error because the contents - has been tampered with (in which case we clear that error). - */ - if (ret == SP_PARSE_ERROR) - thd->clear_error(); - /* - If we cleared the parse error, or when db_find_routine() flagged - an error with it's return value without calling my_error(), we - set the generic "mysql.proc table corrupt" error here. - */ - if (! thd->is_error()) - { - /* - SP allows full NAME_LEN chars thus he have to allocate enough - size in bytes. Otherwise there is stack overrun could happen - if multibyte sequence is `name`. `db` is still safe because the - rest of the server checks agains NAME_LEN bytes and not chars. - Hence, the overrun happens only if the name is in length > 32 and - uses multibyte (cyrillic, greek, etc.) - */ - char n[NAME_LEN*2+2]; - - /* m_qname.str is not always \0 terminated */ - memcpy(n, name.m_qname.str, name.m_qname.length); - n[name.m_qname.length]= '\0'; - my_error(ER_SP_PROC_TABLE_CORRUPT, MYF(0), n, ret); - } - break; - } - } - if (sp) - { - if (!(first && first_no_prelock)) - { - sp_update_stmt_used_routines(thd, lex, &sp->m_sroutines, - rt->belong_to_view); - (void)sp->add_used_tables_to_table_list(thd, &lex->query_tables_last, - rt->belong_to_view); - } - sp->propagate_attributes(lex); - } - first= FALSE; - } - DBUG_RETURN(ret); -} - - -/** - Cache all routines from the set of used by statement, add tables used - by those routines to statement table list. Do the same for all routines - used by those routines. - - @param thd thread context - @param lex LEX representing statement - @param first_no_prelock If true, don't add tables or cache routines used by - the body of the first routine (i.e. *start) + char qname_buff[NAME_LEN*2+1+1]; + sp_name name(&rt->mdl_request.key, qname_buff); + MDL_key::enum_mdl_namespace mdl_type= rt->mdl_request.key.mdl_namespace(); + int type= ((mdl_type == MDL_key::FUNCTION) ? + TYPE_ENUM_FUNCTION : TYPE_ENUM_PROCEDURE); - @retval - 0 success - @retval - non-0 failure -*/ + /* + Check that we have an MDL lock on this routine, unless it's a top-level + CALL. The assert below should be unambiguous: the first element + in sroutines_list has an MDL lock unless it's a top-level call, or a + trigger, but triggers can't occur here (see the preceding assert). + */ + DBUG_ASSERT(rt->mdl_request.ticket || + rt == (Sroutine_hash_entry*) thd->lex->sroutines_list.first); -int -sp_cache_routines_and_add_tables(THD *thd, LEX *lex, bool first_no_prelock) -{ - return sp_cache_routines_and_add_tables_aux(thd, lex, - (Sroutine_hash_entry *)lex->sroutines_list.first, - first_no_prelock); + return sp_cache_routine(thd, type, &name, lookup_only, sp); } /** - Add all routines used by view to the set of routines used by - statement. - - Add tables used by those routines to statement table list. Do the same - for all routines used by these routines. - - @param thd Thread context - @param lex LEX representing statement - @param view Table list element representing view - - @retval - 0 success - @retval - non-0 failure + Ensure that routine is present in cache by loading it from the mysql.proc + table if needed. If the routine is present but old, reload it. + Emit an appropriate error if there was a problem during + loading. + + @param[in] thd Thread context. + @param[in] type Type of object (TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE). + @param[in] name Name of routine. + @param[in] lookup_only Only check that the routine is in the cache. + If it's not, don't try to load. If it is present, + but old, don't try to reload. + @param[out] sp Pointer to sp_head object for routine, NULL if routine was + not found. + + @retval 0 Either routine is found and was succesfully loaded into cache + or it does not exist. + @retval non-0 Error while loading routine from mysql,proc table. */ -int -sp_cache_routines_and_add_tables_for_view(THD *thd, LEX *lex, TABLE_LIST *view) +int sp_cache_routine(THD *thd, int type, sp_name *name, + bool lookup_only, sp_head **sp) { - Sroutine_hash_entry **last_cached_routine_ptr= - (Sroutine_hash_entry **)lex->sroutines_list.next; - sp_update_stmt_used_routines(thd, lex, &view->view->sroutines_list, - view->top_table()); - return sp_cache_routines_and_add_tables_aux(thd, lex, - *last_cached_routine_ptr, FALSE); -} + int ret= 0; + sp_cache **spc= (type == TYPE_ENUM_FUNCTION ? + &thd->sp_func_cache : &thd->sp_proc_cache); + DBUG_ENTER("sp_cache_routine"); -/** - Add triggers for table to the set of routines used by statement. - Add tables used by them to statement table list. Do the same for - all implicitly used routines. + DBUG_ASSERT(type == TYPE_ENUM_FUNCTION || type == TYPE_ENUM_PROCEDURE); - @param thd thread context - @param lex LEX respresenting statement - @param table Table list element for table with trigger - @retval - 0 success - @retval - non-0 failure -*/ + *sp= sp_cache_lookup(spc, name); -int -sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex, - TABLE_LIST *table) -{ - int ret= 0; + if (lookup_only) + DBUG_RETURN(SP_OK); - Sroutine_hash_entry **last_cached_routine_ptr= - (Sroutine_hash_entry **)lex->sroutines_list.next; + if (*sp) + { + sp_cache_flush_obsolete(spc, sp); + if (*sp) + DBUG_RETURN(SP_OK); + } - if (static_cast<int>(table->lock_type) >= - static_cast<int>(TL_WRITE_ALLOW_WRITE)) + switch ((ret= db_find_routine(thd, type, name, sp))) { - for (int i= 0; i < (int)TRG_EVENT_MAX; i++) - { - if (table->trg_event_map & - static_cast<uint8>(1 << static_cast<int>(i))) + case SP_OK: + sp_cache_insert(spc, *sp); + break; + case SP_KEY_NOT_FOUND: + ret= SP_OK; + break; + default: + /* Query might have been killed, don't set error. */ + if (thd->killed) + break; + /* + Any error when loading an existing routine is either some problem + with the mysql.proc table, or a parse error because the contents + has been tampered with (in which case we clear that error). + */ + if (ret == SP_PARSE_ERROR) + thd->clear_error(); + /* + If we cleared the parse error, or when db_find_routine() flagged + an error with it's return value without calling my_error(), we + set the generic "mysql.proc table corrupt" error here. + */ + if (! thd->is_error()) { - for (int j= 0; j < (int)TRG_ACTION_MAX; j++) - { - /* We can have only one trigger per action type currently */ - sp_head *trigger= table->table->triggers->bodies[i][j]; - if (trigger && - add_used_routine(lex, thd->stmt_arena, &trigger->m_sroutines_key, - table->belong_to_view)) - { - trigger->add_used_tables_to_table_list(thd, &lex->query_tables_last, - table->belong_to_view); - trigger->propagate_attributes(lex); - sp_update_stmt_used_routines(thd, lex, - &trigger->m_sroutines, - table->belong_to_view); - } - } + /* + SP allows full NAME_LEN chars thus he have to allocate enough + size in bytes. Otherwise there is stack overrun could happen + if multibyte sequence is `name`. `db` is still safe because the + rest of the server checks agains NAME_LEN bytes and not chars. + Hence, the overrun happens only if the name is in length > 32 and + uses multibyte (cyrillic, greek, etc.) + */ + char n[NAME_LEN*2+2]; + + /* m_qname.str is not always \0 terminated */ + memcpy(n, name->m_qname.str, name->m_qname.length); + n[name->m_qname.length]= '\0'; + my_error(ER_SP_PROC_TABLE_CORRUPT, MYF(0), n, ret); } - } + break; } - ret= sp_cache_routines_and_add_tables_aux(thd, lex, - *last_cached_routine_ptr, - FALSE); - return ret; + DBUG_RETURN(ret); } @@ -42,6 +42,15 @@ sp_head * sp_find_routine(THD *thd, int type, sp_name *name, sp_cache **cp, bool cache_only); +int +sp_cache_routine(THD *thd, Sroutine_hash_entry *rt, + bool lookup_only, sp_head **sp); + + +int +sp_cache_routine(THD *thd, int type, sp_name *name, + bool lookup_only, sp_head **sp); + bool sp_exist_routines(THD *thd, TABLE_LIST *procs, bool any); @@ -60,22 +69,57 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics); int sp_drop_routine(THD *thd, int type, sp_name *name); + +/** + Structure that represents element in the set of stored routines + used by statement or routine. +*/ + +class Sroutine_hash_entry +{ +public: + /** + Metadata lock request for routine. + MDL_key in this request is also used as a key for set. + */ + MDL_request mdl_request; + /** + Next element in list linking all routines in set. See also comments + for LEX::sroutine/sroutine_list and sp_head::m_sroutines. + */ + Sroutine_hash_entry *next; + /** + Uppermost view which directly or indirectly uses this routine. + 0 if routine is not used in view. Note that it also can be 0 if + statement uses routine both via view and directly. + */ + TABLE_LIST *belong_to_view; + /** + This is for prepared statement validation purposes. + A statement looks up and pre-loads all its stored functions + at prepare. Later on, if a function is gone from the cache, + execute may fail. + Remember the version of sp_head at prepare to be able to + invalidate the prepared statement at execute if it + changes. + */ + ulong m_sp_cache_version; +}; + + /* - Procedures for pre-caching of stored routines and building table list - for prelocking. + Procedures for handling sets of stored routines used by statement or routine. */ -void sp_get_prelocking_info(THD *thd, bool *need_prelocking, - bool *first_no_prelocking); -void sp_add_used_routine(LEX *lex, Query_arena *arena, +void sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, sp_name *rt, char rt_type); -void sp_remove_not_own_routines(LEX *lex); +bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, + const MDL_key *key, TABLE_LIST *belong_to_view); +void sp_remove_not_own_routines(Query_tables_list *prelocking_ctx); bool sp_update_sp_used_routines(HASH *dst, HASH *src); -int sp_cache_routines_and_add_tables(THD *thd, LEX *lex, - bool first_no_prelock); -int sp_cache_routines_and_add_tables_for_view(THD *thd, LEX *lex, - TABLE_LIST *view); -int sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex, - TABLE_LIST *table); +void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx, + HASH *src, TABLE_LIST *belong_to_view); +void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx, + SQL_LIST *src, TABLE_LIST *belong_to_view); extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen, my_bool first); @@ -84,6 +128,6 @@ extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen, Routines which allow open/lock and close mysql.proc table even when we already have some tables open and locked. */ -TABLE *open_proc_table_for_read(THD *thd, Open_tables_state *backup); +TABLE *open_proc_table_for_read(THD *thd, Open_tables_backup *backup); #endif /* _SP_H_ */ diff --git a/sql/sp_cache.cc b/sql/sp_cache.cc index e8604baf466..a585004b1e8 100644 --- a/sql/sp_cache.cc +++ b/sql/sp_cache.cc @@ -31,8 +31,6 @@ static ulong volatile Cversion= 0; class sp_cache { public: - ulong version; - sp_cache(); ~sp_cache(); @@ -54,25 +52,10 @@ public: namelen); } -#ifdef NOT_USED - inline bool remove(char *name, uint namelen) - { - sp_head *sp= lookup(name, namelen); - if (sp) - { - hash_delete(&m_hashtable, (uchar *)sp); - return TRUE; - } - return FALSE; - } -#endif - - inline void remove_all() + inline void remove(sp_head *sp) { - cleanup(); - init(); + my_hash_delete(&m_hashtable, (uchar *)sp); } - private: void init(); void cleanup(); @@ -159,8 +142,9 @@ void sp_cache_insert(sp_cache **cp, sp_head *sp) { if (!(c= new sp_cache())) return; // End of memory error - c->version= Cversion; // No need to lock when reading long variable } + /* Reading a ulong variable with no lock. */ + sp->set_sp_cache_version(Cversion); DBUG_PRINT("info",("sp_cache: inserting: %.*s", (int) sp->m_qname.length, sp->m_qname.str)); c->insert(sp); @@ -211,46 +195,34 @@ void sp_cache_invalidate() } -/* - Remove out-of-date SPs from the cache. - - SYNOPSIS - sp_cache_flush_obsolete() - cp Cache to flush +/** + Remove an out-of-date SP from the cache. - NOTE - This invalidates pointers to sp_head objects this thread uses. - In practice that means 'dont call this function when inside SP'. + @param[in] cp Cache to flush + @param[in] sp SP to remove. + + @note This invalidates pointers to sp_head objects this thread + uses. In practice that means 'dont call this function when + inside SP'. */ -void sp_cache_flush_obsolete(sp_cache **cp) +void sp_cache_flush_obsolete(sp_cache **cp, sp_head **sp) { - sp_cache *c= *cp; - if (c) + if ((*sp)->sp_cache_version() < Cversion && !(*sp)->is_invoked()) { - ulong v; - v= Cversion; // No need to lock when reading long variable - if (c->version < v) - { - DBUG_PRINT("info",("sp_cache: deleting all functions")); - /* We need to delete all elements. */ - c->remove_all(); - c->version= v; - } + (*cp)->remove(*sp); + *sp= NULL; } } /** - Return the current version of the cache. + Return the current global version of the cache. */ -ulong sp_cache_version(sp_cache **cp) +ulong sp_cache_version() { - sp_cache *c= *cp; - if (c) - return c->version; - return 0; + return Cversion; } @@ -295,7 +267,6 @@ sp_cache::init() { my_hash_init(&m_hashtable, system_charset_info, 0, 0, 0, hash_get_key_for_sp_head, hash_free_sp_head, 0); - version= 0; } diff --git a/sql/sp_cache.h b/sql/sp_cache.h index f4d44a1f29f..7dbb0d5429c 100644 --- a/sql/sp_cache.h +++ b/sql/sp_cache.h @@ -57,7 +57,7 @@ void sp_cache_clear(sp_cache **cp); void sp_cache_insert(sp_cache **cp, sp_head *sp); sp_head *sp_cache_lookup(sp_cache **cp, sp_name *name); void sp_cache_invalidate(); -void sp_cache_flush_obsolete(sp_cache **cp); -ulong sp_cache_version(sp_cache **cp); +void sp_cache_flush_obsolete(sp_cache **cp, sp_head **sp); +ulong sp_cache_version(); #endif /* _SP_CACHE_H_ */ diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 6e69b2e39a4..b1ede0fc6af 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -383,31 +383,34 @@ error: } -/* - * - * sp_name - * - */ +/** + Create temporary sp_name object from MDL key. -sp_name::sp_name(THD *thd, char *key, uint key_len) + @note The lifetime of this object is bound to the lifetime of the MDL_key. + This should be fine as sp_name objects created by this constructor + are mainly used for SP-cache lookups. + + @param key MDL key containing database and routine name. + @param qname_buff Buffer to be used for storing quoted routine name + (should be at least 2*NAME_LEN+1+1 bytes). +*/ + +sp_name::sp_name(const MDL_key *key, char *qname_buff) { - m_sroutines_key.str= key; - m_sroutines_key.length= key_len; - m_qname.str= ++key; - m_qname.length= key_len - 1; - if ((m_name.str= strchr(m_qname.str, '.'))) + m_db.str= (char*)key->db_name(); + m_db.length= key->db_name_length(); + m_name.str= (char*)key->name(); + m_name.length= key->name_length(); + m_qname.str= qname_buff; + if (m_db.length) { - m_db.length= m_name.str - key; - m_db.str= strmake_root(thd->mem_root, key, m_db.length); - m_name.str++; - m_name.length= m_qname.length - m_db.length - 1; + strxmov(qname_buff, m_db.str, ".", m_name.str, NullS); + m_qname.length= m_db.length + 1 + m_name.length; } else { - m_name.str= m_qname.str; - m_name.length= m_qname.length; - m_db.str= 0; - m_db.length= 0; + strmov(qname_buff, m_name.str); + m_qname.length= m_name.length; } m_explicit_name= false; } @@ -420,12 +423,10 @@ void sp_name::init_qname(THD *thd) { const uint dot= !!m_db.length; - /* m_sroutines format: m_type + [database + dot] + name + nul */ - m_sroutines_key.length= 1 + m_db.length + dot + m_name.length; - if (!(m_sroutines_key.str= (char*) thd->alloc(m_sroutines_key.length + 1))) + /* m_qname format: [database + dot] + name + '\0' */ + m_qname.length= m_db.length + dot + m_name.length; + if (!(m_qname.str= (char*) thd->alloc(m_qname.length + 1))) return; - m_qname.length= m_sroutines_key.length - 1; - m_qname.str= m_sroutines_key.str + 1; sprintf(m_qname.str, "%.*s%.*s%.*s", (int) m_db.length, (m_db.length ? m_db.str : ""), dot, ".", @@ -511,7 +512,10 @@ sp_head::operator delete(void *ptr, size_t size) throw() sp_head::sp_head() :Query_arena(&main_mem_root, INITIALIZED_FOR_SP), - m_flags(0), m_recursion_level(0), m_next_cached_sp(0), + m_flags(0), + m_sp_cache_version(0), + m_recursion_level(0), + m_next_cached_sp(0), m_cont_level(0) { const LEX_STRING str_reset= { NULL, 0 }; @@ -586,9 +590,6 @@ sp_head::init(LEX *lex) m_defstr.str= NULL; m_defstr.length= 0; - m_sroutines_key.str= NULL; - m_sroutines_key.length= 0; - m_return_field_def.charset= NULL; DBUG_VOID_RETURN; @@ -618,14 +619,10 @@ sp_head::init_sp_name(THD *thd, sp_name *spname) if (spname->m_qname.length == 0) spname->init_qname(thd); - m_sroutines_key.length= spname->m_sroutines_key.length; - m_sroutines_key.str= (char*) memdup_root(thd->mem_root, - spname->m_sroutines_key.str, - spname->m_sroutines_key.length + 1); - m_sroutines_key.str[0]= static_cast<char>(m_type); - - m_qname.length= m_sroutines_key.length - 1; - m_qname.str= m_sroutines_key.str + 1; + m_qname.length= spname->m_qname.length; + m_qname.str= (char*) memdup_root(thd->mem_root, + spname->m_qname.str, + spname->m_qname.length + 1); DBUG_VOID_RETURN; } @@ -734,16 +731,6 @@ create_typelib(MEM_ROOT *mem_root, Create_field *field_def, List<String> *src) } -int -sp_head::create(THD *thd) -{ - DBUG_ENTER("sp_head::create"); - DBUG_PRINT("info", ("type: %d name: %s params: %s body: %s", - m_type, m_name.str, m_params.str, m_body.str)); - - DBUG_RETURN(sp_create_routine(thd, m_type, this)); -} - sp_head::~sp_head() { DBUG_ENTER("sp_head::~sp_head"); @@ -1185,8 +1172,7 @@ sp_head::execute(THD *thd) We should also save Item tree change list to avoid rollback something too early in the calling query. */ - old_change_list= thd->change_list; - thd->change_list.empty(); + thd->change_list.move_elements_to(&old_change_list); /* Cursors will use thd->packet, so they may corrupt data which was prepared for sending by upper level. OTOH cursors in the same routine can share this @@ -1255,7 +1241,7 @@ sp_head::execute(THD *thd) Will write this SP statement into binlog separately (TODO: consider changing the condition to "not inside event union") */ - if (thd->prelocked_mode == NON_PRELOCKED) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES) thd->user_var_events_alloc= thd->mem_root; err_status= i->execute(thd, &ip); @@ -1267,7 +1253,7 @@ sp_head::execute(THD *thd) If we've set thd->user_var_events_alloc to mem_root of this SP statement, clean all the events allocated in it. */ - if (thd->prelocked_mode == NON_PRELOCKED) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES) { reset_dynamic(&thd->user_var_events); thd->user_var_events_alloc= NULL;//DEBUG @@ -1332,9 +1318,7 @@ sp_head::execute(THD *thd) /* Restore all saved */ old_packet.swap(thd->packet); DBUG_ASSERT(thd->change_list.is_empty()); - thd->change_list= old_change_list; - /* To avoid wiping out thd->change_list on old_change_list destruction */ - old_change_list.empty(); + old_change_list.move_elements_to(&thd->change_list); thd->lex= old_lex; thd->set_query_id(old_query_id); DBUG_ASSERT(!thd->derived_tables); @@ -1804,7 +1788,6 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, "Invoked ROUTINE modified a transactional table but MySQL " "failed to reflect this change in the binary log"); - err_status= TRUE; } reset_dynamic(&thd->user_var_events); /* Forget those values, in case more function calls are binlogged: */ @@ -2747,7 +2730,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, thd->set_query_id(next_query_id()); - if (thd->prelocked_mode == NON_PRELOCKED) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES) { /* This statement will enter/leave prelocked mode on its own. @@ -3992,6 +3975,9 @@ sp_head::add_used_tables_to_table_list(THD *thd, table->prelocking_placeholder= 1; table->belong_to_view= belong_to_view; table->trg_event_map= stab->trg_event_map; + table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name, + table->lock_type >= TL_WRITE_ALLOW_WRITE ? + MDL_SHARED_WRITE : MDL_SHARED_READ); /* Everyting else should be zeroed */ @@ -4033,7 +4019,10 @@ sp_add_to_query_tables(THD *thd, LEX *lex, table->lock_type= locktype; table->select_lex= lex->current_select; table->cacheable_table= 1; - + table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name, + table->lock_type >= TL_WRITE_ALLOW_WRITE ? + MDL_SHARED_WRITE : MDL_SHARED_READ); + lex->add_to_query_tables(table); return table; } diff --git a/sql/sp_head.h b/sql/sp_head.h index 00c96d44f70..02269cf53a7 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -109,36 +109,21 @@ public: LEX_STRING m_db; LEX_STRING m_name; LEX_STRING m_qname; - /** - Key representing routine in the set of stored routines used by statement. - Consists of 1-byte routine type and m_qname (which usually refences to - same buffer). Note that one must complete initialization of the key by - calling set_routine_type(). - */ - LEX_STRING m_sroutines_key; bool m_explicit_name; /**< Prepend the db name? */ sp_name(LEX_STRING db, LEX_STRING name, bool use_explicit_name) : m_db(db), m_name(name), m_explicit_name(use_explicit_name) { - m_qname.str= m_sroutines_key.str= 0; - m_qname.length= m_sroutines_key.length= 0; + m_qname.str= 0; + m_qname.length= 0; } - /** - Creates temporary sp_name object from key, used mainly - for SP-cache lookups. - */ - sp_name(THD *thd, char *key, uint key_len); + /** Create temporary sp_name object from MDL key. */ + sp_name(const MDL_key *key, char *qname_buff); // Init. the qualified name from the db and name. void init_qname(THD *thd); // thd for memroot allocation - void set_routine_type(char type) - { - m_sroutines_key.str[0]= type; - } - ~sp_name() {} }; @@ -181,12 +166,6 @@ public: ulong m_sql_mode; ///< For SHOW CREATE and execution LEX_STRING m_qname; ///< db.name bool m_explicit_name; ///< Prepend the db name? */ - /** - Key representing routine in the set of stored routines used by statement. - [routine_type]db.name - @sa sp_name::m_sroutines_key - */ - LEX_STRING m_sroutines_key; LEX_STRING m_db; LEX_STRING m_name; LEX_STRING m_params; @@ -196,7 +175,34 @@ public: LEX_STRING m_definer_user; LEX_STRING m_definer_host; + /** + Is this routine being executed? + */ + bool is_invoked() const { return m_flags & IS_INVOKED; } + + /** + Get the value of the SP cache version, as remembered + when the routine was inserted into the cache. + */ + ulong sp_cache_version() const { return m_sp_cache_version; } + + /** Set the value of the SP cache version. */ + void set_sp_cache_version(ulong version_arg) + { + m_sp_cache_version= version_arg; + } private: + /** + Version of the stored routine cache at the moment when the + routine was added to it. Is used only for functions and + procedures, not used for triggers or events. When sp_head is + created, its version is 0. When it's added to the cache, the + version is assigned the global value 'Cversion'. + If later on Cversion is incremented, we know that the routine + is obsolete and should not be used -- + sp_cache_flush_obsolete() will purge it. + */ + ulong m_sp_cache_version; Stored_program_creation_ctx *m_creation_ctx; public: @@ -284,9 +290,6 @@ public: void set_stmt_end(THD *thd); - int - create(THD *thd); - virtual ~sp_head(); /// Free memory @@ -454,10 +457,10 @@ public: /* This method is intended for attributes of a routine which need - to propagate upwards to the LEX of the caller (when a property of a - sp_head needs to "taint" the caller). + to propagate upwards to the Query_tables_list of the caller (when + a property of a sp_head needs to "taint" the calling statement). */ - void propagate_attributes(LEX *lex) + void propagate_attributes(Query_tables_list *prelocking_ctx) { /* If this routine needs row-based binary logging, the entire top statement @@ -466,7 +469,7 @@ public: the substatements not). */ if (m_flags & BINLOG_ROW_BASED_IF_MIXED) - lex->set_stmt_unsafe(); + prelocking_ctx->set_stmt_unsafe(); } diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 1f5ac826bdf..8b821fcb480 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -30,6 +30,7 @@ #include <stdarg.h> #include "sp_head.h" #include "sp.h" +#include "transaction.h" bool mysql_user_table_is_in_short_password_format= false; @@ -659,13 +660,6 @@ my_bool acl_reload(THD *thd) my_bool return_val= 1; DBUG_ENTER("acl_reload"); - if (thd->locked_tables) - { // Can't have locked tables here - thd->lock=thd->locked_tables; - thd->locked_tables=0; - close_thread_tables(thd); - } - /* To avoid deadlocks we should obtain table locks before obtaining acl_cache->lock mutex. @@ -678,8 +672,8 @@ my_bool acl_reload(THD *thd) tables[0].next_local= tables[0].next_global= tables+1; tables[1].next_local= tables[1].next_global= tables+2; tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_READ; - tables[0].skip_temporary= tables[1].skip_temporary= - tables[2].skip_temporary= TRUE; + tables[0].open_type= tables[1].open_type= tables[2].open_type= OT_BASE_ONLY; + init_mdl_requests(tables); if (simple_open_n_lock_tables(thd, tables)) { @@ -718,7 +712,9 @@ 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(); DBUG_RETURN(return_val); } @@ -1581,9 +1577,7 @@ bool change_password(THD *thd, const char *host, const char *user, if (check_change_password(thd, host, user, new_password, new_password_len)) DBUG_RETURN(1); - bzero((char*) &tables, sizeof(tables)); - tables.alias= tables.table_name= (char*) "user"; - tables.db= (char*) "mysql"; + tables.init_one_table("mysql", 5, "user", 4, "user", TL_WRITE); #ifdef HAVE_REPLICATION /* @@ -1638,8 +1632,8 @@ bool change_password(THD *thd, const char *host, const char *user, acl_user->host.hostname ? acl_user->host.hostname : "", new_password)); thd->clear_error(); - result= thd->binlog_query(THD::MYSQL_QUERY_TYPE, buff, query_length, - FALSE, FALSE, 0); + thd->binlog_query(THD::MYSQL_QUERY_TYPE, buff, query_length, + FALSE, FALSE, 0); } end: close_thread_tables(thd); @@ -3107,6 +3101,7 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, ? tables+2 : 0); tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_WRITE; tables[0].db=tables[1].db=tables[2].db=(char*) "mysql"; + init_mdl_requests(tables); /* This statement will be replicated as a statement, even when using @@ -3257,7 +3252,7 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, if (!result) /* success */ { - result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } mysql_rwlock_unlock(&LOCK_grant); @@ -3324,6 +3319,7 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, tables[0].next_local= tables[0].next_global= tables+1; tables[0].lock_type=tables[1].lock_type=TL_WRITE; tables[0].db=tables[1].db=(char*) "mysql"; + init_mdl_requests(tables); /* This statement will be replicated as a statement, even when using @@ -3423,8 +3419,7 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, if (write_to_binlog) { - if (write_bin_log(thd, FALSE, thd->query(), thd->query_length())) - result= TRUE; + write_bin_log(thd, FALSE, thd->query(), thd->query_length()); } mysql_rwlock_unlock(&LOCK_grant); @@ -3464,6 +3459,7 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, tables[0].next_local= tables[0].next_global= tables+1; tables[0].lock_type=tables[1].lock_type=TL_WRITE; tables[0].db=tables[1].db=(char*) "mysql"; + init_mdl_requests(tables); /* This statement will be replicated as a statement, even when using @@ -3543,7 +3539,7 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, if (!result) { - result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } mysql_rwlock_unlock(&LOCK_grant); @@ -3798,11 +3794,10 @@ static my_bool grant_reload_procs_priv(THD *thd) my_bool return_val= FALSE; DBUG_ENTER("grant_reload_procs_priv"); - bzero((char*) &table, sizeof(table)); - table.alias= table.table_name= (char*) "procs_priv"; - table.db= (char *) "mysql"; - table.lock_type= TL_READ; - table.skip_temporary= 1; + table.init_one_table("mysql", 5, "procs_priv", + strlen("procs_priv"), "procs_priv", + TL_READ); + table.open_type= OT_BASE_ONLY; if (simple_open_n_lock_tables(thd, &table)) { @@ -3868,7 +3863,9 @@ my_bool grant_reload(THD *thd) tables[0].db= tables[1].db= (char *) "mysql"; tables[0].next_local= tables[0].next_global= tables+1; tables[0].lock_type= tables[1].lock_type= TL_READ; - tables[0].skip_temporary= tables[1].skip_temporary= TRUE; + tables[0].open_type= tables[1].open_type= OT_BASE_ONLY; + init_mdl_requests(tables); + /* To avoid deadlocks we should obtain table locks before obtaining LOCK_grant rwlock. @@ -3899,7 +3896,9 @@ 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(); /* It is OK failing to load procs_priv table because we may be @@ -5152,6 +5151,7 @@ int open_grant_tables(THD *thd, TABLE_LIST *tables) (tables+4)->lock_type= TL_WRITE; tables->db= (tables+1)->db= (tables+2)->db= (tables+3)->db= (tables+4)->db= (char*) "mysql"; + init_mdl_requests(tables); #ifdef HAVE_REPLICATION /* @@ -5804,7 +5804,7 @@ bool mysql_create_user(THD *thd, List <LEX_USER> &list) my_error(ER_CANNOT_USER, MYF(0), "CREATE USER", wrong_users.c_ptr_safe()); if (some_users_created) - result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + write_bin_log(thd, FALSE, thd->query(), thd->query_length()); mysql_rwlock_unlock(&LOCK_grant); close_thread_tables(thd); @@ -5877,7 +5877,7 @@ bool mysql_drop_user(THD *thd, List <LEX_USER> &list) my_error(ER_CANNOT_USER, MYF(0), "DROP USER", wrong_users.c_ptr_safe()); if (some_users_deleted) - result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + write_bin_log(thd, FALSE, thd->query(), thd->query_length()); mysql_rwlock_unlock(&LOCK_grant); close_thread_tables(thd); @@ -5962,7 +5962,7 @@ bool mysql_rename_user(THD *thd, List <LEX_USER> &list) my_error(ER_CANNOT_USER, MYF(0), "RENAME USER", wrong_users.c_ptr_safe()); if (some_users_renamed && mysql_bin_log.is_open()) - result |= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + write_bin_log(thd, FALSE, thd->query(), thd->query_length()); mysql_rwlock_unlock(&LOCK_grant); close_thread_tables(thd); @@ -6144,17 +6144,15 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) mysql_mutex_unlock(&acl_cache->lock); - int binlog_error= - write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + write_bin_log(thd, FALSE, thd->query(), thd->query_length()); mysql_rwlock_unlock(&LOCK_grant); close_thread_tables(thd); - /* error for writing binary log has already been reported */ - if (result && !binlog_error) + if (result) my_message(ER_REVOKE_GRANTS, ER(ER_REVOKE_GRANTS), MYF(0)); - DBUG_RETURN(result || binlog_error); + DBUG_RETURN(result); } diff --git a/sql/sql_base.cc b/sql/sql_base.cc index a287fd533b7..e1f6d9005f5 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -21,7 +21,9 @@ #include "sql_select.h" #include "sp_head.h" #include "sp.h" +#include "sp_cache.h" #include "sql_trigger.h" +#include "transaction.h" #include "sql_prepare.h" #include <m_ctype.h> #include <my_dir.h> @@ -96,59 +98,43 @@ bool Prelock_error_handler::safely_trapped_errors() @defgroup Data_Dictionary Data Dictionary @{ */ -TABLE *unused_tables; /* Used by mysql_test */ -HASH open_cache; /* Used by mysql_test */ -static HASH table_def_cache; + +/** + Total number of TABLE instances for tables in the table definition cache + (both in use by threads and not in use). This value is accessible to user + as "Open_tables" status variable. +*/ +uint table_cache_count= 0; +/** + List that contains all TABLE instances for tables in the table definition + cache that are not in use by any thread. Recently used TABLE instances are + appended to the end of the list. Thus the beginning of the list contains + tables which have been least recently used. +*/ +TABLE *unused_tables; +HASH table_def_cache; static TABLE_SHARE *oldest_unused_share, end_of_unused_share; -static mysql_mutex_t LOCK_table_share; static bool table_def_inited= 0; +static bool table_def_shutdown_in_progress= 0; -static int open_unireg_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list, - const char *alias, - char *cache_key, uint cache_key_length, - MEM_ROOT *mem_root, uint flags); +static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables, + TABLE_SHARE *table_share); +static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry); +static bool auto_repair_table(THD *thd, TABLE_LIST *table_list); static void free_cache_entry(TABLE *entry); static bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, uint db_stat, uint prgflag, uint ha_open_flags, TABLE *outparam, TABLE_LIST *table_desc, MEM_ROOT *mem_root); -static void close_old_data_files(THD *thd, TABLE *table, bool morph_locks, - bool send_refresh); +static bool tdc_wait_for_old_versions(THD *thd, + MDL_request_list *mdl_requests); static bool has_write_table_with_auto_increment(TABLE_LIST *tables); -extern "C" uchar *table_cache_key(const uchar *record, size_t *length, - my_bool not_used __attribute__((unused))) -{ - TABLE *entry=(TABLE*) record; - *length= entry->s->table_cache_key.length; - return (uchar*) entry->s->table_cache_key.str; -} - - -bool table_cache_init(void) -{ - return my_hash_init(&open_cache, &my_charset_bin, table_cache_size+16, - 0, 0, table_cache_key, - (my_hash_free_key) free_cache_entry, 0) != 0; -} - -void table_cache_free(void) -{ - DBUG_ENTER("table_cache_free"); - if (table_def_inited) - { - close_cached_tables(NULL, NULL, FALSE, FALSE, FALSE); - if (!open_cache.records) // Safety first - my_hash_free(&open_cache); - } - DBUG_VOID_RETURN; -} - uint cached_open_tables(void) { - return open_cache.records; + return table_cache_count; } @@ -156,7 +142,8 @@ uint cached_open_tables(void) static void check_unused(void) { uint count= 0, open_files= 0, idx= 0; - TABLE *cur_link,*start_link; + TABLE *cur_link, *start_link, *entry; + TABLE_SHARE *share; if ((start_link=cur_link=unused_tables)) { @@ -167,45 +154,47 @@ static void check_unused(void) DBUG_PRINT("error",("Unused_links aren't linked properly")); /* purecov: inspected */ return; /* purecov: inspected */ } - } while (count++ < open_cache.records && + } while (count++ < table_cache_count && (cur_link=cur_link->next) != start_link); if (cur_link != start_link) { DBUG_PRINT("error",("Unused_links aren't connected")); /* purecov: inspected */ } } - for (idx=0 ; idx < open_cache.records ; idx++) + for (idx=0 ; idx < table_def_cache.records ; idx++) { - TABLE *entry=(TABLE*) my_hash_element(&open_cache,idx); - if (!entry->in_use) + share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx); + + I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables); + while ((entry= it++)) + { + /* We must not have TABLEs in the free list that have their file closed. */ + DBUG_ASSERT(entry->db_stat && entry->file); + /* Merge children should be detached from a merge parent */ + DBUG_ASSERT(! entry->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN)); + + if (entry->in_use) + { + DBUG_PRINT("error",("Used table is in share's list of unused tables")); /* purecov: inspected */ + } count--; - if (entry->file) open_files++; + } + it.init(share->used_tables); + while ((entry= it++)) + { + if (!entry->in_use) + { + DBUG_PRINT("error",("Unused table is in share's list of used tables")); /* purecov: inspected */ + } + open_files++; + } } if (count != 0) { DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d", /* purecov: inspected */ count)); /* purecov: inspected */ } - -#ifdef NOT_SAFE_FOR_REPAIR - /* - check that open cache and table definition cache has same number of - aktive tables - */ - count= 0; - for (idx=0 ; idx < table_def_cache.records ; idx++) - { - TABLE_SHARE *entry= (TABLE_SHARE*) hash_element(&table_def_cache,idx); - count+= entry->ref_count; - } - if (count != open_files) - { - DBUG_PRINT("error", ("table_def ref_count: %u open_cache: %u", - count, open_files)); - DBUG_ASSERT(count == open_files); - } -#endif } #else #define check_unused() @@ -269,13 +258,12 @@ extern "C" uchar *table_def_key(const uchar *record, size_t *length, static void table_def_free_entry(TABLE_SHARE *share) { DBUG_ENTER("table_def_free_entry"); + mysql_mutex_assert_owner(&LOCK_open); if (share->prev) { /* remove from old_unused_share list */ - mysql_mutex_lock(&LOCK_table_share); *share->prev= share->next; share->next->prev= share->prev; - mysql_mutex_unlock(&LOCK_table_share); } free_table_share(share); DBUG_VOID_RETURN; @@ -285,7 +273,6 @@ static void table_def_free_entry(TABLE_SHARE *share) bool table_def_init(void) { table_def_inited= 1; - mysql_mutex_init(key_LOCK_table_share, &LOCK_table_share, MY_MUTEX_INIT_FAST); oldest_unused_share= &end_of_unused_share; end_of_unused_share.prev= &oldest_unused_share; @@ -295,13 +282,38 @@ bool table_def_init(void) } +/** + Notify table definition cache that process of shutting down server + has started so it has to keep number of TABLE and TABLE_SHARE objects + minimal in order to reduce number of references to pluggable engines. +*/ + +void table_def_start_shutdown(void) +{ + if (table_def_inited) + { + mysql_mutex_lock(&LOCK_open); + /* Free all cached but unused TABLEs and TABLE_SHAREs first. */ + close_cached_tables(NULL, NULL, TRUE, FALSE); + /* + Ensure that TABLE and TABLE_SHARE objects which are created for + tables that are open during process of plugins' shutdown are + immediately released. This keeps number of references to engine + plugins minimal and allows shutdown to proceed smoothly. + */ + table_def_shutdown_in_progress= TRUE; + mysql_mutex_unlock(&LOCK_open); + } +} + + void table_def_free(void) { DBUG_ENTER("table_def_free"); if (table_def_inited) { table_def_inited= 0; - mysql_mutex_destroy(&LOCK_table_share); + /* Free table definitions. */ my_hash_free(&table_def_cache); } DBUG_VOID_RETURN; @@ -315,6 +327,118 @@ uint cached_table_definitions(void) /* + Auxiliary routines for manipulating with per-share used/unused and + global unused lists of TABLE objects and table_cache_count counter. + Responsible for preserving invariants between those lists, counter + and TABLE::in_use member. + In fact those routines implement sort of implicit table cache as + part of table definition cache. +*/ + + +/** + Add newly created TABLE object for table share which is going + to be used right away. +*/ + +static void table_def_add_used_table(THD *thd, TABLE *table) +{ + DBUG_ASSERT(table->in_use == thd); + table->s->used_tables.push_front(table); + table_cache_count++; +} + + +/** + Prepare used or unused TABLE instance for destruction by removing + it from share's and global list. +*/ + +static void table_def_remove_table(TABLE *table) +{ + if (table->in_use) + { + /* Remove from per-share chain of used TABLE objects. */ + table->s->used_tables.remove(table); + } + else + { + /* Remove from per-share chain of unused TABLE objects. */ + table->s->free_tables.remove(table); + + /* And global unused chain. */ + table->next->prev=table->prev; + table->prev->next=table->next; + if (table == unused_tables) + { + unused_tables=unused_tables->next; + if (table == unused_tables) + unused_tables=0; + } + check_unused(); + } + table_cache_count--; +} + + +/** + Mark already existing TABLE instance as used. +*/ + +static void table_def_use_table(THD *thd, TABLE *table) +{ + DBUG_ASSERT(!table->in_use); + + /* Unlink table from list of unused tables for this share. */ + table->s->free_tables.remove(table); + /* Unlink able from global unused tables list. */ + if (table == unused_tables) + { // First unused + unused_tables=unused_tables->next; // Remove from link + if (table == unused_tables) + unused_tables=0; + } + table->prev->next=table->next; /* Remove from unused list */ + table->next->prev=table->prev; + check_unused(); + /* Add table to list of used tables for this share. */ + table->s->used_tables.push_front(table); + table->in_use= thd; + /* The ex-unused table must be fully functional. */ + DBUG_ASSERT(table->db_stat && table->file); + /* The children must be detached from the table. */ + DBUG_ASSERT(! table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN)); +} + + +/** + Mark already existing used TABLE instance as unused. +*/ + +static void table_def_unuse_table(TABLE *table) +{ + DBUG_ASSERT(table->in_use); + + table->in_use= 0; + /* Remove table from the list of tables used in this share. */ + table->s->used_tables.remove(table); + /* Add table to the list of unused TABLE objects for this share. */ + table->s->free_tables.push_front(table); + /* Also link it last in the global list of unused TABLE objects. */ + if (unused_tables) + { + table->next=unused_tables; + table->prev=unused_tables->prev; + unused_tables->prev=table; + table->prev->next=table; + } + else + unused_tables=table->next=table->prev=table; + check_unused(); +} + + +/* Get TABLE_SHARE for a table. get_table_share() @@ -340,16 +464,26 @@ uint cached_table_definitions(void) */ TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key, - uint key_length, uint db_flags, int *error) + uint key_length, uint db_flags, int *error, + my_hash_value_type hash_value) { TABLE_SHARE *share; DBUG_ENTER("get_table_share"); *error= 0; + /* + To be able perform any operation on table we should own + some kind of metadata lock on it. + */ + DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, + table_list->db, + table_list->table_name, + MDL_SHARED)); + /* Read table definition from cache */ - if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key, - key_length))) + if ((share= (TABLE_SHARE*) my_hash_search_using_hash_value(&table_def_cache, + hash_value, (uchar*) key, key_length))) goto found; if (!(share= alloc_table_share(table_list, key, key_length))) @@ -358,12 +492,6 @@ TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key, } /* - Lock mutex to be able to read table definition from file without - conflicts - */ - mysql_mutex_lock(&share->mutex); - - /* We assign a new table id under the protection of the LOCK_open and the share's own mutex. We do this insted of creating a new mutex and using it for the sole purpose of serializing accesses to a @@ -392,7 +520,6 @@ TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key, share->ref_count++; // Mark in use DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u", (ulong) share, share->ref_count)); - mysql_mutex_unlock(&share->mutex); DBUG_RETURN(share); found: @@ -400,20 +527,15 @@ found: We found an existing table definition. Return it if we didn't get an error when reading the table definition from file. */ - - /* We must do a lock to ensure that the structure is initialized */ - mysql_mutex_lock(&share->mutex); if (share->error) { /* Table definition contained an error */ open_table_error(share, share->error, share->open_errno, share->errarg); - mysql_mutex_unlock(&share->mutex); DBUG_RETURN(0); } if (share->is_view && !(db_flags & OPEN_VIEW)) { open_table_error(share, 1, ENOENT, 0); - mysql_mutex_unlock(&share->mutex); DBUG_RETURN(0); } @@ -424,22 +546,16 @@ found: Unlink share from this list */ DBUG_PRINT("info", ("Unlinking from not used list")); - mysql_mutex_lock(&LOCK_table_share); *share->prev= share->next; share->next->prev= share->prev; share->next= 0; share->prev= 0; - mysql_mutex_unlock(&LOCK_table_share); } - mysql_mutex_unlock(&share->mutex); /* Free cache if too big */ while (table_def_cache.records > table_def_size && oldest_unused_share->next) - { - mysql_mutex_lock(&oldest_unused_share->mutex); my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share); - } DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u", (ulong) share, share->ref_count)); @@ -456,13 +572,16 @@ found: static TABLE_SHARE *get_table_share_with_create(THD *thd, TABLE_LIST *table_list, char *key, uint key_length, - uint db_flags, int *error) + uint db_flags, int *error, + my_hash_value_type hash_value) + { TABLE_SHARE *share; int tmp; DBUG_ENTER("get_table_share_with_create"); - share= get_table_share(thd, table_list, key, key_length, db_flags, error); + share= get_table_share(thd, table_list, key, key_length, db_flags, error, + hash_value); /* If share is not NULL, we found an existing share. @@ -532,32 +651,20 @@ static TABLE_SHARE thd->warning_info->clear_warning_info(thd->query_id); thd->clear_error(); // Clear error message DBUG_RETURN(get_table_share(thd, table_list, key, key_length, - db_flags, error)); + db_flags, error, hash_value)); } +/** + Mark that we are not using table share anymore. -/* - Mark that we are not using table share anymore. + @param share Table share - SYNOPSIS - release_table_share() - share Table share - release_type How the release should be done: - RELEASE_NORMAL - - Release without checking - RELEASE_WAIT_FOR_DROP - - Don't return until we get a signal that the - table is deleted or the thread is killed. - - IMPLEMENTATION - If ref_count goes to zero and (we have done a refresh or if we have - already too many open table shares) then delete the definition. - - If type == RELEASE_WAIT_FOR_DROP then don't return until we get a signal - that the table is deleted or the thread is killed. + If the share has no open tables and (we have done a refresh or + if we have already too many open table shares) then delete the + definition. */ -void release_table_share(TABLE_SHARE *share, enum release_type type) +void release_table_share(TABLE_SHARE *share) { bool to_be_deleted= 0; DBUG_ENTER("release_table_share"); @@ -568,10 +675,11 @@ void release_table_share(TABLE_SHARE *share, enum release_type type) mysql_mutex_assert_owner(&LOCK_open); - mysql_mutex_lock(&share->mutex); + DBUG_ASSERT(share->ref_count); if (!--share->ref_count) { - if (share->version != refresh_version) + if (share->version != refresh_version || + table_def_shutdown_in_progress) to_be_deleted=1; else { @@ -579,12 +687,10 @@ void release_table_share(TABLE_SHARE *share, enum release_type type) DBUG_PRINT("info",("moving share to unused list")); DBUG_ASSERT(share->next == 0); - mysql_mutex_lock(&LOCK_table_share); share->prev= end_of_unused_share.prev; *end_of_unused_share.prev= share; end_of_unused_share.prev= &share->next; share->next= &end_of_unused_share; - mysql_mutex_unlock(&LOCK_table_share); to_be_deleted= (table_def_cache.records > table_def_size); } @@ -594,9 +700,7 @@ void release_table_share(TABLE_SHARE *share, enum release_type type) { DBUG_PRINT("info", ("Deleting share")); my_hash_delete(&table_def_cache, (uchar*) share); - DBUG_VOID_RETURN; } - mysql_mutex_unlock(&share->mutex); DBUG_VOID_RETURN; } @@ -629,66 +733,25 @@ TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name) } -/* - Close file handle, but leave the table in the table cache - - SYNOPSIS - close_handle_and_leave_table_as_lock() - table Table handler - - NOTES - By leaving the table in the table cache, it disallows any other thread - to open the table - - thd->killed will be set if we run out of memory +/** + @brief Mark table share as having one more user (increase its reference + count). - If closing a MERGE child, the calling function has to take care for - closing the parent too, if necessary. + @param share Table share for which reference count should be increased. */ - -void close_handle_and_leave_table_as_lock(TABLE *table) +static void reference_table_share(TABLE_SHARE *share) { - TABLE_SHARE *share, *old_share= table->s; - char *key_buff; - MEM_ROOT *mem_root= &table->mem_root; - DBUG_ENTER("close_handle_and_leave_table_as_lock"); - - DBUG_ASSERT(table->db_stat); - - /* - Make a local copy of the table share and free the current one. - This has to be done to ensure that the table share is removed from - the table defintion cache as soon as the last instance is removed - */ - if (multi_alloc_root(mem_root, - &share, sizeof(*share), - &key_buff, old_share->table_cache_key.length, - NULL)) - { - bzero((char*) share, sizeof(*share)); - share->set_table_cache_key(key_buff, old_share->table_cache_key.str, - old_share->table_cache_key.length); - share->tmp_table= INTERNAL_TMP_TABLE; // for intern_close_table() - } - - /* - When closing a MERGE parent or child table, detach the children first. - Do not clear child table references to allow for reopen. - */ - if (table->child_l || table->parent) - detach_merge_children(table, FALSE); - table->file->close(); - table->db_stat= 0; // Mark file closed - release_table_share(table->s, RELEASE_NORMAL); - table->s= share; - table->file->change_table_ptr(table, table->s); - + DBUG_ENTER("reference_table_share"); + DBUG_ASSERT(share->ref_count); + mysql_mutex_assert_owner(&LOCK_open); + share->ref_count++; + DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u", + (ulong) share, share->ref_count)); DBUG_VOID_RETURN; } - /* Create a list for all open tables matching SQL expression @@ -719,11 +782,9 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild) start_list= &open_list; open_list=0; - for (uint idx=0 ; result == 0 && idx < open_cache.records; idx++) + for (uint idx=0 ; result == 0 && idx < table_def_cache.records; idx++) { - OPEN_TABLE_LIST *table; - TABLE *entry=(TABLE*) my_hash_element(&open_cache,idx); - TABLE_SHARE *share= entry->s; + TABLE_SHARE *share= (TABLE_SHARE *)my_hash_element(&table_def_cache, idx); if (db && my_strcasecmp(system_charset_info, db, share->db.str)) continue; @@ -737,21 +798,7 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild) if (check_table_access(thd,SELECT_ACL,&table_list, TRUE, 1, TRUE)) continue; - /* need to check if we haven't already listed it */ - for (table= open_list ; table ; table=table->next) - { - if (!strcmp(table->table, share->table_name.str) && - !strcmp(table->db, share->db.str)) - { - if (entry->in_use) - table->in_use++; - if (entry->locked_by_name) - table->locked++; - break; - } - } - if (table) - continue; + if (!(*start_list = (OPEN_TABLE_LIST *) sql_alloc(sizeof(**start_list)+share->table_cache_key.length))) { @@ -762,8 +809,11 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild) strmov(((*start_list)->db= (char*) ((*start_list)+1)), share->db.str)+1, share->table_name.str); - (*start_list)->in_use= entry->in_use ? 1 : 0; - (*start_list)->locked= entry->locked_by_name ? 1 : 0; + (*start_list)->in_use= 0; + I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables); + while (it++) + ++(*start_list)->in_use; + (*start_list)->locked= (share->version == 0) ? 1 : 0; start_list= &(*start_list)->next; *start_list=0; } @@ -786,7 +836,7 @@ void intern_close_table(TABLE *table) free_io_cache(table); delete table->triggers; - if (table->file) // Not true if name lock + if (table->file) // Not true if placeholder (void) closefrm(table, 1); // close file DBUG_VOID_RETURN; } @@ -806,22 +856,11 @@ static void free_cache_entry(TABLE *table) { DBUG_ENTER("free_cache_entry"); - /* Assert that MERGE children are not attached before final close. */ - DBUG_ASSERT(!table->is_children_attached()); + /* This should be done before releasing table share. */ + table_def_remove_table(table); intern_close_table(table); - if (!table->in_use) - { - table->next->prev=table->prev; /* remove from used chain */ - table->prev->next=table->next; - if (table == unused_tables) - { - unused_tables=unused_tables->next; - if (table == unused_tables) - unused_tables=0; - } - check_unused(); // consisty check - } + my_free((uchar*) table,MYF(0)); DBUG_VOID_RETURN; } @@ -841,6 +880,43 @@ void free_io_cache(TABLE *table) } +/** + Auxiliary function which allows to kill delayed threads for + particular table identified by its share. + + @param share Table share. + + @pre Caller should have LOCK_open mutex acquired. +*/ + +static void kill_delayed_threads_for_table(TABLE_SHARE *share) +{ + I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables); + TABLE *tab; + + mysql_mutex_assert_owner(&LOCK_open); + + while ((tab= it++)) + { + THD *in_use= tab->in_use; + + if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && + ! in_use->killed) + { + in_use->killed= THD::KILL_CONNECTION; + mysql_mutex_lock(&in_use->mysys_var->mutex); + if (in_use->mysys_var->current_cond) + { + mysql_mutex_lock(in_use->mysys_var->current_mutex); + mysql_cond_broadcast(in_use->mysys_var->current_cond); + mysql_mutex_unlock(in_use->mysys_var->current_mutex); + } + mysql_mutex_unlock(&in_use->mysys_var->mutex); + } + } +} + + /* Close all tables which aren't in use by any thread @@ -848,18 +924,23 @@ void free_io_cache(TABLE *table) @param tables List of tables to remove from the cache @param have_lock If LOCK_open is locked @param wait_for_refresh Wait for a impending flush - @param wait_for_placeholders Wait for tables being reopened so that the GRL - won't proceed while write-locked tables are being reopened by other - threads. - @remark THD can be NULL, but then wait_for_refresh must be FALSE - and tables must be NULL. + @note THD can be NULL, but then wait_for_refresh must be FALSE + and tables must be NULL. + + @note When called as part of FLUSH TABLES WITH READ LOCK this function + ignores metadata locks held by other threads. In order to avoid + situation when FLUSH TABLES WITH READ LOCK sneaks in at the moment + when some write-locked table is being reopened (by FLUSH TABLES or + ALTER TABLE) we have to rely on additional global shared metadata + lock taken by thread trying to obtain global read lock. */ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, - bool wait_for_refresh, bool wait_for_placeholders) + bool wait_for_refresh) { - bool result=0; + bool result= FALSE; + bool found= TRUE; DBUG_ENTER("close_cached_tables"); DBUG_ASSERT(thd || (!wait_for_refresh && !tables)); @@ -868,165 +949,146 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, if (!tables) { refresh_version++; // Force close of open tables + DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu", + refresh_version)); + kill_delayed_threads(); + /* + Get rid of all unused TABLE and TABLE_SHARE instances. By doing + this we automatically close all tables which were marked as "old". + */ while (unused_tables) - { -#ifdef EXTRA_DEBUG - if (my_hash_delete(&open_cache,(uchar*) unused_tables)) - printf("Warning: Couldn't delete open table from hash\n"); -#else - (void) my_hash_delete(&open_cache,(uchar*) unused_tables); -#endif - } - /* Free table shares */ + free_cache_entry(unused_tables); + /* Free table shares which were not freed implicitly by loop above. */ while (oldest_unused_share->next) - { - mysql_mutex_lock(&oldest_unused_share->mutex); (void) my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share); - } - DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu", - refresh_version)); - if (wait_for_refresh) - { - /* - Other threads could wait in a loop in open_and_lock_tables(), - trying to lock one or more of our tables. - - If they wait for the locks in thr_multi_lock(), their lock - request is aborted. They loop in open_and_lock_tables() and - enter open_table(). Here they notice the table is refreshed and - wait for COND_refresh. Then they loop again in - open_and_lock_tables() and this time open_table() succeeds. At - this moment, if we (the FLUSH TABLES thread) are scheduled and - on another FLUSH TABLES enter close_cached_tables(), they could - awake while we sleep below, waiting for others threads (us) to - close their open tables. If this happens, the other threads - would find the tables unlocked. They would get the locks, one - after the other, and could do their destructive work. This is an - issue if we have LOCK TABLES in effect. - - The problem is that the other threads passed all checks in - open_table() before we refresh the table. - - The fix for this problem is to set some_tables_deleted for all - threads with open tables. These threads can still get their - locks, but will immediately release them again after checking - this variable. They will then loop in open_and_lock_tables() - again. There they will wait until we update all tables version - below. - - Setting some_tables_deleted is done by remove_table_from_cache() - in the other branch. - - In other words (reviewer suggestion): You need this setting of - some_tables_deleted for the case when table was opened and all - related checks were passed before incrementing refresh_version - (which you already have) but attempt to lock the table happened - after the call to close_old_data_files() i.e. after removal of - current thread locks. - */ - for (uint idx=0 ; idx < open_cache.records ; idx++) - { - TABLE *table=(TABLE*) my_hash_element(&open_cache,idx); - if (table->in_use) - table->in_use->some_tables_deleted= 1; - } - } } else { bool found=0; for (TABLE_LIST *table= tables; table; table= table->next_local) { - if (remove_table_from_cache(thd, table->db, table->table_name, - RTFC_OWNED_BY_THD_FLAG)) + TABLE_SHARE *share= get_cached_table_share(table->db, table->table_name); + + if (share) + { + kill_delayed_threads_for_table(share); + /* tdc_remove_table() also sets TABLE_SHARE::version to 0. */ + tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, table->db, + table->table_name); found=1; + } } if (!found) wait_for_refresh=0; // Nothing to wait for } -#ifndef EMBEDDED_LIBRARY - if (!tables) - kill_delayed_threads(); -#endif - if (wait_for_refresh) + + if (!have_lock) + mysql_mutex_unlock(&LOCK_open); + + if (!wait_for_refresh) + DBUG_RETURN(result); + + /* Code below assume that LOCK_open is released. */ + DBUG_ASSERT(!have_lock); + + if (thd->locked_tables_mode) { /* - If there is any table that has a lower refresh_version, wait until - this is closed (or this thread is killed) before returning + If we are under LOCK TABLES, we need to reopen the tables without + opening a door for any concurrent threads to sneak in and get + lock on our tables. To achieve this we use exclusive metadata + locks. */ - thd->mysys_var->current_mutex= &LOCK_open; - thd->mysys_var->current_cond= &COND_refresh; - thd_proc_info(thd, "Flushing tables"); + TABLE_LIST *tables_to_reopen= (tables ? tables : + thd->locked_tables_list.locked_tables()); + + for (TABLE_LIST *table_list= tables_to_reopen; table_list; + table_list= table_list->next_global) + { + /* A check that the table was locked for write is done by the caller. */ + TABLE *table= find_table_for_mdl_upgrade(thd->open_tables, table_list->db, + table_list->table_name, TRUE); + + /* May return NULL if this table has already been closed via an alias. */ + if (! table) + continue; + + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + { + result= TRUE; + goto err_with_reopen; + } + close_all_tables_for_name(thd, table->s, FALSE); + } + } + + /* Wait until all threads have closed all the tables we are flushing. */ + DBUG_PRINT("info", ("Waiting for other threads to close their open tables")); - close_old_data_files(thd,thd->open_tables,1,1); + while (found && ! thd->killed) + { + found= FALSE; + /* + To avoid self and other kinds of deadlock we have to flush open HANDLERs. + */ mysql_ha_flush(thd); DEBUG_SYNC(thd, "after_flush_unlock"); - bool found=1; - /* Wait until all threads has closed all the tables we had locked */ - DBUG_PRINT("info", - ("Waiting for other threads to close their open tables")); - while (found && ! thd->killed) + mysql_mutex_lock(&LOCK_open); + + thd->enter_cond(&COND_refresh, &LOCK_open, "Flushing tables"); + + if (!tables) { - found=0; - for (uint idx=0 ; idx < open_cache.records ; idx++) + for (uint idx=0 ; idx < table_def_cache.records ; idx++) { - TABLE *table=(TABLE*) my_hash_element(&open_cache,idx); - /* Avoid a self-deadlock. */ - if (table->in_use == thd) - continue; - /* - Note that we wait here only for tables which are actually open, and - not for placeholders with TABLE::open_placeholder set. Waiting for - latter will cause deadlock in the following scenario, for example: - - conn1: lock table t1 write; - conn2: lock table t2 write; - conn1: flush tables; - conn2: flush tables; - - It also does not make sense to wait for those of placeholders that - are employed by CREATE TABLE as in this case table simply does not - exist yet. - */ - if (table->needs_reopen_or_name_lock() && (table->db_stat || - (table->open_placeholder && wait_for_placeholders))) - { - found=1; - DBUG_PRINT("signal", ("Waiting for COND_refresh")); - mysql_cond_wait(&COND_refresh, &LOCK_open); - break; - } + TABLE_SHARE *share=(TABLE_SHARE*) my_hash_element(&table_def_cache, + idx); + if (share->version != refresh_version) + { + found= TRUE; + break; + } } } + else + { + for (TABLE_LIST *table= tables; table; table= table->next_local) + { + TABLE_SHARE *share= get_cached_table_share(table->db, table->table_name); + if (share && share->version != refresh_version) + { + found= TRUE; + break; + } + } + } + + if (found) + { + DBUG_PRINT("signal", ("Waiting for COND_refresh")); + mysql_cond_wait(&COND_refresh, &LOCK_open); + } + + thd->exit_cond(NULL); + } + +err_with_reopen: + if (thd->locked_tables_mode) + { /* No other thread has the locked tables open; reopen them and get the old locks. This should always succeed (unless some external process has removed the tables) */ - thd->in_lock_tables=1; - result=reopen_tables(thd,1,1); - thd->in_lock_tables=0; - /* Set version for table */ - for (TABLE *table=thd->open_tables; table ; table= table->next) - { - /* - Preserve the version (0) of write locked tables so that a impending - global read lock won't sneak in. - */ - if (table->reginfo.lock_type < TL_WRITE_ALLOW_WRITE) - table->s->version= refresh_version; - } - } - if (!have_lock) - mysql_mutex_unlock(&LOCK_open); - if (wait_for_refresh) - { - mysql_mutex_lock(&thd->mysys_var->mutex); - thd->mysys_var->current_mutex= 0; - thd->mysys_var->current_cond= 0; - thd_proc_info(thd, 0); - mysql_mutex_unlock(&thd->mysys_var->mutex); + thd->locked_tables_list.reopen_tables(thd); + /* + Since downgrade_exclusive_lock() won't do anything with shared + metadata lock it is much simpler to go through all open tables rather + than picking only those tables that were flushed. + */ + for (TABLE *tab= thd->open_tables; tab; tab= tab->next) + tab->mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE); } DBUG_RETURN(result); } @@ -1079,7 +1141,7 @@ bool close_cached_connection_tables(THD *thd, bool if_wait_for_refresh, } if (tables) - result= close_cached_tables(thd, tables, TRUE, FALSE, FALSE); + result= close_cached_tables(thd, tables, TRUE, FALSE); if (!have_lock) mysql_mutex_unlock(&LOCK_open); @@ -1112,43 +1174,54 @@ static void mark_temp_tables_as_free_for_reuse(THD *thd) for (TABLE *table= thd->temporary_tables ; table ; table= table->next) { if ((table->query_id == thd->query_id) && ! table->open_by_handler) - { - table->query_id= 0; - table->file->ha_reset(); - /* - Detach temporary MERGE children from temporary parent to allow new - attach at next open. Do not do the detach, if close_thread_tables() - is called from a sub-statement. The temporary table might still be - used in the top-level statement. - */ - if (table->child_l || table->parent) - detach_merge_children(table, TRUE); - /* - Reset temporary table lock type to it's default value (TL_WRITE). - - Statements such as INSERT INTO .. SELECT FROM tmp, CREATE TABLE - .. SELECT FROM tmp and UPDATE may under some circumstances modify - the lock type of the tables participating in the statement. This - isn't a problem for non-temporary tables since their lock type is - reset at every open, but the same does not occur for temporary - tables for historical reasons. - - Furthermore, the lock type of temporary tables is not really that - important because they can only be used by one query at a time and - not even twice in a query -- a temporary table is represented by - only one TABLE object. Nonetheless, it's safer from a maintenance - point of view to reset the lock type of this singleton TABLE object - as to not cause problems when the table is reused. - - Even under LOCK TABLES mode its okay to reset the lock type as - LOCK TABLES is allowed (but ignored) for a temporary table. - */ - table->reginfo.lock_type= TL_WRITE; - } + mark_tmp_table_for_reuse(table); } } +/** + Reset a single temporary table. + Effectively this "closes" one temporary table, + in a session. + + @param table Temporary table. +*/ + +void mark_tmp_table_for_reuse(TABLE *table) +{ + DBUG_ASSERT(table->s->tmp_table); + + table->query_id= 0; + table->file->ha_reset(); + + /* Detach temporary MERGE children from temporary parent. */ + DBUG_ASSERT(table->file); + table->file->extra(HA_EXTRA_DETACH_CHILDREN); + + /* + Reset temporary table lock type to it's default value (TL_WRITE). + + Statements such as INSERT INTO .. SELECT FROM tmp, CREATE TABLE + .. SELECT FROM tmp and UPDATE may under some circumstances modify + the lock type of the tables participating in the statement. This + isn't a problem for non-temporary tables since their lock type is + reset at every open, but the same does not occur for temporary + tables for historical reasons. + + Furthermore, the lock type of temporary tables is not really that + important because they can only be used by one query at a time and + not even twice in a query -- a temporary table is represented by + only one TABLE object. Nonetheless, it's safer from a maintenance + point of view to reset the lock type of this singleton TABLE object + as to not cause problems when the table is reused. + + Even under LOCK TABLES mode its okay to reset the lock type as + LOCK TABLES is allowed (but ignored) for a temporary table. + */ + table->reginfo.lock_type= TL_WRITE; +} + + /* Mark all tables in the list which were used by current substatement as free for reuse. @@ -1176,6 +1249,8 @@ static void mark_used_tables_as_free_for_reuse(THD *thd, TABLE *table) { for (; table ; table= table->next) { + DBUG_ASSERT(table->pos_in_locked_tables == NULL || + table->pos_in_locked_tables->table == table); if (table->query_id == thd->query_id) { table->query_id= 0; @@ -1205,12 +1280,10 @@ static void close_open_tables(THD *thd) while (thd->open_tables) found_old_table|= close_thread_table(thd, &thd->open_tables); - thd->some_tables_deleted= 0; /* Free tables to hold down open files */ - while (open_cache.records > table_cache_size && unused_tables) - my_hash_delete(&open_cache,(uchar*) unused_tables); /* purecov: tested */ - check_unused(); + while (table_cache_count > table_cache_size && unused_tables) + free_cache_entry(unused_tables); if (found_old_table) { /* Tell threads waiting for refresh that something has happened */ @@ -1221,6 +1294,79 @@ static void close_open_tables(THD *thd) } +/** + Close all open instances of the table but keep the MDL lock, + if any. + + Works both under LOCK TABLES and in the normal mode. + Removes all closed instances of the table from the table cache. + + @param thd thread handle + @param[in] share table share, but is just a handy way to + access the table cache key + + @param[in] remove_from_locked_tables + TRUE if the table is being dropped or renamed. + In that case the documented behaviour is to + implicitly remove the table from LOCK TABLES + list. +*/ + +void +close_all_tables_for_name(THD *thd, TABLE_SHARE *share, + bool remove_from_locked_tables) +{ + char key[MAX_DBKEY_LENGTH]; + uint key_length= share->table_cache_key.length; + + memcpy(key, share->table_cache_key.str, key_length); + + mysql_mutex_assert_not_owner(&LOCK_open); + /* + We need to hold LOCK_open while changing the open_tables + list, since another thread may work on it. + @sa mysql_notify_thread_having_shared_lock() + */ + mysql_mutex_lock(&LOCK_open); + + for (TABLE **prev= &thd->open_tables; *prev; ) + { + TABLE *table= *prev; + + if (table->s->table_cache_key.length == key_length && + !memcmp(table->s->table_cache_key.str, key, key_length)) + { + /* Inform handler that table will be dropped after close */ + if (table->db_stat) + table->file->extra(HA_EXTRA_PREPARE_FOR_DROP); + + /* + Does nothing if the table is not locked. + This allows one to use this function after a table + has been unlocked, e.g. in partition management. + */ + mysql_lock_remove(thd, thd->lock, table); + + thd->locked_tables_list.unlink_from_list(thd, + table->pos_in_locked_tables, + remove_from_locked_tables); + + /* Make sure the table is removed from the cache */ + table->s->version= 0; + close_thread_table(thd, prev); + } + else + { + /* Step to next entry in open_tables list. */ + prev= &table->next; + } + } + /* We have been removing tables from the table cache. */ + broadcast_refresh(); + mysql_mutex_unlock(&LOCK_open); +} + + /* Close all tables used by the current substatement, or all tables used by this thread if we are on the upper level. @@ -1242,7 +1388,6 @@ static void close_open_tables(THD *thd) void close_thread_tables(THD *thd) { TABLE *table; - prelocked_mode_type prelocked_mode= thd->prelocked_mode; DBUG_ENTER("close_thread_tables"); #ifdef EXTRA_DEBUG @@ -1252,6 +1397,20 @@ void close_thread_tables(THD *thd) table->s->table_name.str, (long) table)); #endif + /* Detach MERGE children after every statement. Even under LOCK TABLES. */ + for (table= thd->open_tables; table; table= table->next) + { + /* Table might be in use by some outer statement. */ + DBUG_PRINT("tcache", ("table: '%s' query_id: %lu", + table->s->table_name.str, (ulong) table->query_id)); + if (thd->locked_tables_mode <= LTM_LOCK_TABLES || + table->query_id == thd->query_id) + { + DBUG_ASSERT(table->file); + table->file->extra(HA_EXTRA_DETACH_CHILDREN); + } + } + /* We are assuming here that thd->derived_tables contains ONLY derived tables for this substatement. i.e. instead of approach which uses @@ -1293,18 +1452,19 @@ void close_thread_tables(THD *thd) if (!(thd->state_flags & Open_tables_state::BACKUPS_AVAIL)) { thd->stmt_da->can_overwrite_status= TRUE; - ha_autocommit_or_rollback(thd, thd->is_error()); + 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 (! prelocked_mode || thd->lex->requires_prelocking()) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES || + thd->lex->requires_prelocking()) thd->transaction.stmt.reset(); } - if (thd->locked_tables || prelocked_mode) + if (thd->locked_tables_mode) { /* Ensure we are calling ha_reset() for all used tables */ @@ -1313,8 +1473,13 @@ void close_thread_tables(THD *thd) /* We are under simple LOCK TABLES or we're inside a sub-statement of a prelocked statement, so should not do anything else. + + Note that even if we are in LTM_LOCK_TABLES mode and statement + requires prelocking (e.g. when we are closing tables after + failing ot "open" all tables required for statement execution) + we will exit this function a few lines below. */ - if (!prelocked_mode || !thd->lex->requires_prelocking()) + if (! thd->lex->requires_prelocking()) DBUG_VOID_RETURN; /* @@ -1322,14 +1487,14 @@ void close_thread_tables(THD *thd) so we have to leave the prelocked mode now with doing implicit UNLOCK TABLES if needed. */ - DBUG_PRINT("info",("thd->prelocked_mode= NON_PRELOCKED")); - thd->prelocked_mode= NON_PRELOCKED; + if (thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) + thd->locked_tables_mode= LTM_LOCK_TABLES; - if (prelocked_mode == PRELOCKED_UNDER_LOCK_TABLES) + if (thd->locked_tables_mode == LTM_LOCK_TABLES) DBUG_VOID_RETURN; - thd->lock= thd->locked_tables; - thd->locked_tables= 0; + thd->leave_locked_tables_mode(); + /* Fallthrough */ } @@ -1344,28 +1509,38 @@ void close_thread_tables(THD *thd) handled either before writing a query log event (inside binlog_query()) or when preparing a pending event. */ - (void)thd->binlog_flush_pending_rows_event(TRUE); + thd->binlog_flush_pending_rows_event(TRUE); mysql_unlock_tables(thd, thd->lock); thd->lock=0; } /* Note that we need to hold LOCK_open while changing the open_tables list. Another thread may work on it. - (See: remove_table_from_cache(), mysql_wait_completed_table()) + (See: mysql_notify_thread_having_shared_lock()) Closing a MERGE child before the parent would be fatal if the other thread tries to abort the MERGE lock in between. */ if (thd->open_tables) close_open_tables(thd); - if (prelocked_mode == PRELOCKED) + /* + - 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() && + ! (thd->state_flags & Open_tables_state::BACKUPS_AVAIL)) { - /* - If we are here then we are leaving normal prelocked mode, so it is - good idea to turn off OPTION_TABLE_LOCK flag. - */ - DBUG_ASSERT(thd->lex->requires_prelocking()); - thd->variables.option_bits&= ~(OPTION_TABLE_LOCK); + thd->mdl_context.release_transactional_locks(); } DBUG_VOID_RETURN; @@ -1381,48 +1556,29 @@ bool close_thread_table(THD *thd, TABLE **table_ptr) DBUG_ENTER("close_thread_table"); DBUG_ASSERT(table->key_read == 0); DBUG_ASSERT(!table->file || table->file->inited == handler::NONE); - DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str, - table->s->table_name.str, (long) table)); + mysql_mutex_assert_owner(&LOCK_open); *table_ptr=table->next; - /* - When closing a MERGE parent or child table, detach the children first. - Clear child table references to force new assignment at next open. - */ - if (table->child_l || table->parent) - detach_merge_children(table, TRUE); - if (table->needs_reopen_or_name_lock() || - thd->version != refresh_version || !table->db_stat) + table->mdl_ticket= NULL; + if (table->s->needs_reopen() || + thd->version != refresh_version || table->needs_reopen() || + table_def_shutdown_in_progress) { - my_hash_delete(&open_cache,(uchar*) table); + free_cache_entry(table); found_old_table=1; } else { - /* - Open placeholders have TABLE::db_stat set to 0, so they should be - handled by the first alternative. - */ - DBUG_ASSERT(!table->open_placeholder); - - /* Assert that MERGE children are not attached in unused_tables. */ - DBUG_ASSERT(!table->is_children_attached()); + /* Avoid to have MERGE tables with attached children in unused_tables. */ + DBUG_ASSERT(table->file); + table->file->extra(HA_EXTRA_DETACH_CHILDREN); /* Free memory and reset for next loop */ free_field_buffers_larger_than(table,MAX_TDC_BLOB_SIZE); table->file->ha_reset(); - table->in_use=0; - if (unused_tables) - { - table->next=unused_tables; /* Link in last */ - table->prev=unused_tables->prev; - unused_tables->prev=table; - table->prev->next=table; - } - else - unused_tables=table->next=table->prev=table; + table_def_unuse_table(table); } DBUG_RETURN(found_old_table); } @@ -1558,11 +1714,7 @@ void close_temporary_tables(THD *thd) qinfo.db= db.ptr(); qinfo.db_len= db.length(); thd->variables.character_set_client= cs_save; - if (mysql_bin_log.write(&qinfo)) - { - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_ERROR, MYF(0), - "Failed to write the DROP statement for temporary tables to binary log"); - } + mysql_bin_log.write(&qinfo); thd->variables.pseudo_thread_id= save_pseudo_thread_id; } else @@ -1827,9 +1979,10 @@ TABLE *find_temporary_table(THD *thd, TABLE_LIST *table_list) Try to locate the table in the list of thd->temporary_tables. If the table is found: - if the table is being used by some outer statement, fail. - - if the table is in thd->locked_tables, unlock it and - remove it from the list of locked tables. Currently only transactional - temporary tables are present in the locked_tables list. + - if the table is locked with LOCK TABLES or by prelocking, + unlock it and remove it from the list of locked tables + (THD::lock). Currently only transactional temporary tables + are locked. - Close the temporary table, remove its .FRM - remove the table from the list of temporary tables @@ -1868,7 +2021,7 @@ int drop_temporary_table(THD *thd, TABLE_LIST *table_list) If LOCK TABLES list is not empty and contains this table, unlock the table and remove the table from this list. */ - mysql_lock_remove(thd, thd->locked_tables, table, FALSE); + mysql_lock_remove(thd, thd->lock, table); close_temporary_table(thd, table, 1, 1); DBUG_RETURN(0); } @@ -1885,19 +2038,6 @@ void close_temporary_table(THD *thd, TABLE *table, table->s->db.str, table->s->table_name.str, (long) table, table->alias)); - /* - When closing a MERGE parent or child table, detach the children - first. Clear child table references as MERGE table cannot be - reopened after final close of one of its tables. - - This is necessary here because it is sometimes called with attached - tables and without prior close_thread_tables(). E.g. in - mysql_alter_table(), mysql_rm_table_part2(), mysql_truncate(), - drop_open_table(). - */ - if (table->child_l || table->parent) - detach_merge_children(table, TRUE); - if (table->prev) { table->prev->next= table->next; @@ -1984,198 +2124,84 @@ bool rename_temporary_table(THD* thd, TABLE *table, const char *db, } - /* move table first in unused links */ - -static void relink_unused(TABLE *table) -{ - /* Assert that MERGE children are not attached in unused_tables. */ - DBUG_ASSERT(!table->is_children_attached()); - - if (table != unused_tables) - { - table->prev->next=table->next; /* Remove from unused list */ - table->next->prev=table->prev; - table->next=unused_tables; /* Link in unused tables */ - table->prev=unused_tables->prev; - unused_tables->prev->next=table; - unused_tables->prev=table; - unused_tables=table; - check_unused(); - } -} - - /** - Prepare an open merge table for close. + Force all other threads to stop using the table by upgrading + metadata lock on it and remove unused TABLE instances from cache. - @param[in] thd thread context - @param[in] table table to prepare - @param[in,out] prev_pp pointer to pointer of previous table + @param thd Thread handler + @param table Table to remove from cache + @param function HA_EXTRA_PREPARE_FOR_DROP if table is to be deleted + HA_EXTRA_FORCE_REOPEN if table is not be used + HA_EXTRA_PREPARE_FOR_RENAME if table is to be renamed - @detail - If the table is a MERGE parent, just detach the children. - If the table is a MERGE child, close the parent (incl. detach). -*/ + @note When returning, the table will be unusable for other threads + until metadata lock is downgraded. -static void unlink_open_merge(THD *thd, TABLE *table, TABLE ***prev_pp) -{ - DBUG_ENTER("unlink_open_merge"); - - if (table->parent) - { - /* - If MERGE child, close parent too. Closing includes detaching. - - This is used for example in ALTER TABLE t1 RENAME TO t5 under - LOCK TABLES where t1 is a MERGE child: - CREATE TABLE t1 (c1 INT); - CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1); - LOCK TABLES t1 WRITE, t2 WRITE; - ALTER TABLE t1 RENAME TO t5; - */ - TABLE *parent= table->parent; - TABLE **prv_p; - - /* Find parent in open_tables list. */ - for (prv_p= &thd->open_tables; - *prv_p && (*prv_p != parent); - prv_p= &(*prv_p)->next) {} - if (*prv_p) - { - /* Special treatment required if child follows parent in list. */ - if (*prev_pp == &parent->next) - *prev_pp= prv_p; - /* - Remove parent from open_tables list and close it. - This includes detaching and hence clearing parent references. - */ - close_thread_table(thd, prv_p); - } - } - else if (table->child_l) - { - /* - When closing a MERGE parent, detach the children first. It is - not necessary to clear the child or parent table reference of - this table because the TABLE is freed. But we need to clear - the child or parent references of the other belonging tables - so that they cannot be moved into the unused_tables chain with - these pointers set. - - This is used for example in ALTER TABLE t2 RENAME TO t5 under - LOCK TABLES where t2 is a MERGE parent: - CREATE TABLE t1 (c1 INT); - CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1); - LOCK TABLES t1 WRITE, t2 WRITE; - ALTER TABLE t2 RENAME TO t5; - */ - detach_merge_children(table, TRUE); - } - - DBUG_VOID_RETURN; -} - - -/** - Remove all instances of table from thread's open list and - table cache. - - @param thd Thread context - @param find Table to remove - @param unlock TRUE - free all locks on tables removed that are - done with LOCK TABLES - FALSE - otherwise - - @note When unlock parameter is FALSE or current thread doesn't have - any tables locked with LOCK TABLES, tables are assumed to be - not locked (for example already unlocked). + @retval FALSE Success. + @retval TRUE Failure (e.g. because thread was killed). */ -void unlink_open_table(THD *thd, TABLE *find, bool unlock) +bool wait_while_table_is_used(THD *thd, TABLE *table, + enum ha_extra_function function) { - char key[MAX_DBKEY_LENGTH]; - uint key_length= find->s->table_cache_key.length; - TABLE *list, **prev; - DBUG_ENTER("unlink_open_table"); - - mysql_mutex_assert_owner(&LOCK_open); - - memcpy(key, find->s->table_cache_key.str, key_length); - /* - Note that we need to hold LOCK_open while changing the - open_tables list. Another thread may work on it. - (See: remove_table_from_cache(), mysql_wait_completed_table()) - Closing a MERGE child before the parent would be fatal if the - other thread tries to abort the MERGE lock in between. - */ - for (prev= &thd->open_tables; *prev; ) - { - list= *prev; - - if (list->s->table_cache_key.length == key_length && - !memcmp(list->s->table_cache_key.str, key, key_length)) - { - if (unlock && thd->locked_tables) - mysql_lock_remove(thd, thd->locked_tables, - list->parent ? list->parent : list, TRUE); + DBUG_ENTER("wait_while_table_is_used"); + DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu", + table->s->table_name.str, (ulong) table->s, + table->db_stat, table->s->version)); - /* Prepare MERGE table for close. Close parent if necessary. */ - unlink_open_merge(thd, list, &prev); - - /* Remove table from open_tables list. */ - *prev= list->next; - /* Close table. */ - my_hash_delete(&open_cache,(uchar*) list); // Close table - } - else - { - /* Step to next entry in open_tables list. */ - prev= &list->next; - } - } + if (thd->mdl_context.upgrade_shared_lock_to_exclusive(table->mdl_ticket)) + DBUG_RETURN(TRUE); - // Notify any 'refresh' threads - broadcast_refresh(); - DBUG_VOID_RETURN; + mysql_mutex_lock(&LOCK_open); + tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN, + table->s->db.str, table->s->table_name.str); + mysql_mutex_unlock(&LOCK_open); + /* extra() call must come only after all instances above are closed */ + (void) table->file->extra(function); + DBUG_RETURN(FALSE); } /** - Auxiliary routine which closes and drops open table. - - @param thd Thread handle - @param table TABLE object for table to be dropped - @param db_name Name of database for this table - @param table_name Name of this table - - @note This routine assumes that table to be closed is open only - by calling thread so we needn't wait until other threads - will close the table. Also unless called under implicit or - explicit LOCK TABLES mode it assumes that table to be - dropped is already unlocked. In the former case it will - also remove lock on the table. But one should not rely on - this behaviour as it may change in future. - Currently, however, this function is never called for a - table that was locked with LOCK TABLES. + Close a and drop a just created table in CREATE TABLE ... SELECT. + + @param thd Thread handle + @param table TABLE object for the table to be dropped + @param db_name Name of database for this table + @param table_name Name of this table + + This routine assumes that the table to be closed is open only + by the calling thread, so we needn't wait until other threads + close the table. It also assumes that the table is first + in thd->open_ables and a data lock on it, if any, has been + released. To sum up, it's tuned to work with + CREATE TABLE ... SELECT and CREATE TABLE .. SELECT only. + Note, that currently CREATE TABLE ... SELECT is not supported + under LOCK TABLES. This function, still, can be called in + prelocked mode, e.g. if we do CREATE TABLE .. SELECT f1(); */ void drop_open_table(THD *thd, TABLE *table, const char *db_name, const char *table_name) { + DBUG_ENTER("drop_open_table"); if (table->s->tmp_table) close_temporary_table(thd, table, 1, 1); else { + DBUG_ASSERT(table == thd->open_tables); + handlerton *table_type= table->s->db_type(); + /* Ensure the table is removed from the cache. */ + table->s->version= 0; + mysql_mutex_lock(&LOCK_open); - /* - unlink_open_table() also tells threads waiting for refresh or close - that something has happened. - */ - unlink_open_table(thd, table, FALSE); + table->file->extra(HA_EXTRA_PREPARE_FOR_DROP); + close_thread_table(thd, &thd->open_tables); quick_rm_table(table_type, db_name, table_name, 0); mysql_mutex_unlock(&LOCK_open); } + DBUG_VOID_RETURN; } @@ -2224,234 +2250,6 @@ void wait_for_condition(THD *thd, mysql_mutex_t *mutex, mysql_cond_t *cond) /** - Exclusively name-lock a table that is already write-locked by the - current thread. - - @param thd current thread context - @param tables table list containing one table to open. - - @return FALSE on success, TRUE otherwise. -*/ - -bool name_lock_locked_table(THD *thd, TABLE_LIST *tables) -{ - DBUG_ENTER("name_lock_locked_table"); - - /* Under LOCK TABLES we must only accept write locked tables. */ - tables->table= find_locked_table(thd, tables->db, tables->table_name); - - if (!tables->table) - my_error(ER_TABLE_NOT_LOCKED, MYF(0), tables->alias); - else if (tables->table->reginfo.lock_type < TL_WRITE_LOW_PRIORITY) - my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), tables->alias); - else - { - /* - Ensures that table is opened only by this thread and that no - other statement will open this table. - */ - wait_while_table_is_used(thd, tables->table, HA_EXTRA_FORCE_REOPEN); - DBUG_RETURN(FALSE); - } - - DBUG_RETURN(TRUE); -} - - -/* - Open table which is already name-locked by this thread. - - SYNOPSIS - reopen_name_locked_table() - thd Thread handle - table_list TABLE_LIST object for table to be open, TABLE_LIST::table - member should point to TABLE object which was used for - name-locking. - link_in TRUE - if TABLE object for table to be opened should be - linked into THD::open_tables list. - FALSE - placeholder used for name-locking is already in - this list so we only need to preserve TABLE::next - pointer. - - NOTE - This function assumes that its caller already acquired LOCK_open mutex. - - RETURN VALUE - FALSE - Success - TRUE - Error -*/ - -bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in) -{ - TABLE *table= table_list->table; - TABLE_SHARE *share; - char *table_name= table_list->table_name; - TABLE orig_table; - DBUG_ENTER("reopen_name_locked_table"); - - mysql_mutex_assert_owner(&LOCK_open); - - if (thd->killed || !table) - DBUG_RETURN(TRUE); - - orig_table= *table; - - if (open_unireg_entry(thd, table, table_list, table_name, - table->s->table_cache_key.str, - table->s->table_cache_key.length, thd->mem_root, 0)) - { - intern_close_table(table); - /* - If there was an error during opening of table (for example if it - does not exist) '*table' object can be wiped out. To be able - properly release name-lock in this case we should restore this - object to its original state. - */ - *table= orig_table; - DBUG_RETURN(TRUE); - } - - share= table->s; - /* - We want to prevent other connections from opening this table until end - of statement as it is likely that modifications of table's metadata are - not yet finished (for example CREATE TRIGGER have to change .TRG file, - or we might want to drop table if CREATE TABLE ... SELECT fails). - This also allows us to assume that no other connection will sneak in - before we will get table-level lock on this table. - */ - share->version=0; - table->in_use = thd; - check_unused(); - - if (link_in) - { - table->next= thd->open_tables; - thd->open_tables= table; - } - else - { - /* - TABLE object should be already in THD::open_tables list so we just - need to set TABLE::next correctly. - */ - table->next= orig_table.next; - } - - table->tablenr=thd->current_tablenr++; - table->used_fields=0; - table->const_table=0; - table->null_row= table->maybe_null= 0; - table->force_index= table->force_index_order= table->force_index_group= 0; - table->status=STATUS_NO_RECORD; - DBUG_RETURN(FALSE); -} - - -/** - Create and insert into table cache placeholder for table - which will prevent its opening (or creation) (a.k.a lock - table name). - - @param thd Thread context - @param key Table cache key for name to be locked - @param key_length Table cache key length - - @return Pointer to TABLE object used for name locking or 0 in - case of failure. -*/ - -TABLE *table_cache_insert_placeholder(THD *thd, const char *key, - uint key_length) -{ - TABLE *table; - TABLE_SHARE *share; - char *key_buff; - DBUG_ENTER("table_cache_insert_placeholder"); - - mysql_mutex_assert_owner(&LOCK_open); - - /* - Create a table entry with the right key and with an old refresh version - Note that we must use my_multi_malloc() here as this is freed by the - table cache - */ - if (!my_multi_malloc(MYF(MY_WME | MY_ZEROFILL), - &table, sizeof(*table), - &share, sizeof(*share), - &key_buff, key_length, - NULL)) - DBUG_RETURN(NULL); - - table->s= share; - share->set_table_cache_key(key_buff, key, key_length); - share->tmp_table= INTERNAL_TMP_TABLE; // for intern_close_table - table->in_use= thd; - table->locked_by_name=1; - - if (my_hash_insert(&open_cache, (uchar*)table)) - { - my_free((uchar*) table, MYF(0)); - DBUG_RETURN(NULL); - } - - DBUG_RETURN(table); -} - - -/** - Obtain an exclusive name lock on the table if it is not cached - in the table cache. - - @param thd Thread context - @param db Name of database - @param table_name Name of table - @param[out] table Out parameter which is either: - - set to NULL if table cache contains record for - the table or - - set to point to the TABLE instance used for - name-locking. - - @note This function takes into account all records for table in table - cache, even placeholders used for name-locking. This means that - 'table' parameter can be set to NULL for some situations when - table does not really exist. - - @retval TRUE Error occured (OOM) - @retval FALSE Success. 'table' parameter set according to above rules. -*/ - -bool lock_table_name_if_not_cached(THD *thd, const char *db, - const char *table_name, TABLE **table) -{ - char key[MAX_DBKEY_LENGTH]; - uint key_length; - DBUG_ENTER("lock_table_name_if_not_cached"); - - key_length= (uint)(strmov(strmov(key, db) + 1, table_name) - key) + 1; - mysql_mutex_lock(&LOCK_open); - - if (my_hash_search(&open_cache, (uchar *)key, key_length)) - { - mysql_mutex_unlock(&LOCK_open); - DBUG_PRINT("info", ("Table is cached, name-lock is not obtained")); - *table= 0; - DBUG_RETURN(FALSE); - } - if (!(*table= table_cache_insert_placeholder(thd, key, key_length))) - { - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(TRUE); - } - (*table)->open_placeholder= 1; - (*table)->next= thd->open_tables; - thd->open_tables= *table; - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(FALSE); -} - - -/** Check that table exists in table definition cache, on disk or in some storage engine. @@ -2461,8 +2259,8 @@ bool lock_table_name_if_not_cached(THD *thd, const char *db, exists and to FALSE otherwise. @note This function assumes that caller owns LOCK_open mutex. - It also assumes that the fact that there are no name-locks - on the table was checked beforehand. + It also assumes that the fact that there are no exclusive + metadata locks on the table was checked beforehand. @note If there is no .FRM file for the table but it exists in one of engines (e.g. it was created on another node of NDB cluster) @@ -2515,6 +2313,100 @@ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists) } +/** + @brief Helper function used by MDL subsystem for releasing TABLE_SHARE + objects in cases when it no longer wants to cache reference to it. +*/ + +void table_share_release_hook(void *share) +{ + mysql_mutex_lock(&LOCK_open); + release_table_share((TABLE_SHARE*) share); + broadcast_refresh(); + mysql_mutex_unlock(&LOCK_open); +} + + +/** + A helper function that acquires an MDL lock for a table + being opened. +*/ + +static bool +open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list, + MDL_request *mdl_request, + Open_table_context *ot_ctx, + uint flags) +{ + if (table_list->lock_strategy) + { + MDL_request_list mdl_requests; + MDL_request *global_request; + /* + In case of CREATE TABLE .. If NOT EXISTS .. SELECT, the table + may not yet exist. Let's acquire an exclusive lock for that + case. If later it turns out the table existsed, we will + downgrade the lock to shared. Note that, according to the + locking protocol, all exclusive locks must be acquired before + shared locks. This invariant is preserved here and is also + enforced by asserts in metadata locking subsystem. + */ + + mdl_request->set_type(MDL_EXCLUSIVE); + DBUG_ASSERT(! thd->mdl_context.has_locks() || + thd->handler_tables_hash.records || + thd->global_read_lock.is_acquired()); + + if (!(global_request= ot_ctx->get_global_mdl_request(thd))) + return 1; + + mdl_requests.push_front(mdl_request); + mdl_requests.push_front(global_request); + + if (thd->mdl_context.acquire_locks(&mdl_requests)) + return 1; + } + else + { + if (flags & MYSQL_OPEN_FORCE_SHARED_MDL) + { + /* + While executing PREPARE for prepared statement we override + type-of-operation aware type of shared metadata lock which + was set in the parser with simple shared metadata lock. + This is necessary to allow concurrent execution of PREPARE + and LOCK TABLES WRITE statement which locks one of the tables + used in the statement being prepared. + */ + DBUG_ASSERT(!(flags & (MYSQL_OPEN_TAKE_UPGRADABLE_MDL | + MYSQL_LOCK_IGNORE_FLUSH))); + + mdl_request->set_type(MDL_SHARED); + } + else if (flags & MYSQL_LOCK_IGNORE_FLUSH) + { + DBUG_ASSERT(!(flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL)); + mdl_request->set_type(MDL_SHARED_HIGH_PRIO); + } + + ot_ctx->add_request(mdl_request); + + if (thd->mdl_context.try_acquire_lock(mdl_request)) + return 1; + + if (mdl_request->ticket == NULL) + { + if (flags & MYSQL_OPEN_FAIL_ON_MDL_CONFLICT) + my_error(ER_WARN_I_S_SKIPPED_TABLE, MYF(0), table_list->db, table_list->table_name); + else + ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_MDL_LOCK); + return 1; + } + } + return 0; +} + + /* Open a table. @@ -2522,53 +2414,65 @@ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists) open_table() thd Thread context. table_list Open first table in list. - refresh INOUT Pointer to memory that will be set to 1 if - we need to close all tables and reopen them. - If this is a NULL pointer, then the table is not - put in the thread-open-list. + action INOUT Pointer to variable of enum_open_table_action type + which will be set according to action which is + required to remedy problem appeared during attempt + to open table. flags Bitmap of flags to modify how open works: MYSQL_LOCK_IGNORE_FLUSH - Open table even if - someone has done a flush or namelock on it. + someone has done a flush or there is a pending + exclusive metadata lock requests against it + (i.e. request high priority metadata lock). No version number checking is done. MYSQL_OPEN_TEMPORARY_ONLY - Open only temporary table not the base table or view. + MYSQL_OPEN_TAKE_UPGRADABLE_MDL - Obtain upgradable + metadata lock for tables on which we are going to + take some kind of write table-level lock. IMPLEMENTATION Uses a cache of open tables to find a table not in use. - If table list element for the table to be opened has "create" flag - set and table does not exist, this function will automatically insert - a placeholder for exclusive name lock into the open tables cache and - will return the TABLE instance that corresponds to this placeholder. + If TABLE_LIST::open_strategy is set to OPEN_IF_EXISTS, the table is opened + only if it exists. If the open strategy is OPEN_STUB, the underlying table + is never opened. In both cases, metadata locks are always taken according + to the lock strategy. + + This function will take a exclusive metadata lock on the table if + TABLE_LIST::lock_strategy is EXCLUSIVE_DOWNGRADABLE_MDL or EXCLUSIVE_MDL. + If the lock strategy is EXCLUSIVE_DOWNGRADABLE_MDL and opening the table + is successful, the exclusive metadata lock is downgraded to a shared + lock. RETURN - NULL Open failed. If refresh is set then one should close - all other tables and retry the open. - # Success. Pointer to TABLE object for open table. + TRUE Open failed. "action" parameter may contain type of action + needed to remedy problem before retrying again. + FALSE Success. Members of TABLE_LIST structure are filled properly (e.g. + TABLE_LIST::table is set for real tables and TABLE_LIST::view is + set for views). */ -TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, - bool *refresh, uint flags) +bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, + Open_table_context *ot_ctx, uint flags) { reg1 TABLE *table; char key[MAX_DBKEY_LENGTH]; uint key_length; char *alias= table_list->alias; + MDL_request *mdl_request; + MDL_ticket *mdl_ticket; + int error; + TABLE_SHARE *share; my_hash_value_type hash_value; - HASH_SEARCH_STATE state; DBUG_ENTER("open_table"); - /* find a unused table in the open table cache */ - if (refresh) - *refresh=0; - /* an open table operation needs a lot of the stack space */ if (check_stack_overrun(thd, STACK_MIN_SIZE_FOR_OPEN, (uchar *)&alias)) - DBUG_RETURN(0); + DBUG_RETURN(TRUE); if (thd->killed) - DBUG_RETURN(0); + DBUG_RETURN(TRUE); key_length= (create_table_def_key(thd, key, table_list, 1) - TMP_TABLE_KEY_EXTRA); @@ -2580,7 +2484,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, same name. This block implements the behaviour. TODO: move this block into a separate function. */ - if (!table_list->skip_temporary) + if (table_list->open_type != OT_BASE_ONLY && + ! (flags & MYSQL_OPEN_SKIP_TEMPORARY)) { for (table= thd->temporary_tables; table ; table=table->next) { @@ -2602,7 +2507,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, (ulong) table->query_id, (uint) thd->server_id, (ulong) thd->variables.pseudo_thread_id)); my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias); - DBUG_RETURN(0); + DBUG_RETURN(TRUE); } table->query_id= thd->query_id; thd->thread_specific_used= TRUE; @@ -2612,10 +2517,16 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, } } - if (flags & MYSQL_OPEN_TEMPORARY_ONLY) + if (table_list->open_type == OT_TEMPORARY_ONLY || + (flags & MYSQL_OPEN_TEMPORARY_ONLY)) { - my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->table_name); - DBUG_RETURN(0); + if (table_list->open_strategy == TABLE_LIST::OPEN_NORMAL) + { + my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->table_name); + DBUG_RETURN(TRUE); + } + else + DBUG_RETURN(FALSE); } /* @@ -2625,7 +2536,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, open not pre-opened tables in pre-locked/LOCK TABLES mode. TODO: move this block into a separate function. */ - if (thd->locked_tables || thd->prelocked_mode) + if (thd->locked_tables_mode && + ! (flags & MYSQL_OPEN_GET_NEW_TABLE)) { // Using table locks TABLE *best_table= 0; int best_distance= INT_MIN; @@ -2634,17 +2546,24 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (table->s->table_cache_key.length == key_length && !memcmp(table->s->table_cache_key.str, key, key_length)) { - /* - When looking for a usable TABLE, ignore MERGE children, as they - belong to their parent and cannot be used explicitly. - */ if (!my_strcasecmp(system_charset_info, table->alias, alias) && table->query_id != thd->query_id && /* skip tables already used */ - !(thd->prelocked_mode && table->query_id) && - !table->parent) + (thd->locked_tables_mode == LTM_LOCK_TABLES || + table->query_id == 0)) { int distance= ((int) table->reginfo.lock_type - (int) table_list->lock_type); + + /* + If we are performing DDL operation we also should ensure + that we will find TABLE instance with upgradable metadata + lock, + */ + if ((flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) && + table_list->lock_type >= TL_WRITE_ALLOW_WRITE && + ! table->mdl_ticket->is_upgradable_or_exclusive()) + distance= -1; + /* Find a table that either has the exact lock type requested, or has the best suitable lock. In case there is no locked @@ -2678,6 +2597,13 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, } if (best_table) { + if ((flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) && + table_list->lock_type >= TL_WRITE_ALLOW_WRITE && + ! best_table->mdl_ticket->is_upgradable_or_exclusive()) + { + my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), alias); + DBUG_RETURN(TRUE); + } table= best_table; table->query_id= thd->query_id; DBUG_PRINT("info",("Using locked table")); @@ -2687,29 +2613,35 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, Is this table a view and not a base table? (it is work around to allow to open view with locked tables, real fix will be made after definition cache will be made) + + Since opening of view which was not explicitly locked by LOCK + TABLES breaks metadata locking protocol (potentially can lead + to deadlocks) it should be disallowed. */ + if (thd->mdl_context.is_lock_owner(MDL_key::TABLE, + table_list->db, + table_list->table_name, + MDL_SHARED)) { char path[FN_REFLEN + 1]; enum legacy_db_type not_used; build_table_filename(path, sizeof(path) - 1, table_list->db, table_list->table_name, reg_ext, 0); + /* + Note that we can't be 100% sure that it is a view since it's + possible that we either simply have not found unused TABLE + instance in THD::open_tables list or were unable to open table + during prelocking process (in this case in theory we still + should hold shared metadata lock on it). + */ if (mysql_frm_type(thd, path, ¬_used) == FRMTYPE_VIEW) { - /* - Will not be used (because it's VIEW) but has to be passed. - Also we will not free it (because it is a stack variable). - */ - TABLE tab; - table= &tab; - mysql_mutex_lock(&LOCK_open); - if (!open_unireg_entry(thd, table, table_list, alias, - key, key_length, mem_root, 0)) + if (!tdc_open_view(thd, table_list, alias, key, key_length, + mem_root, 0)) { DBUG_ASSERT(table_list->view != 0); - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(0); // VIEW + DBUG_RETURN(FALSE); // VIEW } - mysql_mutex_unlock(&LOCK_open); } } /* @@ -2719,30 +2651,37 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, so we may only end up here if the table did not exist when locked tables list was created. */ - if (thd->prelocked_mode == PRELOCKED) + if (thd->locked_tables_mode == LTM_PRELOCKED) my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->alias); else my_error(ER_TABLE_NOT_LOCKED, MYF(0), alias); - DBUG_RETURN(0); + DBUG_RETURN(TRUE); + } + + /* + Non pre-locked/LOCK TABLES mode, and the table is not temporary. + This is the normal use case. + */ + + mdl_request= &table_list->mdl_request; + if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK)) + { + if (open_table_get_mdl_lock(thd, table_list, mdl_request, ot_ctx, flags)) + { + DEBUG_SYNC(thd, "before_open_table_wait_refresh"); + DBUG_RETURN(TRUE); + } + DEBUG_SYNC(thd, "after_open_table_mdl_shared"); } /* - Non pre-locked/LOCK TABLES mode, and the table is not temporary: - this is the normal use case. - Now we should: - - try to find the table in the table cache. - - if one of the discovered TABLE instances is name-locked - (table->s->version == 0) or some thread has started FLUSH TABLES - (refresh_version > table->s->version), back off -- we have to wait - until no one holds a name lock on the table. - - if there is no such TABLE in the name cache, read the table definition - and insert it into the cache. - We perform all of the above under LOCK_open which currently protects - the open cache (also known as table cache) and table definitions stored - on disk. + Grab reference to the granted MDL lock ticket. Must be done after + open_table_get_mdl_lock as the lock on the table might have been + acquired previously (MYSQL_OPEN_HAS_MDL_LOCK). */ + mdl_ticket= mdl_request->ticket; - hash_value= my_calc_hash(&open_cache, (uchar*) key, key_length); + hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length); mysql_mutex_lock(&LOCK_open); /* @@ -2759,234 +2698,226 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, ! (flags & MYSQL_LOCK_IGNORE_FLUSH)) { /* Someone did a refresh while thread was opening tables */ - if (refresh) - *refresh=1; mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(0); + (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC); + DBUG_RETURN(TRUE); } - /* - In order for the back off and re-start process to work properly, - handler tables having old versions (due to FLUSH TABLES or pending - name-lock) MUST be closed. This is specially important if a name-lock - is pending for any table of the handler_tables list, otherwise a - deadlock may occur. - */ - if (thd->handler_tables) - mysql_ha_flush(thd); + if (table_list->open_strategy == TABLE_LIST::OPEN_IF_EXISTS) + { + bool exists; - /* - Actually try to find the table in the open_cache. - The cache may contain several "TABLE" instances for the same - physical table. The instances that are currently "in use" by - some thread have their "in_use" member != NULL. - There is no good reason for having more than one entry in the - hash for the same physical table, except that we use this as - an implicit "pending locks queue" - see - wait_for_locked_table_names for details. - */ - for (table= (TABLE*) my_hash_first_from_hash_value(&open_cache, - hash_value, - (uchar*) key, - key_length, - &state); - table && table->in_use ; - table= (TABLE*) my_hash_next(&open_cache, (uchar*) key, key_length, - &state)) - { - DBUG_PRINT("tcache", ("in_use table: '%s'.'%s' 0x%lx", table->s->db.str, - table->s->table_name.str, (long) table)); - /* - Here we flush tables marked for flush. - Normally, table->s->version contains the value of - refresh_version from the moment when this table was - (re-)opened and added to the cache. - If since then we did (or just started) FLUSH TABLES - statement, refresh_version has been increased. - For "name-locked" TABLE instances, table->s->version is set - to 0 (see lock_table_name for details). - In case there is a pending FLUSH TABLES or a name lock, we - need to back off and re-start opening tables. - If we do not back off now, we may dead lock in case of lock - order mismatch with some other thread: - c1: name lock t1; -- sort of exclusive lock - c2: open t2; -- sort of shared lock - c1: name lock t2; -- blocks - c2: open t1; -- blocks - */ - if (table->needs_reopen_or_name_lock()) - { - DBUG_PRINT("note", - ("Found table '%s.%s' with different refresh version", - table_list->db, table_list->table_name)); + if (check_if_table_exists(thd, table_list, &exists)) + goto err_unlock2; - if (flags & MYSQL_LOCK_IGNORE_FLUSH) - { - /* Force close at once after usage */ - thd->version= table->s->version; - continue; - } + if (!exists) + { + mysql_mutex_unlock(&LOCK_open); + DBUG_RETURN(FALSE); + } + /* Table exists. Let us try to open it. */ + } + else if (table_list->open_strategy == TABLE_LIST::OPEN_STUB) + { + mysql_mutex_unlock(&LOCK_open); + DBUG_RETURN(FALSE); + } - /* Avoid self-deadlocks by detecting self-dependencies. */ - if (table->open_placeholder && table->in_use == thd) - { - mysql_mutex_unlock(&LOCK_open); - my_error(ER_UPDATE_TABLE_USED, MYF(0), table->s->table_name.str); - DBUG_RETURN(0); - } +#ifdef DISABLED_UNTIL_GRL_IS_MADE_PART_OF_MDL + if (!(share= (TABLE_SHARE *) mdl_ticket->get_cached_object())) +#endif + { + if (!(share= get_table_share_with_create(thd, table_list, key, + key_length, OPEN_VIEW, + &error, + hash_value))) + goto err_unlock2; + if (share->is_view) + { /* - Back off, part 1: mark the table as "unused" for the - purpose of name-locking by setting table->db_stat to 0. Do - that only for the tables in this thread that have an old - table->s->version (this is an optimization (?)). - table->db_stat == 0 signals wait_for_locked_table_names - that the tables in question are not used any more. See - table_is_used call for details. - - Notice that HANDLER tables were already taken care of by - the earlier call to mysql_ha_flush() in this same critical - section. + If parent_l of the table_list is non null then a merge table + has this view as child table, which is not supported. */ - close_old_data_files(thd,thd->open_tables,0,0); + if (table_list->parent_l) + { + my_error(ER_WRONG_MRG_TABLE, MYF(0)); + goto err_unlock; + } + /* - Back-off part 2: try to avoid "busy waiting" on the table: - if the table is in use by some other thread, we suspend - and wait till the operation is complete: when any - operation that juggles with table->s->version completes, - it broadcasts COND_refresh condition variable. - If 'old' table we met is in use by current thread we return - without waiting since in this situation it's this thread - which is responsible for broadcasting on COND_refresh - (and this was done already in close_old_data_files()). - Good example of such situation is when we have statement - that needs two instances of table and FLUSH TABLES comes - after we open first instance but before we open second - instance. + This table is a view. Validate its metadata version: in particular, + that it was a view when the statement was prepared. */ - if (table->in_use != thd) + if (check_and_update_table_version(thd, table_list, share)) + goto err_unlock; + if (table_list->i_s_requested_object & OPEN_TABLE_ONLY) + goto err_unlock; + + /* Open view */ + if (open_new_frm(thd, share, alias, + (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | + HA_GET_INDEX | HA_TRY_READ_ONLY), + READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD | + (flags & OPEN_VIEW_NO_PARSE), thd->open_options, + 0, table_list, mem_root)) + goto err_unlock; + + /* TODO: Don't free this */ + release_table_share(share); + + if (flags & OPEN_VIEW_NO_PARSE) { - /* wait_for_condition will unlock LOCK_open for us */ - wait_for_condition(thd, &LOCK_open, &COND_refresh); + /* + VIEW not really opened, only frm were read. + Set 1 as a flag here + */ + table_list->view= (LEX*)1; } else { - mysql_mutex_unlock(&LOCK_open); + DBUG_ASSERT(table_list->view); } - /* - There is a refresh in progress for this table. - Signal the caller that it has to try again. - */ - if (refresh) - *refresh=1; - DBUG_RETURN(0); + + mysql_mutex_unlock(&LOCK_open); + DBUG_RETURN(FALSE); } + /* + Note that situation when we are trying to open a table for what + was a view during previous execution of PS will be handled in by + the caller. Here we should simply open our table even if + TABLE_LIST::view is true. + */ + + if (table_list->i_s_requested_object & OPEN_VIEW_ONLY) + goto err_unlock; + +#ifdef DISABLED_UNTIL_GRL_IS_MADE_PART_OF_MDL + /* + We are going to to store extra reference to the share in MDL-subsystem + so we need to increase reference counter; + */ + reference_table_share(share); + mdl_ticket->set_cached_object(share, table_share_release_hook); +#endif } - if (table) +#ifdef DISABLED_UNTIL_GRL_IS_MADE_PART_OF_MDL + else { - DBUG_PRINT("tcache", ("unused table: '%s'.'%s' 0x%lx", table->s->db.str, - table->s->table_name.str, (long) table)); - /* Unlink the table from "unused_tables" list. */ - if (table == unused_tables) - { // First unused - unused_tables=unused_tables->next; // Remove from link - if (table == unused_tables) - unused_tables=0; + if (table_list->view) + { + DBUG_ASSERT(thd->m_reprepare_observer); + check_and_update_table_version(thd, table_list, share); + /* Always an error. */ + DBUG_ASSERT(thd->is_error()); + goto err_unlock; } - table->prev->next=table->next; /* Remove from unused list */ - table->next->prev=table->prev; - table->in_use= thd; + /* When we have cached TABLE_SHARE we know that is not a view. */ + if (table_list->i_s_requested_object & OPEN_VIEW_ONLY) + goto err_unlock; + + /* + We are going to use this share for construction of new TABLE object + so reference counter should be increased. + */ + reference_table_share(share); + } +#endif + + if (share->version != refresh_version) + { + if (!(flags & MYSQL_LOCK_IGNORE_FLUSH)) + { + /* + We already have an MDL lock. But we have encountered an old + version of table in the table definition cache which is possible + when someone changes the table version directly in the cache + without acquiring a metadata lock (e.g. this can happen during + "rolling" FLUSH TABLE(S)). + Note, that to avoid a "busywait" in this case, we have to wait + separately in the caller for old table versions to go away + (see tdc_wait_for_old_versions()). + */ + release_table_share(share); + mysql_mutex_unlock(&LOCK_open); + (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC); + DBUG_RETURN(TRUE); + } + /* Force close at once after usage */ + thd->version= share->version; + } + + if (!share->free_tables.is_empty()) + { + table= share->free_tables.front(); + table_def_use_table(thd, table); + /* We need to release share as we have EXTRA reference to it in our hands. */ + release_table_share(share); } else { - /* Insert a new TABLE instance into the open cache */ - int error; - DBUG_PRINT("tcache", ("opening new table")); - /* Free cache if too big */ - while (open_cache.records > table_cache_size && unused_tables) - my_hash_delete(&open_cache,(uchar*) unused_tables); /* purecov: tested */ + /* We have too many TABLE instances around let us try to get rid of them. */ + while (table_cache_count > table_cache_size && unused_tables) + free_cache_entry(unused_tables); - if (table_list->create) + /* make a new table */ + if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME)))) + goto err_unlock; + + error= open_table_from_share(thd, share, alias, + (uint) (HA_OPEN_KEYFILE | + HA_OPEN_RNDFILE | + HA_GET_INDEX | + HA_TRY_READ_ONLY), + (READ_KEYINFO | COMPUTE_TYPES | + EXTRA_RECORD), + thd->open_options, table, FALSE); + + if (error) { - bool exists; + my_free(table, MYF(0)); - if (check_if_table_exists(thd, table_list, &exists)) + if (error == 7) { - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(NULL); + share->version= 0; + (void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER); } - - if (!exists) + else if (share->crashed) { - /* - Table to be created, so we need to create placeholder in table-cache. - */ - if (!(table= table_cache_insert_placeholder(thd, key, key_length))) - { - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(NULL); - } - /* - Link placeholder to the open tables list so it will be automatically - removed once tables are closed. Also mark it so it won't be ignored - by other trying to take name-lock. - */ - table->open_placeholder= 1; - table->next= thd->open_tables; - thd->open_tables= table; - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(table); + share->version= 0; + (void) ot_ctx->request_backoff_action(Open_table_context::OT_REPAIR); } - /* Table exists. Let us try to open it. */ - } - /* make a new table */ - if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME)))) - { - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(NULL); + goto err_unlock; } - error= open_unireg_entry(thd, table, table_list, alias, key, key_length, - mem_root, (flags & OPEN_VIEW_NO_PARSE)); - if (error > 0) + if (open_table_entry_fini(thd, share, table)) { + closefrm(table, 0); my_free((uchar*)table, MYF(0)); - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(NULL); + goto err_unlock; } - if (table_list->view || error < 0) - { - /* - VIEW not really opened, only frm were read. - Set 1 as a flag here - */ - if (error < 0) - table_list->view= (LEX*)1; - my_free((uchar*)table, MYF(0)); - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(0); // VIEW - } - DBUG_PRINT("info", ("inserting table '%s'.'%s' 0x%lx into the cache", - table->s->db.str, table->s->table_name.str, - (long) table)); - if (my_hash_insert(&open_cache,(uchar*) table)) - { - my_free(table, MYF(0)); - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(NULL); - } + /* Add table to the share's used tables list. */ + table_def_add_used_table(thd, table); } - check_unused(); // Debugging call - mysql_mutex_unlock(&LOCK_open); - if (refresh) - { - table->next=thd->open_tables; /* Link into simple list */ - thd->open_tables=table; - } + + /* + In CREATE TABLE .. If NOT EXISTS .. SELECT we have found that + table exists now we should downgrade our exclusive metadata + lock on this table to SW metadata lock. + */ + if (table_list->lock_strategy == TABLE_LIST::EXCLUSIVE_DOWNGRADABLE_MDL && + !(flags & MYSQL_OPEN_HAS_MDL_LOCK)) + mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_WRITE); + + table->mdl_ticket= mdl_ticket; + + table->next=thd->open_tables; /* Link into simple list */ + thd->open_tables=table; + table->reginfo.lock_type=TL_READ; /* Assume read */ reset: @@ -3003,7 +2934,6 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, MYF(MY_WME)); memcpy((char*) table->alias, alias, length); } - /* These variables are also set in reopen_table() */ table->tablenr=thd->current_tablenr++; table->used_fields=0; table->const_table=0; @@ -3022,17 +2952,38 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, table->pos_in_table_list= table_list; table_list->updatable= 1; // It is not derived table nor non-updatable VIEW table->clear_column_bitmaps(); + table_list->table= table; DBUG_ASSERT(table->key_read == 0); - DBUG_RETURN(table); + /* Tables may be reused in a sub statement. */ + if (table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN)) + table->file->extra(HA_EXTRA_DETACH_CHILDREN); + DBUG_RETURN(FALSE); + +err_unlock: + release_table_share(share); +err_unlock2: + mysql_mutex_unlock(&LOCK_open); + + DBUG_RETURN(TRUE); } -TABLE *find_locked_table(THD *thd, const char *db,const char *table_name) +/** + Find table in the list of open tables. + + @param list List of TABLE objects to be inspected. + @param db Database name + @param table_name Table name + + @return Pointer to the TABLE object found, 0 if no table found. +*/ + +TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name) { char key[MAX_DBKEY_LENGTH]; uint key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1; - for (TABLE *table=thd->open_tables; table ; table=table->next) + for (TABLE *table= list; table ; table=table->next) { if (table->s->table_cache_key.length == key_length && !memcmp(table->s->table_cache_key.str, key, key_length)) @@ -3042,682 +2993,374 @@ TABLE *find_locked_table(THD *thd, const char *db,const char *table_name) } -/* - Reopen an table because the definition has changed. - - SYNOPSIS - reopen_table() - table Table object - - NOTES - The data file for the table is already closed and the share is released - The table has a 'dummy' share that mainly contains database and table name. - - RETURN - 0 ok - 1 error. The old table object is not changed. +/** + Find instance of TABLE with upgradable or exclusive metadata + lock from the list of open tables, emit error if no such table + found. + + @param list List of TABLE objects to be searched + @param db Database name. + @param table_name Name of table. + @param no_error Don't emit error if no suitable TABLE + instance were found. + + @return Pointer to TABLE instance with MDL_SHARED_NO_WRITE, + MDL_SHARED_NO_READ_WRITE, or MDL_EXCLUSIVE metadata + lock, NULL otherwise. */ -bool reopen_table(TABLE *table) +TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db, + const char *table_name, + bool no_error) { - TABLE tmp; - bool error= 1; - Field **field; - uint key,part; - TABLE_LIST table_list; - THD *thd= table->in_use; - DBUG_ENTER("reopen_table"); - DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str, - table->s->table_name.str, (long) table)); - - DBUG_ASSERT(table->s->ref_count == 0); - DBUG_ASSERT(!table->sort.io_cache); - DBUG_ASSERT(!table->children_attached); + TABLE *tab= find_locked_table(list, db, table_name); -#ifdef EXTRA_DEBUG - if (table->db_stat) - sql_print_error("Table %s had a open data handler in reopen_table", - table->alias); -#endif - bzero((char*) &table_list, sizeof(TABLE_LIST)); - table_list.db= table->s->db.str; - table_list.table_name= table->s->table_name.str; - table_list.table= table; - - if (wait_for_locked_table_names(thd, &table_list)) - DBUG_RETURN(1); // Thread was killed - - if (open_unireg_entry(thd, &tmp, &table_list, - table->alias, - table->s->table_cache_key.str, - table->s->table_cache_key.length, - thd->mem_root, 0)) - goto end; - - /* This list copies variables set by open_table */ - tmp.tablenr= table->tablenr; - tmp.used_fields= table->used_fields; - tmp.const_table= table->const_table; - tmp.null_row= table->null_row; - tmp.maybe_null= table->maybe_null; - tmp.status= table->status; - - tmp.s->table_map_id= table->s->table_map_id; - - /* Get state */ - tmp.in_use= thd; - tmp.reginfo.lock_type=table->reginfo.lock_type; - tmp.grant= table->grant; - - /* Replace table in open list */ - tmp.next= table->next; - tmp.prev= table->prev; - - /* Preserve MERGE parent. */ - tmp.parent= table->parent; - /* Fix MERGE child list and check for unchanged union. */ - if ((table->child_l || tmp.child_l) && - fix_merge_after_open(table->child_l, table->child_last_l, - tmp.child_l, tmp.child_last_l)) - { - (void) closefrm(&tmp, 1); // close file, free everything - goto end; - } - - delete table->triggers; - if (table->file) - (void) closefrm(table, 1); // close file, free everything - - *table= tmp; - table->default_column_bitmaps(); - table->file->change_table_ptr(table, table->s); - - DBUG_ASSERT(table->alias != 0); - for (field=table->field ; *field ; field++) + if (!tab) { - (*field)->table= (*field)->orig_table= table; - (*field)->table_name= &table->alias; + if (!no_error) + my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_name); + return NULL; } - for (key=0 ; key < table->s->keys ; key++) + else { - for (part=0 ; part < table->key_info[key].usable_key_parts ; part++) + while (tab->mdl_ticket != NULL && + !tab->mdl_ticket->is_upgradable_or_exclusive() && + (tab= find_locked_table(tab->next, db, table_name))) + continue; + if (!tab) { - table->key_info[key].key_part[part].field->table= table; - table->key_info[key].key_part[part].field->orig_table= table; + if (!no_error) + my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_name); + return 0; } } - if (table->triggers) - table->triggers->set_table(table); - /* - Do not attach MERGE children here. The children might be reopened - after the parent. Attach children after reopening all tables that - require reopen. See for example reopen_tables(). - */ - - broadcast_refresh(); - error=0; - - end: - DBUG_RETURN(error); + return tab; } +/*********************************************************************** + class Locked_tables_list implementation. Declared in sql_class.h +************************************************************************/ + /** - Close all instances of a table open by this thread and replace - them with exclusive name-locks. + Enter LTM_LOCK_TABLES mode. - @param thd Thread context - @param db Database name for the table to be closed - @param table_name Name of the table to be closed + Enter the LOCK TABLES mode using all the tables that are + currently open and locked in this connection. + Initializes a TABLE_LIST instance for every locked table. - @note This function assumes that if we are not under LOCK TABLES, - then there is only one table open and locked. This means that - the function probably has to be adjusted before it can be used - anywhere outside ALTER TABLE. + @param thd thread handle - @note Must not use TABLE_SHARE::table_name/db of the table being closed, - the strings are used in a loop even after the share may be freed. + @return TRUE if out of memory. */ -void close_data_files_and_morph_locks(THD *thd, const char *db, - const char *table_name) +bool +Locked_tables_list::init_locked_tables(THD *thd) { - TABLE *table; - DBUG_ENTER("close_data_files_and_morph_locks"); - - mysql_mutex_assert_owner(&LOCK_open); + DBUG_ASSERT(thd->locked_tables_mode == LTM_NONE); + DBUG_ASSERT(m_locked_tables == NULL); + DBUG_ASSERT(m_reopen_array == NULL); + DBUG_ASSERT(m_locked_tables_count == 0); + + for (TABLE *table= thd->open_tables; table; + table= table->next, m_locked_tables_count++) + { + TABLE_LIST *src_table_list= table->pos_in_table_list; + char *db, *table_name, *alias; + size_t db_len= src_table_list->db_length; + size_t table_name_len= src_table_list->table_name_length; + size_t alias_len= strlen(src_table_list->alias); + TABLE_LIST *dst_table_list; + + if (! multi_alloc_root(&m_locked_tables_root, + &dst_table_list, sizeof(*dst_table_list), + &db, db_len + 1, + &table_name, table_name_len + 1, + &alias, alias_len + 1, + NullS)) + { + unlock_locked_tables(0); + return TRUE; + } - if (thd->lock) - { - /* - If we are not under LOCK TABLES we should have only one table - open and locked so it makes sense to remove the lock at once. + memcpy(db, src_table_list->db, db_len + 1); + memcpy(table_name, src_table_list->table_name, table_name_len + 1); + memcpy(alias, src_table_list->alias, alias_len + 1); + /** + Sic: remember the *actual* table level lock type taken, to + acquire the exact same type in reopen_tables(). + E.g. if the table was locked for write, src_table_list->lock_type is + TL_WRITE_DEFAULT, whereas reginfo.lock_type has been updated from + thd->update_lock_default. */ - mysql_unlock_tables(thd, thd->lock); - thd->lock= 0; - } - - /* - Note that open table list may contain a name-lock placeholder - for target table name if we process ALTER TABLE ... RENAME. - So loop below makes sense even if we are not under LOCK TABLES. - */ - for (table=thd->open_tables; table ; table=table->next) - { - if (!strcmp(table->s->table_name.str, table_name) && - !strcmp(table->s->db.str, db)) + dst_table_list->init_one_table(db, db_len, table_name, table_name_len, + alias, + src_table_list->table->reginfo.lock_type); + dst_table_list->table= table; + dst_table_list->mdl_request.ticket= src_table_list->mdl_request.ticket; + + /* Link last into the list of tables */ + *(dst_table_list->prev_global= m_locked_tables_last)= dst_table_list; + m_locked_tables_last= &dst_table_list->next_global; + table->pos_in_locked_tables= dst_table_list; + } + if (m_locked_tables_count) + { + /** + Allocate an auxiliary array to pass to mysql_lock_tables() + in reopen_tables(). reopen_tables() is a critical + path and we don't want to complicate it with extra allocations. + */ + m_reopen_array= (TABLE**)alloc_root(&m_locked_tables_root, + sizeof(TABLE*) * + (m_locked_tables_count+1)); + if (m_reopen_array == NULL) { - if (thd->locked_tables) - { - if (table->parent) - { - /* - If MERGE child, need to reopen parent too. This means that - the first child to be closed will detach all children from - the parent and close it. OTOH in most cases a MERGE table - won't have multiple children with the same db.table_name. - */ - mysql_lock_remove(thd, thd->locked_tables, table->parent, TRUE); - table->parent->open_placeholder= 1; - close_handle_and_leave_table_as_lock(table->parent); - } - else - mysql_lock_remove(thd, thd->locked_tables, table, TRUE); - } - table->open_placeholder= 1; - close_handle_and_leave_table_as_lock(table); + unlock_locked_tables(0); + return TRUE; } } - DBUG_VOID_RETURN; -} - + thd->enter_locked_tables_mode(LTM_LOCK_TABLES); -/** - Reattach MERGE children after reopen. - - @param[in] thd thread context - @param[in,out] err_tables_p pointer to pointer of tables in error - - @return status - @retval FALSE OK, err_tables_p unchanged - @retval TRUE Error, err_tables_p contains table(s) -*/ - -static bool reattach_merge(THD *thd, TABLE **err_tables_p) -{ - TABLE *table; - TABLE *next; - TABLE **prv_p= &thd->open_tables; - bool error= FALSE; - DBUG_ENTER("reattach_merge"); - - for (table= thd->open_tables; table; table= next) - { - next= table->next; - DBUG_PRINT("tcache", ("check table: '%s'.'%s' 0x%lx next: 0x%lx", - table->s->db.str, table->s->table_name.str, - (long) table, (long) next)); - /* Reattach children for MERGE tables with "closed data files" only. */ - if (table->child_l && !table->children_attached) - { - DBUG_PRINT("tcache", ("MERGE parent, attach children")); - if(table->file->extra(HA_EXTRA_ATTACH_CHILDREN)) - { - my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias); - error= TRUE; - /* Remove table from open_tables. */ - *prv_p= next; - if (next) - prv_p= &next->next; - /* Stack table on error list. */ - table->next= *err_tables_p; - *err_tables_p= table; - continue; - } - else - { - table->children_attached= TRUE; - DBUG_PRINT("myrg", ("attached parent: '%s'.'%s' 0x%lx", - table->s->db.str, - table->s->table_name.str, (long) table)); - } - } - prv_p= &table->next; - } - DBUG_RETURN(error); + return FALSE; } - /** - Reopen all tables with closed data files. - - @param thd Thread context - @param get_locks Should we get locks after reopening tables ? - @param mark_share_as_old Mark share as old to protect from a impending - global read lock. + Leave LTM_LOCK_TABLES mode if it's been entered. - @note Since this function can't properly handle prelocking and - create placeholders it should be used in very special - situations like FLUSH TABLES or ALTER TABLE. In general - case one should just repeat open_tables()/lock_tables() - combination when one needs tables to be reopened (for - example see open_and_lock_tables()). + Close all locked tables, free memory, and leave the mode. - @note One should have lock on LOCK_open when calling this. - - @return FALSE in case of success, TRUE - otherwise. + @note This function is a no-op if we're not in LOCK TABLES. */ -bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old) -{ - TABLE *table,*next,**prev; - TABLE **tables,**tables_ptr; // For locks - TABLE *err_tables= NULL; - bool error=0, not_used; - bool merge_table_found= FALSE; - const uint flags= MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN | - MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK | - MYSQL_LOCK_IGNORE_FLUSH; - - DBUG_ENTER("reopen_tables"); - - if (!thd->open_tables) - DBUG_RETURN(0); +void +Locked_tables_list::unlock_locked_tables(THD *thd) - mysql_mutex_assert_owner(&LOCK_open); - if (get_locks) +{ + if (thd) { + DBUG_ASSERT(!thd->in_sub_stmt && + !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL)); /* - The ptr is checked later - Do not handle locks of MERGE children. + Sic: we must be careful to not close open tables if + we're not in LOCK TABLES mode: unlock_locked_tables() is + sometimes called implicitly, expecting no effect on + open tables, e.g. from begin_trans(). */ - uint opens=0; - for (table= thd->open_tables; table ; table=table->next) - if (!table->parent) - opens++; - DBUG_PRINT("tcache", ("open tables to lock: %u", opens)); - tables= (TABLE**) my_alloca(sizeof(TABLE*)*opens); - } - else - tables= &thd->open_tables; - tables_ptr =tables; + if (thd->locked_tables_mode != LTM_LOCK_TABLES) + return; - prev= &thd->open_tables; - for (table=thd->open_tables; table ; table=next) - { - uint db_stat=table->db_stat; - next=table->next; - DBUG_PRINT("tcache", ("open table: '%s'.'%s' 0x%lx " - "parent: 0x%lx db_stat: %u", - table->s->db.str, table->s->table_name.str, - (long) table, (long) table->parent, db_stat)); - if (table->child_l && !db_stat) - merge_table_found= TRUE; - if (!tables || (!db_stat && reopen_table(table))) + for (TABLE_LIST *table_list= m_locked_tables; + table_list; table_list= table_list->next_global) { - my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias); /* - If we could not allocate 'tables', we may close open tables - here. If a MERGE table is affected, detach the children first. - It is not necessary to clear the child or parent table reference - of this table because the TABLE is freed. But we need to clear - the child or parent references of the other belonging tables so - that they cannot be moved into the unused_tables chain with - these pointers set. + Clear the position in the list, the TABLE object will be + returned to the table cache. */ - if (table->child_l || table->parent) - detach_merge_children(table, TRUE); - my_hash_delete(&open_cache,(uchar*) table); - error=1; - } - else - { - DBUG_PRINT("tcache", ("opened. need lock: %d", - get_locks && !db_stat && !table->parent)); - *prev= table; - prev= &table->next; - /* Do not handle locks of MERGE children. */ - if (get_locks && !db_stat && !table->parent) - *tables_ptr++= table; // need new lock on this - if (mark_share_as_old) - { - table->s->version=0; - table->open_placeholder= 0; - } + table_list->table->pos_in_locked_tables= NULL; } + thd->leave_locked_tables_mode(); + + close_thread_tables(thd); } - *prev=0; /* - When all tables are open again, we can re-attach MERGE children to - their parents. All TABLE objects are still present. + After closing tables we can free memory used for storing lock + request for metadata locks and TABLE_LIST elements. */ - DBUG_PRINT("tcache", ("re-attaching MERGE tables: %d", merge_table_found)); - if (!error && merge_table_found && reattach_merge(thd, &err_tables)) - { - while (err_tables) - { - my_hash_delete(&open_cache, (uchar*) err_tables); - err_tables= err_tables->next; - } - } - DBUG_PRINT("tcache", ("open tables to lock: %u", - (uint) (tables_ptr - tables))); - if (tables != tables_ptr) // Should we get back old locks - { - MYSQL_LOCK *lock; - /* - We should always get these locks. Anyway, we must not go into - wait_for_tables() as it tries to acquire LOCK_open, which is - already locked. - */ - thd->some_tables_deleted=0; - if ((lock= mysql_lock_tables(thd, tables, (uint) (tables_ptr - tables), - flags, ¬_used))) - { - thd->locked_tables=mysql_lock_merge(thd->locked_tables,lock); - } - else - { - /* - This case should only happen if there is a bug in the reopen logic. - Need to issue error message to have a reply for the application. - Not exactly what happened though, but close enough. - */ - my_error(ER_LOCK_DEADLOCK, MYF(0)); - error=1; - } - } - if (get_locks && tables) - { - my_afree((uchar*) tables); - } - broadcast_refresh(); - DBUG_RETURN(error); + free_root(&m_locked_tables_root, MYF(0)); + m_locked_tables= NULL; + m_locked_tables_last= &m_locked_tables; + m_reopen_array= NULL; + m_locked_tables_count= 0; } /** - Close handlers for tables in list, but leave the TABLE structure - intact so that we can re-open these quickly. - - @param thd Thread context - @param table Head of the list of TABLE objects - @param morph_locks TRUE - remove locks which we have on tables being closed - but ensure that no DML or DDL will sneak in before - we will re-open the table (i.e. temporarily morph - our table-level locks into name-locks). - FALSE - otherwise - @param send_refresh Should we awake waiters even if we didn't close any tables? + Unlink a locked table from the locked tables list, either + temporarily or permanently. + + @param thd thread handle + @param table_list the element of locked tables list. + The implementation assumes that this argument + points to a TABLE_LIST element linked into + the locked tables list. Passing a TABLE_LIST + instance that is not part of locked tables + list will lead to a crash. + @param remove_from_locked_tables + TRUE if the table is removed from the list + permanently. + + This function is a no-op if we're not under LOCK TABLES. + + @sa Locked_tables_list::reopen_tables() */ -static void close_old_data_files(THD *thd, TABLE *table, bool morph_locks, - bool send_refresh) -{ - bool found= send_refresh; - DBUG_ENTER("close_old_data_files"); - for (; table ; table=table->next) - { - DBUG_PRINT("tcache", ("checking table: '%s'.'%s' 0x%lx", - table->s->db.str, table->s->table_name.str, - (long) table)); - DBUG_PRINT("tcache", ("needs refresh: %d is open: %u", - table->needs_reopen_or_name_lock(), table->db_stat)); - /* - Reopen marked for flush. - */ - if (table->needs_reopen_or_name_lock()) - { - found=1; - if (table->db_stat) - { - if (morph_locks) - { - /* - Forward lock handling to MERGE parent. But unlock parent - once only. - */ - TABLE *ulcktbl= table->parent ? table->parent : table; - if (ulcktbl->lock_count) - { - /* - Wake up threads waiting for table-level lock on this table - so they won't sneak in when we will temporarily remove our - lock on it. This will also give them a chance to close their - instances of this table. - */ - mysql_lock_abort(thd, ulcktbl, TRUE); - mysql_lock_remove(thd, thd->locked_tables, ulcktbl, TRUE); - ulcktbl->lock_count= 0; - } - if ((ulcktbl != table) && ulcktbl->db_stat) - { - /* - Close the parent too. Note that parent can come later in - the list of tables. It will then be noticed as closed and - as a placeholder. When this happens, do not clear the - placeholder flag. See the branch below ("***"). - */ - ulcktbl->open_placeholder= 1; - close_handle_and_leave_table_as_lock(ulcktbl); - } - /* - We want to protect the table from concurrent DDL operations - (like RENAME TABLE) until we will re-open and re-lock it. - */ - table->open_placeholder= 1; - } - close_handle_and_leave_table_as_lock(table); - } - else if (table->open_placeholder && !morph_locks) - { - /* - We come here only in close-for-back-off scenario. So we have to - "close" create placeholder here to avoid deadlocks (for example, - in case of concurrent execution of CREATE TABLE t1 SELECT * FROM t2 - and RENAME TABLE t2 TO t1). In close-for-re-open scenario we will - probably want to let it stay. - - Note "***": We must not enter this branch if the placeholder - flag has been set because of a former close through a child. - See above the comment that refers to this note. - */ - table->open_placeholder= 0; - } - } - } - if (found) - broadcast_refresh(); - DBUG_VOID_RETURN; -} - - -/* - Wait until all threads has closed the tables in the list - We have also to wait if there is thread that has a lock on this table even - if the table is closed -*/ - -bool table_is_used(TABLE *table, bool wait_for_name_lock) -{ - DBUG_ENTER("table_is_used"); - do - { - char *key= table->s->table_cache_key.str; - uint key_length= table->s->table_cache_key.length; - - DBUG_PRINT("loop", ("table_name: %s", table->alias)); - HASH_SEARCH_STATE state; - for (TABLE *search= (TABLE*) my_hash_first(&open_cache, (uchar*) key, - key_length, &state); - search ; - search= (TABLE*) my_hash_next(&open_cache, (uchar*) key, - key_length, &state)) - { - DBUG_PRINT("info", ("share: 0x%lx " - "open_placeholder: %d locked_by_name: %d " - "db_stat: %u version: %lu", - (ulong) search->s, - search->open_placeholder, search->locked_by_name, - search->db_stat, - search->s->version)); - if (search->in_use == table->in_use) - continue; // Name locked by this thread - /* - We can't use the table under any of the following conditions: - - There is an name lock on it (Table is to be deleted or altered) - - If we are in flush table and we didn't execute the flush - - If the table engine is open and it's an old version - (We must wait until all engines are shut down to use the table) - */ - if ( (search->locked_by_name && wait_for_name_lock) || - (search->is_name_opened() && search->needs_reopen_or_name_lock())) - DBUG_RETURN(1); - } - } while ((table=table->next)); - DBUG_RETURN(0); -} +void Locked_tables_list::unlink_from_list(THD *thd, + TABLE_LIST *table_list, + bool remove_from_locked_tables) +{ + /* + If mode is not LTM_LOCK_TABLES, we needn't do anything. Moreover, + outside this mode pos_in_locked_tables value is not trustworthy. + */ + if (thd->locked_tables_mode != LTM_LOCK_TABLES) + return; + /* + table_list must be set and point to pos_in_locked_tables of some + table. + */ + DBUG_ASSERT(table_list->table->pos_in_locked_tables == table_list); -/* Wait until all used tables are refreshed */ + /* Clear the pointer, the table will be returned to the table cache. */ + table_list->table->pos_in_locked_tables= NULL; -bool wait_for_tables(THD *thd) -{ - bool result; - DBUG_ENTER("wait_for_tables"); + /* Mark the table as closed in the locked tables list. */ + table_list->table= NULL; - thd_proc_info(thd, "Waiting for tables"); - mysql_mutex_lock(&LOCK_open); - while (!thd->killed) - { - thd->some_tables_deleted=0; - close_old_data_files(thd,thd->open_tables,0,dropping_tables != 0); - mysql_ha_flush(thd); - if (!table_is_used(thd->open_tables,1)) - break; - mysql_cond_wait(&COND_refresh, &LOCK_open); - } - if (thd->killed) - result= 1; // aborted - else + /* + If the table is being dropped or renamed, remove it from + the locked tables list (implicitly drop the LOCK TABLES lock + on it). + */ + if (remove_from_locked_tables) { - /* Now we can open all tables without any interference */ - thd_proc_info(thd, "Reopen tables"); - thd->version= refresh_version; - result=reopen_tables(thd,0,0); + *table_list->prev_global= table_list->next_global; + if (table_list->next_global == NULL) + m_locked_tables_last= table_list->prev_global; + else + table_list->next_global->prev_global= table_list->prev_global; } - mysql_mutex_unlock(&LOCK_open); - thd_proc_info(thd, 0); - DBUG_RETURN(result); } +/** + This is an attempt to recover (somewhat) in case of an error. + If we failed to reopen a closed table, let's unlink it from the + list and forget about it. From a user perspective that would look + as if the server "lost" the lock on one of the locked tables. -/* - drop tables from locked list - - SYNOPSIS - drop_locked_tables() - thd Thread thandler - db Database - table_name Table name - - INFORMATION - This is only called on drop tables - - The TABLE object for the dropped table is unlocked but still kept around - as a name lock, which means that the table will be available for other - thread as soon as we call unlock_table_names(). - If there is multiple copies of the table locked, all copies except - the first, which acts as a name lock, is removed. - - RETURN - # If table existed, return table - 0 Table was not locked + @note This function is a no-op if we're not under LOCK TABLES. */ - -TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name) +void Locked_tables_list:: +unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count) { - TABLE *table,*next,**prev, *found= 0; - prev= &thd->open_tables; - DBUG_ENTER("drop_locked_tables"); - + /* If we managed to take a lock, unlock tables and free the lock. */ + if (lock) + mysql_unlock_tables(thd, lock); /* - Note that we need to hold LOCK_open while changing the - open_tables list. Another thread may work on it. - (See: remove_table_from_cache(), mysql_wait_completed_table()) - Closing a MERGE child before the parent would be fatal if the - other thread tries to abort the MERGE lock in between. + If a failure happened in reopen_tables(), we may have succeeded + reopening some tables, but not all. + This works when the connection was killed in mysql_lock_tables(). */ - for (table= thd->open_tables; table ; table=next) + if (reopen_count) { - next=table->next; - if (!strcmp(table->s->table_name.str, table_name) && - !strcmp(table->s->db.str, db)) + mysql_mutex_lock(&LOCK_open); + while (reopen_count--) { - /* If MERGE child, forward lock handling to parent. */ - mysql_lock_remove(thd, thd->locked_tables, - table->parent ? table->parent : table, TRUE); /* - When closing a MERGE parent or child table, detach the children first. - Clear child table references in case this object is opened again. + When closing the table, we must remove it + from thd->open_tables list. + We rely on the fact that open_table() that was used + in reopen_tables() always links the opened table + to the beginning of the open_tables list. */ - if (table->child_l || table->parent) - detach_merge_children(table, TRUE); + DBUG_ASSERT(thd->open_tables == m_reopen_array[reopen_count]); - if (!found) - { - found= table; - /* Close engine table, but keep object around as a name lock */ - if (table->db_stat) - { - table->db_stat= 0; - table->file->close(); - } - } - else - { - /* We already have a name lock, remove copy */ - my_hash_delete(&open_cache,(uchar*) table); - } - } - else - { - *prev=table; - prev= &table->next; + thd->open_tables->pos_in_locked_tables->table= NULL; + + close_thread_table(thd, &thd->open_tables); } - } - *prev=0; - if (found) broadcast_refresh(); - if (thd->locked_tables && thd->locked_tables->table_count == 0) + mysql_mutex_unlock(&LOCK_open); + } + /* Exclude all closed tables from the LOCK TABLES list. */ + for (TABLE_LIST *table_list= m_locked_tables; table_list; table_list= + table_list->next_global) { - my_free((uchar*) thd->locked_tables,MYF(0)); - thd->locked_tables=0; + if (table_list->table == NULL) + { + /* Unlink from list. */ + *table_list->prev_global= table_list->next_global; + if (table_list->next_global == NULL) + m_locked_tables_last= table_list->prev_global; + else + table_list->next_global->prev_global= table_list->prev_global; + } } - DBUG_RETURN(found); } -/* - If we have the table open, which only happens when a LOCK TABLE has been - done on the table, change the lock type to a lock that will abort all - other threads trying to get the lock. +/** + Reopen the tables locked with LOCK TABLES and temporarily closed + by a DDL statement or FLUSH TABLES. + + @note This function is a no-op if we're not under LOCK TABLES. + + @return TRUE if an error reopening the tables. May happen in + case of some fatal system error only, e.g. a disk + corruption, out of memory or a serious bug in the + locking. */ -void abort_locked_tables(THD *thd,const char *db, const char *table_name) +bool +Locked_tables_list::reopen_tables(THD *thd) { - TABLE *table; - for (table= thd->open_tables; table ; table= table->next) + Open_table_context ot_ctx_unused(thd); + bool lt_refresh_unused; + size_t reopen_count= 0; + MYSQL_LOCK *lock; + MYSQL_LOCK *merged_lock; + + for (TABLE_LIST *table_list= m_locked_tables; + table_list; table_list= table_list->next_global) { - if (!strcmp(table->s->table_name.str, table_name) && - !strcmp(table->s->db.str, db)) + if (table_list->table) /* The table was not closed */ + continue; + + /* Links into thd->open_tables upon success */ + if (open_table(thd, table_list, thd->mem_root, &ot_ctx_unused, + MYSQL_OPEN_REOPEN)) { - /* If MERGE child, forward lock handling to parent. */ - mysql_lock_abort(thd, table->parent ? table->parent : table, TRUE); - break; + unlink_all_closed_tables(thd, 0, reopen_count); + return TRUE; } + table_list->table->pos_in_locked_tables= table_list; + /* See also the comment on lock type in init_locked_tables(). */ + table_list->table->reginfo.lock_type= table_list->lock_type; + + DBUG_ASSERT(reopen_count < m_locked_tables_count); + m_reopen_array[reopen_count++]= table_list->table; } + if (reopen_count) + { + thd->in_lock_tables= 1; + /* + We re-lock all tables with mysql_lock_tables() at once rather + than locking one table at a time because of the case + reported in Bug#45035: when the same table is present + in the list many times, thr_lock.c fails to grant READ lock + on a table that is already locked by WRITE lock, even if + WRITE lock is taken by the same thread. If READ and WRITE + lock are passed to thr_lock.c in the same list, everything + works fine. Patching legacy code of thr_lock.c is risking to + break something else. + */ + lock= mysql_lock_tables(thd, m_reopen_array, reopen_count, + MYSQL_OPEN_REOPEN, <_refresh_unused); + thd->in_lock_tables= 0; + if (lock == NULL || (merged_lock= + mysql_lock_merge(thd->lock, lock)) == NULL) + { + unlink_all_closed_tables(thd, lock, reopen_count); + if (! thd->killed) + my_error(ER_LOCK_DEADLOCK, MYF(0)); + return TRUE; + } + thd->lock= merged_lock; + } + return FALSE; } @@ -3823,7 +3466,7 @@ static bool inject_reprepare(THD *thd) @retval FALSE success, version in TABLE_LIST has been updated */ -bool +static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables, TABLE_SHARE *table_share) { @@ -3845,210 +3488,137 @@ check_and_update_table_version(THD *thd, } DBUG_EXECUTE_IF("reprepare_each_statement", return inject_reprepare(thd);); + return FALSE; +} + +/** + Compares versions of a stored routine obtained from the sp cache + and the version used at prepare. + + @details If the new and the old values mismatch, invoke + Metadata_version_observer. + At prepared statement prepare, all Sroutine_hash_entry version values + are NULL and we always have a mismatch. But there is no observer set + in THD, and therefore no error is reported. Instead, we update + the value in Sroutine_hash_entry, effectively recording the original + version. + At prepared statement execute, an observer may be installed. If + there is a version mismatch, we push an error and return TRUE. + + For conventional execution (no prepared statements), the + observer is never installed. + + @param[in] thd used to report errors + @param[in/out] rt pointer to stored routine entry in the + parse tree + @param[in] sp pointer to stored routine cache entry. + Can be NULL if there is no such routine. + @retval TRUE an error, which has been reported + @retval FALSE success, version in Sroutine_hash_entry has been updated +*/ + +static bool +check_and_update_routine_version(THD *thd, Sroutine_hash_entry *rt, + sp_head *sp) +{ + ulong spc_version= sp_cache_version(); + /* sp is NULL if there is no such routine. */ + ulong version= sp ? sp->sp_cache_version() : spc_version; + /* + If the version in the parse tree is stale, + or the version in the cache is stale and sp is not used, + we need to reprepare. + Sic: version != spc_version <--> sp is not NULL. + */ + if (rt->m_sp_cache_version != version || + (version != spc_version && !sp->is_invoked())) + { + if (thd->m_reprepare_observer && + thd->m_reprepare_observer->report_error(thd)) + { + /* + Version of the sp cache is different from the + previous execution of the prepared statement, and it is + unacceptable for this SQLCOM. Error has been reported. + */ + DBUG_ASSERT(thd->is_error()); + return TRUE; + } + /* Always maintain the latest cache version. */ + rt->m_sp_cache_version= version; + } return FALSE; } -/* - Load a table definition from file and open unireg table - SYNOPSIS - open_unireg_entry() - thd Thread handle - entry Store open table definition here - table_list TABLE_LIST with db, table_name & belong_to_view - alias Alias name - cache_key Key for share_cache - cache_key_length length of cache_key - mem_root temporary mem_root for parsing - flags the OPEN_VIEW_NO_PARSE flag to be passed to - openfrm()/open_new_frm() +/** + Open view by getting its definition from disk (and table cache in future). - NOTES - Extra argument for open is taken from thd->open_options - One must have a lock on LOCK_open when calling this function + @param thd Thread handle + @param table_list TABLE_LIST with db, table_name & belong_to_view + @param alias Alias name + @param cache_key Key for table definition cache + @param cache_key_length Length of cache_key + @param mem_root Memory to be used for .frm parsing. + @param flags Flags which modify how we open the view - RETURN - 0 ok - # Error + @todo This function is needed for special handling of views under + LOCK TABLES. We probably should get rid of it in long term. + + @return FALSE if success, TRUE - otherwise. */ -static int open_unireg_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list, - const char *alias, - char *cache_key, uint cache_key_length, - MEM_ROOT *mem_root, uint flags) +bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, + char *cache_key, uint cache_key_length, + MEM_ROOT *mem_root, uint flags) { + TABLE not_used; int error; + my_hash_value_type hash_value; TABLE_SHARE *share; - uint discover_retry_count= 0; - DBUG_ENTER("open_unireg_entry"); - mysql_mutex_assert_owner(&LOCK_open); -retry: + hash_value= my_calc_hash(&table_def_cache, (uchar*) cache_key, + cache_key_length); + mysql_mutex_lock(&LOCK_open); + if (!(share= get_table_share_with_create(thd, table_list, cache_key, cache_key_length, - OPEN_VIEW | - table_list->i_s_requested_object, - &error))) - DBUG_RETURN(1); - - if (share->is_view) - { - /* - If parent_l of the table_list is non null then a merge table - has this view as child table, which is not supported. - */ - if (table_list->parent_l) - { - my_error(ER_WRONG_MRG_TABLE, MYF(0)); - goto err; - } - - /* - This table is a view. Validate its metadata version: in particular, - that it was a view when the statement was prepared. - */ - if (check_and_update_table_version(thd, table_list, share)) - goto err; - if (table_list->i_s_requested_object & OPEN_TABLE_ONLY) - goto err; + OPEN_VIEW, &error, + hash_value))) + goto err; - /* Open view */ - error= (int) open_new_frm(thd, share, alias, - (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | - HA_GET_INDEX | HA_TRY_READ_ONLY), - READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD | - (flags & OPEN_VIEW_NO_PARSE), - thd->open_options, entry, table_list, - mem_root); - if (error) - goto err; - /* TODO: Don't free this */ - release_table_share(share, RELEASE_NORMAL); - DBUG_RETURN((flags & OPEN_VIEW_NO_PARSE)? -1 : 0); - } - else if (table_list->view) + if (share->is_view && + !open_new_frm(thd, share, alias, + (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | + HA_GET_INDEX | HA_TRY_READ_ONLY), + READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD | + flags, thd->open_options, ¬_used, table_list, + mem_root)) { - /* - We're trying to open a table for what was a view. - This can only happen during (re-)execution. - At prepared statement prepare the view has been opened and - merged into the statement parse tree. After that, someone - performed a DDL and replaced the view with a base table. - Don't try to open the table inside a prepared statement, - invalidate it instead. - - Note, the assert below is known to fail inside stored - procedures (Bug#27011). - */ - DBUG_ASSERT(thd->m_reprepare_observer); - check_and_update_table_version(thd, table_list, share); - /* Always an error. */ - DBUG_ASSERT(thd->is_error()); - goto err; + release_table_share(share); + mysql_mutex_unlock(&LOCK_open); + return FALSE; } - if (table_list->i_s_requested_object & OPEN_VIEW_ONLY) - goto err; + my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str, "VIEW"); + release_table_share(share); +err: + mysql_mutex_unlock(&LOCK_open); + return TRUE; +} - while ((error= open_table_from_share(thd, share, alias, - (uint) (HA_OPEN_KEYFILE | - HA_OPEN_RNDFILE | - HA_GET_INDEX | - HA_TRY_READ_ONLY), - (READ_KEYINFO | COMPUTE_TYPES | - EXTRA_RECORD), - thd->open_options, entry, FALSE))) - { - if (error == 7) // Table def changed - { - share->version= 0; // Mark share as old - if (discover_retry_count++) // Retry once - goto err; - /* - TODO: - Here we should wait until all threads has released the table. - For now we do one retry. This may cause a deadlock if there - is other threads waiting for other tables used by this thread. - - Proper fix would be to if the second retry failed: - - Mark that table def changed - - Return from open table - - Close all tables used by this thread - - Start waiting that the share is released - - Retry by opening all tables again - */ - if (ha_create_table_from_engine(thd, table_list->db, - table_list->table_name)) - goto err; - /* - TO BE FIXED - To avoid deadlock, only wait for release if no one else is - using the share. - */ - if (share->ref_count != 1) - goto err; - /* Free share and wait until it's released by all threads */ - release_table_share(share, RELEASE_WAIT_FOR_DROP); - if (!thd->killed) - { - thd->warning_info->clear_warning_info(thd->query_id); - thd->clear_error(); // Clear error message - goto retry; - } - DBUG_RETURN(1); - } - if (!entry->s || !entry->s->crashed) - goto err; - // Code below is for repairing a crashed file - if ((error= lock_table_name(thd, table_list, TRUE))) - { - if (error < 0) - goto err; - if (wait_for_locked_table_names(thd, table_list)) - { - unlock_table_name(thd, table_list); - goto err; - } - } - mysql_mutex_unlock(&LOCK_open); - thd->clear_error(); // Clear error message - error= 0; - if (open_table_from_share(thd, share, alias, - (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | - HA_GET_INDEX | - HA_TRY_READ_ONLY), - READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, - ha_open_options | HA_OPEN_FOR_REPAIR, - entry, FALSE) || ! entry->file || - (entry->file->is_crashed() && entry->file->ha_check_and_repair(thd))) - { - /* Give right error message */ - thd->clear_error(); - my_error(ER_NOT_KEYFILE, MYF(0), share->table_name.str, my_errno); - sql_print_error("Couldn't repair table: %s.%s", share->db.str, - share->table_name.str); - if (entry->file) - closefrm(entry, 0); - error=1; - } - else - thd->clear_error(); // Clear error message - mysql_mutex_lock(&LOCK_open); - unlock_table_name(thd, table_list); - - if (error) - goto err; - break; - } +/** + Finalize the process of TABLE creation by loading table triggers + and taking action if a HEAP table content was emptied implicitly. +*/ +static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry) +{ if (Table_triggers_list::check_n_load(thd, share->db.str, share->table_name.str, entry, 0)) - { - closefrm(entry, 0); - goto err; - } + return TRUE; /* If we are here, there was no fatal error (but error may be still @@ -4067,13 +3637,9 @@ retry: end = strxmov(strmov(query, "DELETE FROM `"), share->db.str,"`.`",share->table_name.str,"`", NullS); int errcode= query_error_code(thd, TRUE); - if (thd->binlog_query(THD::STMT_QUERY_TYPE, - query, (ulong)(end-query), - FALSE, FALSE, errcode)) - { - my_free(query, MYF(0)); - goto err; - } + thd->binlog_query(THD::STMT_QUERY_TYPE, + query, (ulong)(end-query), + FALSE, FALSE, errcode); my_free(query, MYF(0)); } else @@ -4085,352 +3651,267 @@ retry: */ sql_print_error("When opening HEAP table, could not allocate memory " "to write 'DELETE FROM `%s`.`%s`' to the binary log", - table_list->db, table_list->table_name); + share->db.str, share->table_name.str); delete entry->triggers; - closefrm(entry, 0); - goto err; + return TRUE; } } } - DBUG_RETURN(0); - -err: - release_table_share(share, RELEASE_NORMAL); - DBUG_RETURN(1); + return FALSE; } /** - @brief Add list of MERGE children to a TABLE_LIST list. + Auxiliary routine which is used for performing automatical table repair. +*/ - @param[in] tlist the parent TABLE_LIST object just opened +static bool auto_repair_table(THD *thd, TABLE_LIST *table_list) +{ + char cache_key[MAX_DBKEY_LENGTH]; + uint cache_key_length; + TABLE_SHARE *share; + TABLE *entry; + int not_used; + bool result= FALSE; + my_hash_value_type hash_value; - @return status - @retval 0 OK - @retval != 0 Error + cache_key_length= create_table_def_key(thd, cache_key, table_list, 0); - @detail - When a MERGE parent table has just been opened, insert the - TABLE_LIST chain from the MERGE handle into the table list used for - opening tables for this statement. This lets the children be opened - too. -*/ + thd->clear_error(); -static int add_merge_table_list(TABLE_LIST *tlist) -{ - TABLE *parent= tlist->table; - TABLE_LIST *child_l; - DBUG_ENTER("add_merge_table_list"); - DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", parent->s->db.str, - parent->s->table_name.str, (long) parent)); + hash_value= my_calc_hash(&table_def_cache, (uchar*) cache_key, + cache_key_length); + mysql_mutex_lock(&LOCK_open); - /* Must not call this with attached children. */ - DBUG_ASSERT(!parent->children_attached); - /* Must not call this with children list in place. */ - DBUG_ASSERT(tlist->next_global != parent->child_l); - /* Prevent inclusion of another MERGE table. Could make infinite recursion. */ - if (tlist->parent_l) + if (!(share= get_table_share_with_create(thd, table_list, cache_key, + cache_key_length, + OPEN_VIEW, ¬_used, + hash_value))) { - my_error(ER_ADMIN_WRONG_MRG_TABLE, MYF(0), tlist->alias); - DBUG_RETURN(1); + mysql_mutex_unlock(&LOCK_open); + return TRUE; } - /* Fix children.*/ - for (child_l= parent->child_l; ; child_l= child_l->next_global) - { - /* - Note: child_l->table may still be set if this parent was taken - from the unused_tables chain. Ignore this fact here. The - reference will be replaced by the handler in - ::extra(HA_EXTRA_ATTACH_CHILDREN). - */ - - /* Set lock type. */ - child_l->lock_type= tlist->lock_type; - - /* Set parent reference. */ - child_l->parent_l= tlist; + if (share->is_view) + goto end_with_lock_open; - /* Break when this was the last child. */ - if (&child_l->next_global == parent->child_last_l) - break; + if (!(entry= (TABLE*)my_malloc(sizeof(TABLE), MYF(MY_WME)))) + { + result= TRUE; + goto end_with_lock_open; } + share->version= 0; + mysql_mutex_unlock(&LOCK_open); - /* Insert children into the table list. */ - *parent->child_last_l= tlist->next_global; - tlist->next_global= parent->child_l; + if (open_table_from_share(thd, share, table_list->alias, + (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | + HA_GET_INDEX | + HA_TRY_READ_ONLY), + READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, + ha_open_options | HA_OPEN_FOR_REPAIR, + entry, FALSE) || ! entry->file || + (entry->file->is_crashed() && entry->file->ha_check_and_repair(thd))) + { + /* Give right error message */ + thd->clear_error(); + my_error(ER_NOT_KEYFILE, MYF(0), share->table_name.str, my_errno); + sql_print_error("Couldn't repair table: %s.%s", share->db.str, + share->table_name.str); + if (entry->file) + closefrm(entry, 0); + result= TRUE; + } + else + { + thd->clear_error(); // Clear error message + closefrm(entry, 0); + } + my_free(entry, MYF(0)); - /* - Do not fix the prev_global pointers. We will remove the - chain soon anyway. - */ + mysql_mutex_lock(&LOCK_open); - DBUG_RETURN(0); +end_with_lock_open: + release_table_share(share); + mysql_mutex_unlock(&LOCK_open); + return result; } -/** - @brief Attach MERGE children to the parent. +/** Open_table_context */ - @param[in] tlist the child TABLE_LIST object just opened +Open_table_context::Open_table_context(THD *thd) + :m_action(OT_NO_ACTION), + m_start_of_statement_svp(thd->mdl_context.mdl_savepoint()), + m_has_locks((thd->in_multi_stmt_transaction() && + thd->mdl_context.has_locks()) || + thd->mdl_context.trans_sentinel()), + m_global_mdl_request(NULL) +{} - @return status - @retval 0 OK - @retval != 0 Error - @note - This is called when the last MERGE child has just been opened, let - the handler attach the MyISAM tables to the MERGE table. Remove - MERGE TABLE_LIST chain from the statement list so that it cannot be - changed or freed. +/** + Get MDL_request object for global intention exclusive lock which + is acquired during opening tables for statements which take + upgradable shared metadata locks. */ -static int attach_merge_children(TABLE_LIST *tlist) +MDL_request *Open_table_context::get_global_mdl_request(THD *thd) { - TABLE *parent= tlist->parent_l->table; - int error; - DBUG_ENTER("attach_merge_children"); - DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", parent->s->db.str, - parent->s->table_name.str, (long) parent)); + if (! m_global_mdl_request) + { + char *buff; + if ((buff= (char*)thd->alloc(sizeof(MDL_request)))) + { + m_global_mdl_request= new (buff) MDL_request(); + m_global_mdl_request->init(MDL_key::GLOBAL, "", "", + MDL_INTENTION_EXCLUSIVE); + } + } + return m_global_mdl_request; +} - /* Must not call this with attached children. */ - DBUG_ASSERT(!parent->children_attached); - /* Must call this with children list in place. */ - DBUG_ASSERT(tlist->parent_l->next_global == parent->child_l); - /* Attach MyISAM tables to MERGE table. */ - error= parent->file->extra(HA_EXTRA_ATTACH_CHILDREN); +/** + Check if we can back-off and set back off action if we can. + Otherwise report and return error. - /* - Remove children from the table list. Even in case of an error. - This should prevent tampering with them. - */ - tlist->parent_l->next_global= *parent->child_last_l; + @retval TRUE if back-off is impossible. + @retval FALSE if we can back off. Back off action has been set. +*/ +bool +Open_table_context:: +request_backoff_action(enum_open_table_action action_arg) +{ /* - Do not fix the last childs next_global pointer. It is needed for - stepping to the next table in the enclosing loop in open_tables(). - Do not fix prev_global pointers. We did not set them. + We are inside a transaction that already holds locks and have + met a broken table or a table which needs re-discovery. + Performing any recovery action requires acquiring an exclusive + metadata lock on this table. Doing that with locks breaks the + metadata locking protocol and might lead to deadlocks, + so we report an error. + + However, if we have only met a conflicting lock or an old + TABLE version, and just need to wait for the conflict to + disappear/old version to go away, allow waiting. + While waiting, we use a simple empiric to detect + deadlocks: we never wait on someone who's waiting too. + Waiting will be done after releasing metadata locks acquired + by this statement. */ - - if (error) + if (m_has_locks && action_arg != OT_WAIT_MDL_LOCK) { - DBUG_PRINT("error", ("attaching MERGE children failed: %d", my_errno)); - parent->file->print_error(error, MYF(0)); - DBUG_RETURN(1); + my_error(ER_LOCK_DEADLOCK, MYF(0)); + return TRUE; } - - parent->children_attached= TRUE; - DBUG_PRINT("myrg", ("attached parent: '%s'.'%s' 0x%lx", parent->s->db.str, - parent->s->table_name.str, (long) parent)); - - /* - Note that we have the cildren in the thd->open_tables list at this - point. - */ - - DBUG_RETURN(0); + m_action= action_arg; + return FALSE; } /** - @brief Detach MERGE children from the parent. - - @note - Call this before the first table of a MERGE table (parent or child) - is closed. + Recover from failed attempt of open table by performing requested action. - When closing thread tables at end of statement, both parent and - children are in thd->open_tables and will be closed. In most cases - the children will be closed before the parent. They are opened after - the parent and thus stacked into thd->open_tables before it. + @param thd Thread context + @param mdl_request MDL_request of the object that caused the problem. + @param table Optional (can be NULL). Used only if action is OT_REPAIR. + In that case a TABLE_LIST for the table to be repaired. + @todo: It's unnecessary and should be removed. - To avoid that we touch a closed children in any way, we must detach - the children from the parent when the first belonging table is - closed (parent or child). + @pre This function should be called only with "action" != OT_NO_ACTION + and after having called @sa close_tables_for_reopen(). - All references to the children should be removed on handler level - and optionally on table level. - - @note - Assure that you call it for a MERGE parent or child only. - Either table->child_l or table->parent must be set. - - @param[in] table the TABLE object of the parent - @param[in] clear_refs if to clear TABLE references - this must be true when called from - close_thread_tables() to enable fresh - open in open_tables() - it must be false when called in preparation - for reopen_tables() + @retval FALSE - Success. One should try to open tables once again. + @retval TRUE - Error */ -void detach_merge_children(TABLE *table, bool clear_refs) +bool +Open_table_context:: +recover_from_failed_open(THD *thd, MDL_request *mdl_request, + TABLE_LIST *table) { - TABLE_LIST *child_l; - TABLE *parent= table->child_l ? table : table->parent; - bool first_detach; - DBUG_ENTER("detach_merge_children"); - /* - Either table->child_l or table->parent must be set. Parent must have - child_l set. - */ - DBUG_ASSERT(parent && parent->child_l); - DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx clear_refs: %d", - table->s->db.str, table->s->table_name.str, - (long) table, clear_refs)); - DBUG_PRINT("myrg", ("parent: '%s'.'%s' 0x%lx", parent->s->db.str, - parent->s->table_name.str, (long) parent)); - + bool result= FALSE; /* - In a open_tables() loop it can happen that not all tables have their - children attached yet. Also this is called for every child and the - parent from close_thread_tables(). + Remove reference to released ticket from MDL_request. */ - if ((first_detach= parent->children_attached)) + if (m_global_mdl_request) + m_global_mdl_request->ticket= NULL; + /* Execute the action. */ + switch (m_action) { - (void) parent->file->extra(HA_EXTRA_DETACH_CHILDREN); - parent->children_attached= FALSE; - DBUG_PRINT("myrg", ("detached parent: '%s'.'%s' 0x%lx", parent->s->db.str, - parent->s->table_name.str, (long) parent)); - } - else - DBUG_PRINT("myrg", ("parent is already detached")); - - if (clear_refs) - { - /* In any case clear the own parent reference. (***) */ - table->parent= NULL; - - /* - On the first detach, clear all references. If this table is the - parent, we still may need to clear the child references. The first - detach might not have done this. - */ - if (first_detach || (table == parent)) - { - /* Clear TABLE references to force new assignment at next open. */ - for (child_l= parent->child_l; ; child_l= child_l->next_global) + case OT_WAIT_MDL_LOCK: + result= thd->mdl_context.wait_for_lock(mdl_request); + break; + case OT_WAIT_TDC: + result= tdc_wait_for_old_versions(thd, &m_mdl_requests); + DBUG_ASSERT(thd->mysys_var->current_mutex == NULL); + break; + case OT_DISCOVER: { - /* - Do not DBUG_ASSERT(child_l->table); open_tables might be - incomplete. + MDL_request mdl_global_request; + MDL_request mdl_xlock_request(mdl_request); + MDL_request_list mdl_requests; - Clear the parent reference of the children only on the first - detach. The children might already be closed. They will clear - it themseves when this function is called for them with - 'clear_refs' true. See above "(***)". - */ - if (first_detach && child_l->table) - child_l->table->parent= NULL; + mdl_global_request.init(MDL_key::GLOBAL, "", "", + MDL_INTENTION_EXCLUSIVE); + mdl_xlock_request.set_type(MDL_EXCLUSIVE); - /* Clear the table reference to force new assignment at next open. */ - child_l->table= NULL; + mdl_requests.push_front(&mdl_xlock_request); + mdl_requests.push_front(&mdl_global_request); - /* Break when this was the last child. */ - if (&child_l->next_global == parent->child_last_l) + if ((result= thd->mdl_context.acquire_locks(&mdl_requests))) break; - } - } - } - DBUG_VOID_RETURN; -} - - -/** - @brief Fix MERGE children after open. - - @param[in] old_child_list first list member from original table - @param[in] old_last pointer to &next_global of last list member - @param[in] new_child_list first list member from freshly opened table - @param[in] new_last pointer to &next_global of last list member - - @return mismatch - @retval FALSE OK, no mismatch - @retval TRUE Error, lists mismatch - - @detail - Main action is to copy TABLE reference for each member of original - child list to new child list. After a fresh open these references - are NULL. Assign the old children to the new table. Some of them - might also be reopened or will be reopened soon. - - Other action is to verify that the table definition with respect to - the UNION list did not change. - - @note - This function terminates the child list if the respective '*_last' - pointer is non-NULL. Do not call it from a place where the list is - embedded in another list and this would break it. + DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE); + mysql_mutex_lock(&LOCK_open); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, + mdl_request->key.db_name(), + mdl_request->key.name()); + ha_create_table_from_engine(thd, + mdl_request->key.db_name(), + mdl_request->key.name()); + mysql_mutex_unlock(&LOCK_open); - Terminating the list is required for example in the first - reopen_table() after open_tables(). open_tables() requires the end - of the list not to be terminated because other tables could follow - behind the child list. + thd->warning_info->clear_warning_info(thd->query_id); + thd->clear_error(); // Clear error message + thd->mdl_context.release_transactional_locks(); + break; + } + case OT_REPAIR: + { + MDL_request mdl_global_request; + MDL_request mdl_xlock_request(mdl_request); + MDL_request_list mdl_requests; - If a '*_last' pointer is NULL, the respective list is assumed to be - NULL terminated. -*/ + mdl_global_request.init(MDL_key::GLOBAL, "", "", + MDL_INTENTION_EXCLUSIVE); + mdl_xlock_request.set_type(MDL_EXCLUSIVE); -bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last, - TABLE_LIST *new_child_list, TABLE_LIST **new_last) -{ - bool mismatch= FALSE; - DBUG_ENTER("fix_merge_after_open"); - DBUG_PRINT("myrg", ("old last addr: 0x%lx new last addr: 0x%lx", - (long) old_last, (long) new_last)); + mdl_requests.push_front(&mdl_xlock_request); + mdl_requests.push_front(&mdl_global_request); - /* Terminate the lists for easier check of list end. */ - if (old_last) - *old_last= NULL; - if (new_last) - *new_last= NULL; + if ((result= thd->mdl_context.acquire_locks(&mdl_requests))) + break; - for (;;) - { - DBUG_PRINT("myrg", ("old list item: 0x%lx new list item: 0x%lx", - (long) old_child_list, (long) new_child_list)); - /* Break if one of the list is at its end. */ - if (!old_child_list || !new_child_list) - break; - /* Old table has references to child TABLEs. */ - DBUG_ASSERT(old_child_list->table); - /* New table does not yet have references to child TABLEs. */ - DBUG_ASSERT(!new_child_list->table); - DBUG_PRINT("myrg", ("old table: '%s'.'%s' new table: '%s'.'%s'", - old_child_list->db, old_child_list->table_name, - new_child_list->db, new_child_list->table_name)); - /* Child db.table names must match. */ - if (strcmp(old_child_list->table_name, new_child_list->table_name) || - strcmp(old_child_list->db, new_child_list->db)) - break; - /* - Copy TABLE reference. Child TABLE objects are still in place - though not necessarily open yet. - */ - DBUG_PRINT("myrg", ("old table ref: 0x%lx replaces new table ref: 0x%lx", - (long) old_child_list->table, - (long) new_child_list->table)); - new_child_list->table= old_child_list->table; - /* Step both lists. */ - old_child_list= old_child_list->next_global; - new_child_list= new_child_list->next_global; - } - DBUG_PRINT("myrg", ("end of list, mismatch: %d", mismatch)); - /* - If the list pointers are not both NULL after the loop, then the - lists differ. If the are both identical, but not NULL, then they - have at least one table in common and hence the rest of the list - would be identical too. But in this case the loop woul run until the - list end, where both pointers would become NULL. - */ - if (old_child_list != new_child_list) - mismatch= TRUE; - if (mismatch) - my_error(ER_TABLE_DEF_CHANGED, MYF(0)); + DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE); + mysql_mutex_lock(&LOCK_open); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, + mdl_request->key.db_name(), + mdl_request->key.name()); + mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(mismatch); + result= auto_repair_table(thd, table); + thd->mdl_context.release_transactional_locks(); + break; + } + default: + DBUG_ASSERT(0); + } + /* Remove all old requests, they will be re-added. */ + m_mdl_requests.empty(); + /* Prepare for possible another back-off. */ + m_action= OT_NO_ACTION; + return result; } @@ -4468,49 +3949,512 @@ thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table) /* + Handle element of prelocking set other than table. E.g. cache routine + and, if prelocking strategy prescribes so, extend the prelocking set + with tables and routines used by it. + + @param[in] thd Thread context. + @param[in] prelocking_ctx Prelocking context. + @param[in] rt Element of prelocking set to be processed. + @param[in] prelocking_strategy Strategy which specifies how the + prelocking set should be extended when + one of its elements is processed. + @param[in] has_prelocking_list Indicates that prelocking set/list for + this statement has already been built. + @param[in] ot_ctx Context of open_table used to recover from + locking failures. + @param[out] need_prelocking Set to TRUE if it was detected that this + statement will require prelocked mode for + its execution, not touched otherwise. + + @retval FALSE Success. + @retval TRUE Failure (Conflicting metadata lock, OOM, other errors). +*/ + +static bool +open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, + Sroutine_hash_entry *rt, + Prelocking_strategy *prelocking_strategy, + bool has_prelocking_list, + Open_table_context *ot_ctx, + bool *need_prelocking) +{ + MDL_key::enum_mdl_namespace mdl_type= rt->mdl_request.key.mdl_namespace(); + DBUG_ENTER("open_and_process_routine"); + + switch (mdl_type) + { + case MDL_key::FUNCTION: + case MDL_key::PROCEDURE: + { + sp_head *sp; + /* + Try to get MDL lock on the routine. + Note that we do not take locks on top-level CALLs as this can + lead to a deadlock. Not locking top-level CALLs does not break + the binlog as only the statements in the called procedure show + up there, not the CALL itself. + */ + if (rt != (Sroutine_hash_entry*)prelocking_ctx->sroutines_list.first || + mdl_type != MDL_key::PROCEDURE) + { + ot_ctx->add_request(&rt->mdl_request); + + /* + Since we acquire only shared lock on routines we don't + need to care about global intention exclusive locks. + */ + DBUG_ASSERT(rt->mdl_request.type == MDL_SHARED); + + if (thd->mdl_context.try_acquire_lock(&rt->mdl_request)) + DBUG_RETURN(TRUE); + + if (rt->mdl_request.ticket == NULL) + { + /* A lock conflict. Someone's trying to modify SP metadata. */ + ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_MDL_LOCK); + DBUG_RETURN(TRUE); + } + DEBUG_SYNC(thd, "after_shared_lock_pname"); + + /* Ensures the routine is up-to-date and cached, if exists. */ + if (sp_cache_routine(thd, rt, has_prelocking_list, &sp)) + DBUG_RETURN(TRUE); + + /* Remember the version of the routine in the parse tree. */ + if (check_and_update_routine_version(thd, rt, sp)) + DBUG_RETURN(TRUE); + + /* 'sp' is NULL when there is no such routine. */ + if (sp && !has_prelocking_list) + { + prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp, + need_prelocking); + } + } + else + { + /* + If it's a top level call, just make sure we have a recent + version of the routine, if it exists. + Validating routine version is unnecessary, since CALL + does not affect the prepared statement prelocked list. + */ + if (sp_cache_routine(thd, rt, FALSE, &sp)) + DBUG_RETURN(TRUE); + } + } + break; + case MDL_key::TRIGGER: + /** + We add trigger entries to lex->sroutines_list, but we don't + load them here. The trigger entry is only used when building + a transitive closure of objects used in a statement, to avoid + adding to this closure objects that are used in the trigger more + than once. + E.g. if a trigger trg refers to table t2, and the trigger table t1 + is used multiple times in the statement (say, because it's used in + function f1() twice), we will only add t2 once to the list of + tables to prelock. + + We don't take metadata locks on triggers either: they are protected + by a respective lock on the table, on which the trigger is defined. + + The only two cases which give "trouble" are SHOW CREATE TRIGGER + and DROP TRIGGER statements. For these, statement syntax doesn't + specify the table on which this trigger is defined, so we have + to make a "dirty" read in the data dictionary to find out the + table name. Once we discover the table name, we take a metadata + lock on it, and this protects all trigger operations. + Of course the table, in theory, may disappear between the dirty + read and metadata lock acquisition, but in that case we just return + a run-time error. + + Grammar of other trigger DDL statements (CREATE, DROP) requires + the table to be specified explicitly, so we use the table metadata + lock to protect trigger metadata in these statements. Similarly, in + DML we always use triggers together with their tables, and thus don't + need to take separate metadata locks on them. + */ + break; + default: + /* Impossible type value. */ + DBUG_ASSERT(0); + } + DBUG_RETURN(FALSE); +} + + +/** + Handle table list element by obtaining metadata lock, opening table or view + and, if prelocking strategy prescribes so, extending the prelocking set with + tables and routines used by it. + + @param[in] thd Thread context. + @param[in] lex LEX structure for statement. + @param[in] tables Table list element to be processed. + @param[in,out] counter Number of tables which are open. + @param[in] flags Bitmap of flags to modify how the tables + will be open, see open_table() description + for details. + @param[in] prelocking_strategy Strategy which specifies how the + prelocking set should be extended + when table or view is processed. + @param[in] has_prelocking_list Indicates that prelocking set/list for + this statement has already been built. + @param[in] ot_ctx Context used to recover from a failed + open_table() attempt. + @param[in] new_frm_mem Temporary MEM_ROOT to be used for + parsing .FRMs for views. + + @retval FALSE Success. + @retval TRUE Error, reported unless there is a chance to recover from it. +*/ + +static bool +open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, + uint *counter, uint flags, + Prelocking_strategy *prelocking_strategy, + bool has_prelocking_list, + Open_table_context *ot_ctx, + MEM_ROOT *new_frm_mem) +{ + bool error= FALSE; + bool safe_to_ignore_table= FALSE; + DBUG_ENTER("open_and_process_table"); + + /* + Ignore placeholders for derived tables. After derived tables + processing, link to created temporary table will be put here. + If this is derived table for view then we still want to process + routines used by this view. + */ + if (tables->derived) + { + if (!tables->view) + goto end; + /* + We restore view's name and database wiped out by derived tables + processing and fall back to standard open process in order to + obtain proper metadata locks and do other necessary steps like + stored routine processing. + */ + tables->db= tables->view_db.str; + tables->db_length= tables->view_db.length; + tables->table_name= tables->view_name.str; + tables->table_name_length= tables->view_name.length; + } + /* + If this TABLE_LIST object is a placeholder for an information_schema + table, create a temporary table to represent the information_schema + table in the query. Do not fill it yet - will be filled during + execution. + */ + if (tables->schema_table) + { + /* + If this information_schema table is merged into a mergeable + view, ignore it for now -- it will be filled when its respective + TABLE_LIST is processed. This code works only during re-execution. + */ + if (tables->view) + goto process_view_routines; + if (!mysql_schema_table(thd, lex, tables) && + !check_and_update_table_version(thd, tables, tables->table->s)) + { + goto end; + } + error= TRUE; + goto end; + } + DBUG_PRINT("tcache", ("opening table: '%s'.'%s' item: %p", + tables->db, tables->table_name, tables)); //psergey: invalid read of size 1 here + (*counter)++; + + /* Not a placeholder: must be a base table or a view. Let us open it. */ + DBUG_ASSERT(!tables->table); + + if (tables->prelocking_placeholder) + { + /* + For the tables added by the pre-locking code, attempt to open + the table but fail silently if the table does not exist. + The real failure will occur when/if a statement attempts to use + that table. + */ + Prelock_error_handler prelock_handler; + thd->push_internal_handler(& prelock_handler); + error= open_table(thd, tables, new_frm_mem, ot_ctx, flags); + thd->pop_internal_handler(); + safe_to_ignore_table= prelock_handler.safely_trapped_errors(); + } + else + error= open_table(thd, tables, new_frm_mem, ot_ctx, flags); + + free_root(new_frm_mem, MYF(MY_KEEP_PREALLOC)); + + if (error) + { + if (! ot_ctx->can_recover_from_failed_open() && safe_to_ignore_table) + { + DBUG_PRINT("info", ("open_table: ignoring table '%s'.'%s'", + tables->db, tables->alias)); + error= FALSE; + } + goto end; + } + + /* + We can't rely on simple check for TABLE_LIST::view to determine + that this is a view since during re-execution we might reopen + ordinary table in place of view and thus have TABLE_LIST::view + set from repvious execution and TABLE_LIST::table set from + current. + */ + if (!tables->table && tables->view) + { + /* VIEW placeholder */ + (*counter)--; + + /* + tables->next_global list consists of two parts: + 1) Query tables and underlying tables of views. + 2) Tables used by all stored routines that this statement invokes on + execution. + We need to know where the bound between these two parts is. If we've + just opened a view, which was the last table in part #1, and it + has added its base tables after itself, adjust the boundary pointer + accordingly. + */ + if (lex->query_tables_own_last == &(tables->next_global) && + tables->view->query_tables) + lex->query_tables_own_last= tables->view->query_tables_last; + /* + Let us free memory used by 'sroutines' hash here since we never + call destructor for this LEX. + */ + my_hash_free(&tables->view->sroutines); + goto process_view_routines; + } + + /* + Special types of open can succeed but still don't set + TABLE_LIST::table to anything. + */ + if (tables->open_strategy && !tables->table) + goto end; + + /* + If we are not already in prelocked mode and extended table list is not + yet built we might have to build the prelocking set for this statement. + + Since currently no prelocking strategy prescribes doing anything for + tables which are only read, we do below checks only if table is going + to be changed. + */ + if (thd->locked_tables_mode <= LTM_LOCK_TABLES && + ! has_prelocking_list && + tables->lock_type >= TL_WRITE_ALLOW_WRITE) + { + bool need_prelocking= FALSE; + TABLE_LIST **save_query_tables_last= lex->query_tables_last; + /* + Extend statement's table list and the prelocking set with + tables and routines according to the current prelocking + strategy. + + For example, for DML statements we need to add tables and routines + used by triggers which are going to be invoked for this element of + table list and also add tables required for handling of foreign keys. + */ + error= prelocking_strategy->handle_table(thd, lex, tables, + &need_prelocking); + + if (need_prelocking && ! lex->requires_prelocking()) + lex->mark_as_requiring_prelocking(save_query_tables_last); + + if (error) + goto end; + } + + if (tables->lock_type != TL_UNLOCK && ! thd->locked_tables_mode) + { + if (tables->lock_type == TL_WRITE_DEFAULT) + tables->table->reginfo.lock_type= thd->update_lock_default; + else if (tables->lock_type == TL_READ_DEFAULT) + tables->table->reginfo.lock_type= + read_lock_type_for_table(thd, tables->table); + else + tables->table->reginfo.lock_type= tables->lock_type; + } + tables->table->grant= tables->grant; + + /* Check and update metadata version of a base table. */ + error= check_and_update_table_version(thd, tables, tables->table->s); + + if (error) + goto end; + /* + After opening a MERGE table add the children to the query list of + tables, so that they are opened too. + Note that placeholders don't have the handler open. + */ + /* MERGE tables need to access parent and child TABLE_LISTs. */ + DBUG_ASSERT(tables->table->pos_in_table_list == tables); + /* Non-MERGE tables ignore this call. */ + if (tables->table->file->extra(HA_EXTRA_ADD_CHILDREN_LIST)) + { + error= TRUE; + goto end; + } + +process_view_routines: + /* + Again we may need cache all routines used by this view and add + tables used by them to table list. + */ + if (tables->view && + thd->locked_tables_mode <= LTM_LOCK_TABLES && + ! has_prelocking_list) + { + bool need_prelocking= FALSE; + TABLE_LIST **save_query_tables_last= lex->query_tables_last; + + error= prelocking_strategy->handle_view(thd, lex, tables, + &need_prelocking); + + if (need_prelocking && ! lex->requires_prelocking()) + lex->mark_as_requiring_prelocking(save_query_tables_last); + + if (error) + goto end; + } + +end: + DBUG_RETURN(error); +} + + +/** + Acquire upgradable (SNW, SNRW) metadata locks on tables to be opened + for LOCK TABLES or a DDL statement. + + @param thd Thread context. + @param tables_start Start of list of tables on which upgradable locks + should be acquired. + @param tables_end End of list of tables. + @param ot_ctx Context of open_tables() operation. + + @retval FALSE Success. + @retval TRUE Failure (e.g. connection was killed) +*/ + +static bool +open_tables_acquire_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, + TABLE_LIST *tables_end, + Open_table_context *ot_ctx) +{ + MDL_request_list mdl_requests; + TABLE_LIST *table; + + for (table= tables_start; table && table != tables_end; + table= table->next_global) + { + if (table->lock_type >= TL_WRITE_ALLOW_WRITE) + { + table->mdl_request.set_type(table->lock_type > TL_WRITE_ALLOW_READ ? + MDL_SHARED_NO_READ_WRITE : + MDL_SHARED_NO_WRITE); + mdl_requests.push_front(&table->mdl_request); + } + } + + if (! mdl_requests.is_empty()) + { + MDL_request *global_request= ot_ctx->get_global_mdl_request(thd); + + if (global_request == NULL) + return TRUE; + mdl_requests.push_front(global_request); + } + + if (thd->mdl_context.acquire_locks(&mdl_requests)) + return TRUE; + + for (table= tables_start; table && table != tables_end; + table= table->next_global) + { + if (table->lock_type >= TL_WRITE_ALLOW_WRITE) + { + table->mdl_request.ticket= NULL; + table->mdl_request.set_type(MDL_SHARED_WRITE); + } + } + + return FALSE; +} + + +/** Open all tables in list - SYNOPSIS - open_tables() - thd - thread handler - start - list of tables in/out - counter - number of opened tables will be return using this parameter - flags - bitmap of flags to modify how the tables will be open: - MYSQL_LOCK_IGNORE_FLUSH - open table even if someone has - done a flush or namelock on it. + @param[in] thd Thread context. + @param[in,out] start List of tables to be open (it can be adjusted for + statement that uses tables only implicitly, e.g. + for "SELECT f1()"). + @param[out] counter Number of tables which were open. + @param[in] flags Bitmap of flags to modify how the tables will be + open, see open_table() description for details. + @param[in] prelocking_strategy Strategy which specifies how prelocking + algorithm should work for this statement. - NOTE - Unless we are already in prelocked mode, this function will also precache - all SP/SFs explicitly or implicitly (via views and triggers) used by the - query and add tables needed for their execution to table list. If resulting - tables list will be non empty it will mark query as requiring precaching. + @note + Unless we are already in prelocked mode and prelocking strategy prescribes + so this function will also precache all SP/SFs explicitly or implicitly + (via views and triggers) used by the query and add tables needed for their + execution to table list. Statement that uses SFs, invokes triggers or + requires foreign key checks will be marked as requiring prelocking. Prelocked mode will be enabled for such query during lock_tables() call. If query for which we are opening tables is already marked as requiring prelocking it won't do such precaching and will simply reuse table list which is already built. - If any table has a trigger and start->trg_event_map is non-zero - the final lock will end up in thd->locked_tables, otherwise, the - lock will be placed in thd->lock. See also comments in - st_lex::set_trg_event_type_for_tables(). - - RETURN - 0 - OK - -1 - error + @retval FALSE Success. + @retval TRUE Error, reported. */ -int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) +bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, + Prelocking_strategy *prelocking_strategy) { - TABLE_LIST *tables= NULL; - bool refresh; - int result=0; + /* + We use pointers to "next_global" member in the last processed TABLE_LIST + element and to the "next" member in the last processed Sroutine_hash_entry + element as iterators over, correspondingly, the table list and stored routines + list which stay valid and allow to continue iteration when new elements are + added to the tail of the lists. + */ + TABLE_LIST **table_to_open; + Sroutine_hash_entry **sroutine_to_open; + TABLE_LIST *tables; + Open_table_context ot_ctx(thd); + bool error= FALSE; MEM_ROOT new_frm_mem; - /* Also used for indicating that prelocking is need */ - TABLE_LIST **query_tables_last_own; - bool safe_to_ignore_table; - + bool has_prelocking_list; DBUG_ENTER("open_tables"); + + /* + Close HANDLER tables which are marked for flush or against which there + are pending exclusive metadata locks. Note that we do this not to avoid + deadlocks (calls to mysql_ha_flush() in mdl_wait_for_locks() and + tdc_wait_for_old_version() are enough for this) but in order to have + a point during statement execution at which such HANDLERs are closed + even if they don't create problems for current thread (i.e. to avoid + having DDL blocked by HANDLERs opened for long time). + */ + if (thd->handler_tables_hash.records) + mysql_ha_flush(thd); + /* temporary mem_root for new .frm parsing. TODO: variables for size @@ -4518,310 +4462,398 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) init_sql_alloc(&new_frm_mem, 8024, 8024); thd->current_tablenr= 0; - restart: +restart: + has_prelocking_list= thd->lex->requires_prelocking(); + table_to_open= start; + sroutine_to_open= (Sroutine_hash_entry**) &thd->lex->sroutines_list.first; *counter= 0; - query_tables_last_own= 0; thd_proc_info(thd, "Opening tables"); /* - If we are not already executing prelocked statement and don't have - statement for which table list for prelocking is already built, let - us cache routines and try to build such table list. - + If we are executing LOCK TABLES statement or a DDL statement + (in non-LOCK TABLES mode) we might have to acquire upgradable + semi-exclusive metadata locks (SNW or SNRW) on some of the + tables to be opened. + So we acquire all such locks at once here as doing this in one + by one fashion may lead to deadlocks or starvation. Later when + we will be opening corresponding table pre-acquired metadata + lock will be reused (thanks to the fact that in recursive case + metadata locks are acquired without waiting). */ - - if (!thd->prelocked_mode && !thd->lex->requires_prelocking() && - thd->lex->uses_stored_routines()) + if ((flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) && + ! thd->locked_tables_mode) { - bool first_no_prelocking, need_prelocking; - TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last; - - DBUG_ASSERT(thd->lex->query_tables == *start); - sp_get_prelocking_info(thd, &need_prelocking, &first_no_prelocking); - - if (sp_cache_routines_and_add_tables(thd, thd->lex, first_no_prelocking)) + if (open_tables_acquire_upgradable_mdl(thd, *start, + thd->lex->first_not_own_table(), + &ot_ctx)) { - /* - Serious error during reading stored routines from mysql.proc table. - Something's wrong with the table or its contents, and an error has - been emitted; we must abort. - */ - result= -1; + error= TRUE; goto err; } - else if (need_prelocking) - { - query_tables_last_own= save_query_tables_last; - *start= thd->lex->query_tables; - } } /* - For every table in the list of tables to open, try to find or open - a table. + Perform steps of prelocking algorithm until there are unprocessed + elements in prelocking list/set. */ - for (tables= *start; tables ;tables= tables->next_global) + while (*table_to_open || + (thd->locked_tables_mode <= LTM_LOCK_TABLES && + *sroutine_to_open)) { - safe_to_ignore_table= FALSE; - /* - Ignore placeholders for derived tables. After derived tables - processing, link to created temporary table will be put here. - If this is derived table for view then we still want to process - routines used by this view. - */ - if (tables->derived) - { - if (tables->view) - goto process_view_routines; - continue; - } - /* - If this TABLE_LIST object is a placeholder for an information_schema - table, create a temporary table to represent the information_schema - table in the query. Do not fill it yet - will be filled during - execution. + For every table in the list of tables to open, try to find or open + a table. */ - if (tables->schema_table) + for (tables= *table_to_open; tables; + table_to_open= &tables->next_global, tables= tables->next_global) { - /* - If this information_schema table is merged into a mergeable - view, ignore it for now -- it will be filled when its respective - TABLE_LIST is processed. This code works only during re-execution. - */ - if (tables->view) - goto process_view_routines; - if (!mysql_schema_table(thd, thd->lex, tables) && - !check_and_update_table_version(thd, tables, tables->table->s)) + error= open_and_process_table(thd, thd->lex, tables, counter, + flags, prelocking_strategy, + has_prelocking_list, &ot_ctx, + &new_frm_mem); + + if (error) { - continue; + if (ot_ctx.can_recover_from_failed_open()) + { + /* + We have met exclusive metadata lock or old version of table. + Now we have to close all tables and release metadata locks. + We also have to throw away set of prelocked tables (and thus + close tables from this set that were open by now) since it + is possible that one of tables which determined its content + was changed. + + Instead of implementing complex/non-robust logic mentioned + above we simply close and then reopen all tables. + + We have to save pointer to table list element for table which we + have failed to open since closing tables can trigger removal of + elements from the table list (if MERGE tables are involved), + */ + TABLE_LIST *failed_table= *table_to_open; + close_tables_for_reopen(thd, start, ot_ctx.start_of_statement_svp()); + + /* + Here we rely on the fact that 'tables' still points to the valid + TABLE_LIST element. Altough currently this assumption is valid + it may change in future. + */ + if (ot_ctx.recover_from_failed_open(thd, &failed_table->mdl_request, + failed_table)) + goto err; + + error= FALSE; + goto restart; + } + goto err; } - DBUG_RETURN(-1); } - DBUG_PRINT("tcache", ("opening table: '%s'.'%s' item: 0x%lx", - tables->db, tables->table_name, (long) tables)); - (*counter)++; /* - Not a placeholder: must be a base table or a view, and the table is - not opened yet. Try to open the table. + If we are not already in prelocked mode and extended table list is + not yet built for our statement we need to cache routines it uses + and build the prelocking list for it. + If we are not in prelocked mode but have built the extended table + list, we still need to call open_and_process_routine() to take + MDL locks on the routines. */ - if (!tables->table) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES) { - if (tables->prelocking_placeholder) - { - /* - For the tables added by the pre-locking code, attempt to open - the table but fail silently if the table does not exist. - The real failure will occur when/if a statement attempts to use - that table. - */ - Prelock_error_handler prelock_handler; - thd->push_internal_handler(& prelock_handler); - tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags); - thd->pop_internal_handler(); - safe_to_ignore_table= prelock_handler.safely_trapped_errors(); - } - else - tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags); - } - else - DBUG_PRINT("tcache", ("referenced table: '%s'.'%s' 0x%lx", - tables->db, tables->table_name, - (long) tables->table)); - - if (!tables->table) - { - free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC)); - - if (tables->view) - { - /* VIEW placeholder */ - (*counter)--; - - /* - tables->next_global list consists of two parts: - 1) Query tables and underlying tables of views. - 2) Tables used by all stored routines that this statement invokes on - execution. - We need to know where the bound between these two parts is. If we've - just opened a view, which was the last table in part #1, and it - has added its base tables after itself, adjust the boundary pointer - accordingly. - */ - if (query_tables_last_own == &(tables->next_global) && - tables->view->query_tables) - query_tables_last_own= tables->view->query_tables_last; - /* - Let us free memory used by 'sroutines' hash here since we never - call destructor for this LEX. - */ - my_hash_free(&tables->view->sroutines); - goto process_view_routines; - } - + bool need_prelocking= FALSE; + TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last; /* - If in a MERGE table open, we need to remove the children list - from statement table list before restarting. Otherwise the list - will be inserted another time. - */ - if (tables->parent_l) - { - TABLE_LIST *parent_l= tables->parent_l; - /* The parent table should be correctly open at this point. */ - DBUG_ASSERT(parent_l->table); - parent_l->next_global= *parent_l->table->child_last_l; - } + Process elements of the prelocking set which are present there + since parsing stage or were added to it by invocations of + Prelocking_strategy methods in the above loop over tables. - if (refresh) // Refresh in progress - { - /* - We have met name-locked or old version of table. Now we have - to close all tables which are not up to date. We also have to - throw away set of prelocked tables (and thus close tables from - this set that were open by now) since it possible that one of - tables which determined its content was changed. - - Instead of implementing complex/non-robust logic mentioned - above we simply close and then reopen all tables. - - In order to prepare for recalculation of set of prelocked tables - we pretend that we have finished calculation which we were doing - currently. - */ - if (query_tables_last_own) - thd->lex->mark_as_requiring_prelocking(query_tables_last_own); - close_tables_for_reopen(thd, start); - goto restart; - } - - if (safe_to_ignore_table) - { - DBUG_PRINT("info", ("open_table: ignoring table '%s'.'%s'", - tables->db, tables->alias)); - continue; - } - - result= -1; // Fatal error - break; - } - else - { - /* - If we are not already in prelocked mode and extended table list is not - yet built and we have trigger for table being opened then we should - cache all routines used by its triggers and add their tables to - prelocking list. - If we lock table for reading we won't update it so there is no need to - process its triggers since they never will be activated. + For example, if element is a routine, cache it and then, + if prelocking strategy prescribes so, add tables it uses to the + table list and routines it might invoke to the prelocking set. */ - if (!thd->prelocked_mode && !thd->lex->requires_prelocking() && - tables->trg_event_map && tables->table->triggers && - tables->lock_type >= TL_WRITE_ALLOW_WRITE) + for (Sroutine_hash_entry *rt= *sroutine_to_open; rt; + sroutine_to_open= &rt->next, rt= rt->next) { - if (!query_tables_last_own) - query_tables_last_own= thd->lex->query_tables_last; - if (sp_cache_routines_and_add_tables_for_triggers(thd, thd->lex, - tables)) + error= open_and_process_routine(thd, thd->lex, rt, prelocking_strategy, + has_prelocking_list, &ot_ctx, + &need_prelocking); + + if (error) { + if (ot_ctx.can_recover_from_failed_open()) + { + close_tables_for_reopen(thd, start, + ot_ctx.start_of_statement_svp()); + if (ot_ctx.recover_from_failed_open(thd, &rt->mdl_request, NULL)) + goto err; + + error= FALSE; + goto restart; + } /* Serious error during reading stored routines from mysql.proc table. - Something's wrong with the table or its contents, and an error has + Something is wrong with the table or its contents, and an error has been emitted; we must abort. */ - result= -1; goto err; } } - free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC)); - } - if (tables->lock_type != TL_UNLOCK && ! thd->locked_tables) - { - if (tables->lock_type == TL_WRITE_DEFAULT) - tables->table->reginfo.lock_type= thd->update_lock_default; - else if (tables->lock_type == TL_READ_DEFAULT) - tables->table->reginfo.lock_type= - read_lock_type_for_table(thd, tables->table); - else - tables->table->reginfo.lock_type= tables->lock_type; - } - tables->table->grant= tables->grant; + if (need_prelocking && ! thd->lex->requires_prelocking()) + thd->lex->mark_as_requiring_prelocking(save_query_tables_last); - /* Check and update metadata version of a base table. */ - if (check_and_update_table_version(thd, tables, tables->table->s)) - { - result= -1; - goto err; + if (need_prelocking && ! *start) + *start= thd->lex->query_tables; } + } - /* Attach MERGE children if not locked already. */ - DBUG_PRINT("tcache", ("is parent: %d is child: %d", - test(tables->table->child_l), - test(tables->parent_l))); - DBUG_PRINT("tcache", ("in lock tables: %d in prelock mode: %d", - test(thd->locked_tables), test(thd->prelocked_mode))); - if (((!thd->locked_tables && !thd->prelocked_mode) || - tables->table->s->tmp_table) && - ((tables->table->child_l && - add_merge_table_list(tables)) || - (tables->parent_l && - (&tables->next_global == tables->parent_l->table->child_last_l) && - attach_merge_children(tables)))) - { - result= -1; - goto err; - } + /* + After successful open of all tables, including MERGE parents and + children, attach the children to their parents. At end of statement, + the children are detached. Attaching and detaching are always done, + even under LOCK TABLES. + */ + for (tables= *start; tables; tables= tables->next_global) + { + TABLE *tbl= tables->table; -process_view_routines: - /* - Again we may need cache all routines used by this view and add - tables used by them to table list. - */ - if (tables->view && !thd->prelocked_mode && - !thd->lex->requires_prelocking() && - tables->view->uses_stored_routines()) - { - /* We have at least one table in TL here. */ - if (!query_tables_last_own) - query_tables_last_own= thd->lex->query_tables_last; - if (sp_cache_routines_and_add_tables_for_view(thd, thd->lex, tables)) + /* Schema tables may not have a TABLE object here. */ + if (tbl && tbl->file->ht->db_type == DB_TYPE_MRG_MYISAM) + { + /* MERGE tables need to access parent and child TABLE_LISTs. */ + DBUG_ASSERT(tbl->pos_in_table_list == tables); + if (tbl->file->extra(HA_EXTRA_ATTACH_CHILDREN)) { - /* - Serious error during reading stored routines from mysql.proc table. - Something is wrong with the table or its contents, and an error has - been emitted; we must abort. - */ - result= -1; + error= TRUE; goto err; } } } - err: +err: thd_proc_info(thd, 0); free_root(&new_frm_mem, MYF(0)); // Free pre-alloced block - if (query_tables_last_own) - thd->lex->mark_as_requiring_prelocking(query_tables_last_own); + if (error && *table_to_open) + { + (*table_to_open)->table= NULL; + } + DBUG_PRINT("open_tables", ("returning: %d", (int) error)); + DBUG_RETURN(error); +} + + +/** + Defines how prelocking algorithm for DML statements should handle routines: + - For CALL statements we do unrolling (i.e. open and lock tables for each + sub-statement individually). So for such statements prelocking is enabled + only if stored functions are used in parameter list and only for period + during which we calculate values of parameters. Thus in this strategy we + ignore procedure which is directly called by such statement and extend + the prelocking set only with tables/functions used by SF called from the + parameter list. + - For any other statement any routine which is directly or indirectly called + by statement is going to be executed in prelocked mode. So in this case we + simply add all tables and routines used by it to the prelocking set. + + @param[in] thd Thread context. + @param[in] prelocking_ctx Prelocking context of the statement. + @param[in] rt Prelocking set element describing routine. + @param[in] sp Routine body. + @param[out] need_prelocking Set to TRUE if method detects that prelocking + required, not changed otherwise. + + @retval FALSE Success. + @retval TRUE Failure (OOM). +*/ + +bool DML_prelocking_strategy:: +handle_routine(THD *thd, Query_tables_list *prelocking_ctx, + Sroutine_hash_entry *rt, sp_head *sp, bool *need_prelocking) +{ + /* + We assume that for any "CALL proc(...)" statement sroutines_list will + have 'proc' as first element (it may have several, consider e.g. + "proc(sp_func(...)))". This property is currently guaranted by the + parser. + */ - if (result && tables) + if (rt != (Sroutine_hash_entry*)prelocking_ctx->sroutines_list.first || + rt->mdl_request.key.mdl_namespace() != MDL_key::PROCEDURE) { - /* - Some functions determine success as (tables->table != NULL). - tables->table is in thd->open_tables. It won't go lost. If the - error happens on a MERGE child, clear the parents TABLE reference. - */ - if (tables->parent_l) + *need_prelocking= TRUE; + sp_update_stmt_used_routines(thd, prelocking_ctx, &sp->m_sroutines, + rt->belong_to_view); + (void)sp->add_used_tables_to_table_list(thd, + &prelocking_ctx->query_tables_last, + rt->belong_to_view); + } + sp->propagate_attributes(prelocking_ctx); + return FALSE; +} + + +/** + Defines how prelocking algorithm for DML statements should handle table list + elements: + - If table has triggers we should add all tables and routines + used by them to the prelocking set. + + We do not need to acquire metadata locks on trigger names + in DML statements, since all DDL statements + that change trigger metadata always lock their + subject tables. + + @param[in] thd Thread context. + @param[in] prelocking_ctx Prelocking context of the statement. + @param[in] table_list Table list element for table. + @param[in] sp Routine body. + @param[out] need_prelocking Set to TRUE if method detects that prelocking + required, not changed otherwise. + + @retval FALSE Success. + @retval TRUE Failure (OOM). +*/ + +bool DML_prelocking_strategy:: +handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking) +{ + /* We rely on a caller to check that table is going to be changed. */ + DBUG_ASSERT(table_list->lock_type >= TL_WRITE_ALLOW_WRITE); + + if (table_list->trg_event_map) + { + if (table_list->table->triggers) { - if (tables->parent_l->next_global == tables->parent_l->table->child_l) - tables->parent_l->next_global= *tables->parent_l->table->child_last_l; - tables->parent_l->table= NULL; + *need_prelocking= TRUE; + + if (table_list->table->triggers-> + add_tables_and_routines_for_triggers(thd, prelocking_ctx, table_list)) + return TRUE; } - tables->table= NULL; } - DBUG_PRINT("tcache", ("returning: %d", result)); - DBUG_RETURN(result); + + return FALSE; +} + + +/** + Defines how prelocking algorithm for DML statements should handle view - + all view routines should be added to the prelocking set. + + @param[in] thd Thread context. + @param[in] prelocking_ctx Prelocking context of the statement. + @param[in] table_list Table list element for view. + @param[in] sp Routine body. + @param[out] need_prelocking Set to TRUE if method detects that prelocking + required, not changed otherwise. + + @retval FALSE Success. + @retval TRUE Failure (OOM). +*/ + +bool DML_prelocking_strategy:: +handle_view(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking) +{ + if (table_list->view->uses_stored_routines()) + { + *need_prelocking= TRUE; + + sp_update_stmt_used_routines(thd, prelocking_ctx, + &table_list->view->sroutines_list, + table_list->top_table()); + } + return FALSE; +} + + +/** + Defines how prelocking algorithm for LOCK TABLES statement should handle + table list elements. + + @param[in] thd Thread context. + @param[in] prelocking_ctx Prelocking context of the statement. + @param[in] table_list Table list element for table. + @param[in] sp Routine body. + @param[out] need_prelocking Set to TRUE if method detects that prelocking + required, not changed otherwise. + + @retval FALSE Success. + @retval TRUE Failure (OOM). +*/ + +bool Lock_tables_prelocking_strategy:: +handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking) +{ + if (DML_prelocking_strategy::handle_table(thd, prelocking_ctx, table_list, + need_prelocking)) + return TRUE; + + /* We rely on a caller to check that table is going to be changed. */ + DBUG_ASSERT(table_list->lock_type >= TL_WRITE_ALLOW_WRITE); + + return FALSE; +} + + +/** + Defines how prelocking algorithm for ALTER TABLE statement should handle + routines - do nothing as this statement is not supposed to call routines. + + We still can end up in this method when someone tries + to define a foreign key referencing a view, and not just + a simple view, but one that uses stored routines. +*/ + +bool Alter_table_prelocking_strategy:: +handle_routine(THD *thd, Query_tables_list *prelocking_ctx, + Sroutine_hash_entry *rt, sp_head *sp, bool *need_prelocking) +{ + return FALSE; +} + + +/** + Defines how prelocking algorithm for ALTER TABLE statement should handle + table list elements. + + Unlike in DML, we do not process triggers here. + + @param[in] thd Thread context. + @param[in] prelocking_ctx Prelocking context of the statement. + @param[in] table_list Table list element for table. + @param[in] sp Routine body. + @param[out] need_prelocking Set to TRUE if method detects that prelocking + required, not changed otherwise. + + + @retval FALSE Success. + @retval TRUE Failure (OOM). +*/ + +bool Alter_table_prelocking_strategy:: +handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking) +{ + return FALSE; +} + + +/** + Defines how prelocking algorithm for ALTER TABLE statement + should handle view - do nothing. We don't need to add view + routines to the prelocking set in this case as view is not going + to be materialized. +*/ + +bool Alter_table_prelocking_strategy:: +handle_view(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking) +{ + return FALSE; } @@ -4866,6 +4898,8 @@ static bool check_lock_and_start_stmt(THD *thd, TABLE *table, @param[in] thd thread handle @param[in] table_l table to open is first table in this list @param[in] lock_type lock to use for table + @param[in] flags options to be used while opening and locking + table (see open_table(), mysql_lock_tables()) @return table @retval != NULL OK, opened table returned @@ -4891,7 +4925,7 @@ static bool check_lock_and_start_stmt(THD *thd, TABLE *table, */ TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l, - thr_lock_type lock_type) + thr_lock_type lock_type, uint flags) { TABLE_LIST *save_next_global; DBUG_ENTER("open_n_lock_single_table"); @@ -4907,7 +4941,7 @@ TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l, table_l->required_type= FRMTYPE_TABLE; /* Open the table. */ - if (simple_open_n_lock_tables(thd, table_l)) + if (open_and_lock_tables_derived(thd, table_l, FALSE, flags)) table_l->table= NULL; /* Just to be sure. */ /* Restore list. */ @@ -4945,23 +4979,46 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, uint lock_flags) { TABLE *table; + Open_table_context ot_ctx(thd); bool refresh; + bool error; DBUG_ENTER("open_ltable"); /* should not be used in a prelocked_mode context, see NOTE above */ - DBUG_ASSERT(!thd->prelocked_mode); + DBUG_ASSERT(thd->locked_tables_mode < LTM_PRELOCKED); thd_proc_info(thd, "Opening table"); thd->current_tablenr= 0; /* open_ltable can be used only for BASIC TABLEs */ table_list->required_type= FRMTYPE_TABLE; - while (!(table= open_table(thd, table_list, thd->mem_root, &refresh, 0)) && - refresh) - ; - if (table) +retry: + while ((error= open_table(thd, table_list, thd->mem_root, &ot_ctx, 0)) && + ot_ctx.can_recover_from_failed_open()) { - if (table->child_l) + /* We never have an open HANDLER, LOCK TABLES or GRL here. */ + DBUG_ASSERT(thd->mdl_context.trans_sentinel() == NULL); + /* + Even though we have failed to open table we still need to + call release_transactional_locks() to release metadata locks which + might have been acquired successfully. + */ + thd->mdl_context.rollback_to_savepoint(ot_ctx.start_of_statement_svp()); + table_list->mdl_request.ticket= 0; + if (ot_ctx.recover_from_failed_open(thd, &table_list->mdl_request, + table_list)) + break; + } + + if (!error) + { + /* + We can't have a view or some special "open_strategy" in this function + so there should be a TABLE instance. + */ + DBUG_ASSERT(table_list->table); + table= table_list->table; + if (table->file->ht->db_type == DB_TYPE_MRG_MYISAM) { /* A MERGE table must not come here. */ /* purecov: begin tested */ @@ -4973,9 +5030,8 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, } table_list->lock_type= lock_type; - table_list->table= table; table->grant= table_list->grant; - if (thd->locked_tables) + if (thd->locked_tables_mode) { if (check_lock_and_start_stmt(thd, table, lock_type)) table= 0; @@ -4986,70 +5042,99 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, if ((table->reginfo.lock_type= lock_type) != TL_UNLOCK) if (! (thd->lock= mysql_lock_tables(thd, &table_list->table, 1, lock_flags, &refresh))) - table= 0; + { + if (refresh) + { + close_thread_tables(thd); + table_list->table= NULL; + table_list->mdl_request.ticket= NULL; + /* We never have an open HANDLER, LOCK TABLES or GRL here. */ + DBUG_ASSERT(thd->mdl_context.trans_sentinel() == NULL); + thd->mdl_context.rollback_to_savepoint(ot_ctx.start_of_statement_svp()); + goto retry; + } + else + table= 0; + } } } + else + table= 0; - end: +end: thd_proc_info(thd, 0); DBUG_RETURN(table); } -/* +/** Open all tables in list, locks them and optionally process derived tables. - SYNOPSIS - open_and_lock_tables_derived() - thd - thread handler - tables - list of tables for open&locking - derived - if to handle derived tables - - RETURN - FALSE - ok - TRUE - error + @param thd Thread context. + @param tables List of tables for open and locking. + @param derived If to handle derived tables. + @param flags Bitmap of options to be used to open and lock + tables (see open_tables() and mysql_lock_tables() + for details). + @param prelocking_strategy Strategy which specifies how prelocking algorithm + should work for this statement. - NOTE + @note The lock will automaticaly be freed by close_thread_tables() - NOTE - There are two convenience functions: + @note + There are several convenience functions, e.g. : - simple_open_n_lock_tables(thd, tables) without derived handling - open_and_lock_tables(thd, tables) with derived handling Both inline functions call open_and_lock_tables_derived() with the third argument set appropriately. + + @retval FALSE OK. + @retval TRUE Error */ -int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived) +bool open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, + bool derived, uint flags, + Prelocking_strategy *prelocking_strategy) { uint counter; bool need_reopen; + /* + Remember the set of metadata locks which this connection + managed to acquire before the start of the current statement. + It can be either transaction-scope locks, or HANDLER locks, + or LOCK TABLES locks. If mysql_lock_tables() fails with + need_reopen request, we'll use it to instruct + close_tables_for_reopen() to release all locks of this + statement. + */ + MDL_ticket *start_of_statement_svp= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("open_and_lock_tables_derived"); DBUG_PRINT("enter", ("derived handling: %d", derived)); for ( ; ; ) { - if (open_tables(thd, &tables, &counter, 0)) - DBUG_RETURN(-1); - + if (open_tables(thd, &tables, &counter, flags, prelocking_strategy)) + DBUG_RETURN(TRUE); DBUG_EXECUTE_IF("sleep_open_and_lock_after_open", { const char *old_proc_info= thd->proc_info; thd->proc_info= "DBUG sleep"; my_sleep(6000000); thd->proc_info= old_proc_info;}); - if (!lock_tables(thd, tables, counter, &need_reopen)) + if (!lock_tables(thd, tables, counter, flags, + &need_reopen)) break; if (!need_reopen) - DBUG_RETURN(-1); - close_tables_for_reopen(thd, &tables); + DBUG_RETURN(TRUE); + close_tables_for_reopen(thd, &tables, start_of_statement_svp); } 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 */ - DBUG_RETURN(0); + DBUG_RETURN(FALSE); } @@ -5143,7 +5228,7 @@ static void mark_real_tables_as_free_for_reuse(TABLE_LIST *table) @param tables Tables involved in the query */ -int decide_logging_format(THD *thd, TABLE_LIST *tables) +bool decide_logging_format(THD *thd, TABLE_LIST *tables) { /* In SBR mode, we are only proceeding if we are binlogging this @@ -5244,7 +5329,7 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables) DBUG_PRINT("info", ("error: %d", error)); if (error) - return -1; + return TRUE; /* We switch to row-based format if we are in mixed mode and one of @@ -5265,7 +5350,7 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables) } } - return 0; + return FALSE; } /* @@ -5276,6 +5361,7 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables) thd Thread handler tables Tables to lock count Number of opened tables + flags Options (see mysql_lock_tables() for details) need_reopen Out parameter which if TRUE indicates that some tables were dropped or altered during this call and therefore invoker should reopen tables and @@ -5287,16 +5373,17 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables) handling thr_lock gives us. You most always get all needed locks at once. - If query for which we are calling this function marked as requring - prelocking, this function will do implicit LOCK TABLES and change - thd::prelocked_mode accordingly. + If query for which we are calling this function marked as requiring + prelocking, this function will change locked_tables_mode to + LTM_PRELOCKED. RETURN VALUES 0 ok -1 Error */ -int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) +bool lock_tables(THD *thd, TABLE_LIST *tables, uint count, + uint flags, bool *need_reopen) { TABLE_LIST *table; @@ -5305,29 +5392,30 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) We can't meet statement requiring prelocking if we already in prelocked mode. */ - DBUG_ASSERT(!thd->prelocked_mode || !thd->lex->requires_prelocking()); + DBUG_ASSERT(thd->locked_tables_mode <= LTM_LOCK_TABLES || + !thd->lex->requires_prelocking()); *need_reopen= FALSE; if (!tables && !thd->lex->requires_prelocking()) DBUG_RETURN(decide_logging_format(thd, tables)); /* - We need this extra check for thd->prelocked_mode because we want to avoid - attempts to lock tables in substatements. Checking for thd->locked_tables - is not enough in some situations. For example for SP containing + Check for thd->locked_tables_mode to avoid a redundant + and harmful attempt to lock the already locked tables again. + Checking for thd->lock is not enough in some situations. For example, + if a stored function contains "drop table t3; create temporary t3 ..; insert into t3 ...;" - thd->locked_tables may be 0 after drop tables, and without this extra - check insert will try to lock temporary table t3, that will lead - to memory leak... + thd->lock may be 0 after drop tables, whereas locked_tables_mode + is still on. In this situation an attempt to lock temporary + table t3 will lead to a memory leak. */ - if (!thd->locked_tables && !thd->prelocked_mode) + if (! thd->locked_tables_mode) { DBUG_ASSERT(thd->lock == 0); // You must lock everything at once TABLE **start,**ptr; - uint lock_flag= MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN; if (!(ptr=start=(TABLE**) thd->alloc(sizeof(TABLE*)*count))) - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); for (table= tables; table; table= table->next_global) { if (!table->placeholder()) @@ -5337,8 +5425,6 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) /* We have to emulate LOCK TABLES if we are statement needs prelocking. */ if (thd->lex->requires_prelocking()) { - thd->in_lock_tables=1; - thd->variables.option_bits|= OPTION_TABLE_LOCK; /* A query that modifies autoinc column in sub-statement can make the master and slave inconsistent. @@ -5355,15 +5441,10 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) DEBUG_SYNC(thd, "before_lock_tables_takes_lock"); if (! (thd->lock= mysql_lock_tables(thd, start, (uint) (ptr - start), - lock_flag, need_reopen))) - { - if (thd->lex->requires_prelocking()) - { - thd->variables.option_bits&= ~(OPTION_TABLE_LOCK); - thd->in_lock_tables=0; - } - DBUG_RETURN(-1); - } + flags, need_reopen))) + DBUG_RETURN(TRUE); + + DEBUG_SYNC(thd, "after_lock_tables_takes_lock"); if (thd->lex->requires_prelocking() && thd->lex->sql_command != SQLCOM_LOCK_TABLES) @@ -5373,17 +5454,6 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) We just have done implicit LOCK TABLES, and now we have to emulate first open_and_lock_tables() after it. - Note that "LOCK TABLES" can also be marked as requiring prelocking - (e.g. if one locks view which uses functions). We should not emulate - such open_and_lock_tables() in this case. We also should not set - THD::prelocked_mode or first close_thread_tables() call will do - "UNLOCK TABLES". - */ - thd->locked_tables= thd->lock; - thd->lock= 0; - thd->in_lock_tables=0; - - /* When open_and_lock_tables() is called for a single table out of a table list, the 'next_global' chain is temporarily broken. We may not find 'first_not_own' before the end of the "list". @@ -5400,10 +5470,9 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) table->table->query_id= thd->query_id; if (check_lock_and_start_stmt(thd, table->table, table->lock_type)) { - mysql_unlock_tables(thd, thd->locked_tables); - thd->locked_tables= 0; - thd->variables.option_bits&= ~(OPTION_TABLE_LOCK); - DBUG_RETURN(-1); + mysql_unlock_tables(thd, thd->lock); + thd->lock= 0; + DBUG_RETURN(TRUE); } } } @@ -5412,8 +5481,8 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) and was marked as occupied during open_tables() as free for reuse. */ mark_real_tables_as_free_for_reuse(first_not_own); - DBUG_PRINT("info",("prelocked_mode= PRELOCKED")); - thd->prelocked_mode= PRELOCKED; + DBUG_PRINT("info",("locked_tables_mode= LTM_PRELOCKED")); + thd->enter_locked_tables_mode(LTM_PRELOCKED); } } else @@ -5438,7 +5507,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) In a stored function or trigger we should ensure that we won't change a table that is already used by the calling statement. */ - if (thd->prelocked_mode && + if (thd->locked_tables_mode >= LTM_PRELOCKED && table->lock_type >= TL_WRITE_ALLOW_WRITE) { for (TABLE* opentab= thd->open_tables; opentab; opentab= opentab->next) @@ -5448,14 +5517,14 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) { my_error(ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG, MYF(0), table->table->s->table_name.str); - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } } if (check_lock_and_start_stmt(thd, table->table, table->lock_type)) { - DBUG_RETURN(-1); + DBUG_RETURN(TRUE); } } /* @@ -5466,8 +5535,9 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) if (thd->lex->requires_prelocking()) { mark_real_tables_as_free_for_reuse(first_not_own); - DBUG_PRINT("info", ("thd->prelocked_mode= PRELOCKED_UNDER_LOCK_TABLES")); - thd->prelocked_mode= PRELOCKED_UNDER_LOCK_TABLES; + DBUG_PRINT("info", + ("thd->locked_tables_mode= LTM_PRELOCKED_UNDER_LOCK_TABLES")); + thd->locked_tables_mode= LTM_PRELOCKED_UNDER_LOCK_TABLES; } } @@ -5475,30 +5545,59 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) } -/* +/** Prepare statement for reopening of tables and recalculation of set of prelocked tables. - SYNOPSIS - close_tables_for_reopen() - thd in Thread context - tables in/out List of tables which we were trying to open and lock - + @param[in] thd Thread context. + @param[in,out] tables List of tables which we were trying to open + and lock. + @param[in] start_of_statement_svp MDL savepoint which represents the set + of metadata locks which the current transaction + managed to acquire before execution of the current + statement and to which we should revert before + trying to reopen tables. NULL if no metadata locks + were held and thus all metadata locks should be + released. */ -void close_tables_for_reopen(THD *thd, TABLE_LIST **tables) +void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, + MDL_ticket *start_of_statement_svp) { + TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table(); + TABLE_LIST *tmp; + /* If table list consists only from tables from prelocking set, table list for new attempt should be empty, so we have to update list's root pointer. */ - if (thd->lex->first_not_own_table() == *tables) + if (first_not_own_table == *tables) *tables= 0; thd->lex->chop_off_not_own_tables(); + /* Reset MDL tickets for procedures/functions */ + for (Sroutine_hash_entry *rt= + (Sroutine_hash_entry*)thd->lex->sroutines_list.first; + rt; rt= rt->next) + rt->mdl_request.ticket= NULL; sp_remove_not_own_routines(thd->lex); - for (TABLE_LIST *tmp= *tables; tmp; tmp= tmp->next_global) + for (tmp= *tables; tmp; tmp= tmp->next_global) + { tmp->table= 0; + tmp->mdl_request.ticket= NULL; + /* We have to cleanup translation tables of views. */ + tmp->cleanup_items(); + } + /* + Metadata lock requests for tables from extended part of prelocking set + are part of list of requests to be waited for in Open_table_context. + So to satisfy assumptions in MDL_context::wait_for_locks(), which will + performs the waiting, we have to reset MDL_request::ticket values for + them as well. + */ + for (tmp= first_not_own_table; tmp; tmp= tmp->next_global) + tmp->mdl_request.ticket= NULL; close_thread_tables(thd); + thd->mdl_context.rollback_to_savepoint(start_of_statement_svp); } @@ -8405,36 +8504,6 @@ my_bool mysql_rm_tmp_tables(void) *****************************************************************************/ /* - Invalidate any cache entries that are for some DB - - SYNOPSIS - remove_db_from_cache() - db Database name. This will be in lower case if - lower_case_table_name is set - - NOTE: - We can't use hash_delete when looping hash_elements. We mark them first - and afterwards delete those marked unused. -*/ - -void remove_db_from_cache(const char *db) -{ - for (uint idx=0 ; idx < open_cache.records ; idx++) - { - TABLE *table=(TABLE*) my_hash_element(&open_cache,idx); - if (!strcmp(table->s->db.str, db)) - { - table->s->version= 0L; /* Free when thread is ready */ - if (!table->in_use) - relink_unused(table); - } - } - while (unused_tables && !unused_tables->s->version) - my_hash_delete(&open_cache,(uchar*) unused_tables); -} - - -/* free all unused tables NOTE @@ -8446,167 +8515,219 @@ void flush_tables() { mysql_mutex_lock(&LOCK_open); while (unused_tables) - my_hash_delete(&open_cache,(uchar*) unused_tables); - mysql_mutex_unlock(&LOCK_open); + free_cache_entry(unused_tables); + (void) mysql_mutex_unlock(&LOCK_open); } -/* - Mark all entries with the table as deleted to force an reopen of the table +/** + A callback to the server internals that is used to address + special cases of the locking protocol. + Invoked when acquiring an exclusive lock, for each thread that + has a conflicting shared metadata lock. + + This function: + - aborts waiting of the thread on a data lock, to make it notice + the pending exclusive lock and back off. + - if the thread is an INSERT DELAYED thread, sends it a KILL + signal to terminate it. + + @note This function does not wait for the thread to give away its + locks. Waiting is done outside for all threads at once. + + @param thd Current thread context + @param in_use The thread to wake up + @param needs_thr_lock_abort Indicates that to wake up thread + this call needs to abort its waiting + on table-level lock. + + @retval TRUE if the thread was woken up + @retval FALSE otherwise. + + @note It is one of two places where border between MDL and the + rest of the server is broken. +*/ - The table will be closed (not stored in cache) by the current thread when - close_thread_tables() is called. +bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use, + bool needs_thr_lock_abort) +{ + bool signalled= FALSE; + if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && + !in_use->killed) + { + in_use->killed= THD::KILL_CONNECTION; + mysql_mutex_lock(&in_use->mysys_var->mutex); + if (in_use->mysys_var->current_cond) + mysql_cond_broadcast(in_use->mysys_var->current_cond); + mysql_mutex_unlock(&in_use->mysys_var->mutex); + signalled= TRUE; + } + mysql_mutex_lock(&LOCK_open); - PREREQUISITES - Lock on LOCK_open() + if (needs_thr_lock_abort) + { + for (TABLE *thd_table= in_use->open_tables; + thd_table ; + thd_table= thd_table->next) + { + /* + Check for TABLE::needs_reopen() is needed since in some places we call + handler::close() for table instance (and set TABLE::db_stat to 0) + and do not remove such instances from the THD::open_tables + for some time, during which other thread can see those instances + (e.g. see partitioning code). + */ + if (!thd_table->needs_reopen()) + signalled|= mysql_lock_abort_for_thread(thd, thd_table); + } + } + /* + Wake up threads waiting in tdc_wait_for_old_versions(). + Normally such threads would already get blocked + in MDL subsystem, when trying to acquire a shared lock. + But in case a thread has an open HANDLER statement, + (and thus already grabbed a metadata lock), it gets + blocked only too late -- at the table cache level. + Starting from 5.5, this could also easily happen in + a multi-statement transaction. + */ + broadcast_refresh(); + mysql_mutex_unlock(&LOCK_open); + return signalled; +} - RETURN - 0 This thread now have exclusive access to this table and no other thread - can access the table until close_thread_tables() is called. - 1 Table is in use by another thread + +/** + Remove all or some (depending on parameter) instances of TABLE and + TABLE_SHARE from the table definition cache. + + @param thd Thread context + @param remove_type Type of removal: + TDC_RT_REMOVE_ALL - remove all TABLE instances and + TABLE_SHARE instance. There + should be no used TABLE objects + and caller should have exclusive + metadata lock on the table. + TDC_RT_REMOVE_NOT_OWN - remove all TABLE instances + except those that belong to + this thread. There should be + no TABLE objects used by other + threads and caller should have + exclusive metadata lock on the + table. + TDC_RT_REMOVE_UNUSED - remove all unused TABLE + instances (if there are no + used instances will also + remove TABLE_SHARE). + @param db Name of database + @param table_name Name of table + + @note Unlike remove_table_from_cache() it assumes that table instances + are already not used by any (other) thread (this should be achieved + by using meta-data locks). */ -bool remove_table_from_cache(THD *thd, const char *db, const char *table_name, - uint flags) +void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, + const char *db, const char *table_name) { char key[MAX_DBKEY_LENGTH]; uint key_length; TABLE *table; TABLE_SHARE *share; - bool result= 0, signalled= 0; - DBUG_ENTER("remove_table_from_cache"); - DBUG_PRINT("enter", ("table: '%s'.'%s' flags: %u", db, table_name, flags)); + + mysql_mutex_assert_owner(&LOCK_open); + + DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED || + thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name, + MDL_EXCLUSIVE)); key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1; - for (;;) - { - HASH_SEARCH_STATE state; - result= signalled= 0; - for (table= (TABLE*) my_hash_first(&open_cache, (uchar*) key, key_length, - &state); - table; - table= (TABLE*) my_hash_next(&open_cache, (uchar*) key, key_length, - &state)) + if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key, + key_length))) + { + if (share->ref_count) { - THD *in_use; - DBUG_PRINT("tcache", ("found table: '%s'.'%s' 0x%lx", table->s->db.str, - table->s->table_name.str, (long) table)); - - table->s->version=0L; /* Free when thread is ready */ - if (!(in_use=table->in_use)) + I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables); +#ifndef DBUG_OFF + if (remove_type == TDC_RT_REMOVE_ALL) { - DBUG_PRINT("info",("Table was not in use")); - relink_unused(table); + DBUG_ASSERT(share->used_tables.is_empty()); } - else if (in_use != thd) + else if (remove_type == TDC_RT_REMOVE_NOT_OWN) { - DBUG_PRINT("info", ("Table was in use by other thread")); - /* - Mark that table is going to be deleted from cache. This will - force threads that are in mysql_lock_tables() (but not yet - in thr_multi_lock()) to abort it's locks, close all tables and retry - */ - in_use->some_tables_deleted= 1; - if (table->is_name_opened()) - { - DBUG_PRINT("info", ("Found another active instance of the table")); - result=1; - } - /* Kill delayed insert threads */ - if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && - ! in_use->killed) - { - in_use->killed= THD::KILL_CONNECTION; - mysql_mutex_lock(&in_use->mysys_var->mutex); - if (in_use->mysys_var->current_cond) - { - mysql_mutex_lock(in_use->mysys_var->current_mutex); - signalled= 1; - mysql_cond_broadcast(in_use->mysys_var->current_cond); - mysql_mutex_unlock(in_use->mysys_var->current_mutex); - } - mysql_mutex_unlock(&in_use->mysys_var->mutex); - } - /* - Now we must abort all tables locks used by this thread - as the thread may be waiting to get a lock for another table. - Note that we need to hold LOCK_open while going through the - list. So that the other thread cannot change it. The other - thread must also hold LOCK_open whenever changing the - open_tables list. Aborting the MERGE lock after a child was - closed and before the parent is closed would be fatal. - */ - for (TABLE *thd_table= in_use->open_tables; - thd_table ; - thd_table= thd_table->next) - { - /* Do not handle locks of MERGE children. */ - if (thd_table->db_stat && !thd_table->parent) // If table is open - signalled|= mysql_lock_abort_for_thread(thd, thd_table); - } - } - else - { - DBUG_PRINT("info", ("Table was in use by current thread. db_stat: %u", - table->db_stat)); - result= result || (flags & RTFC_OWNED_BY_THD_FLAG); + I_P_List_iterator<TABLE, TABLE_share> it2(share->used_tables); + while ((table= it2++)) + if (table->in_use != thd) + { + DBUG_ASSERT(0); + } } +#endif + /* + Set share's version to zero in order to ensure that it gets + automatically deleted once it is no longer referenced. + */ + share->version= 0; + while ((table= it++)) + free_cache_entry(table); } - while (unused_tables && !unused_tables->s->version) - my_hash_delete(&open_cache,(uchar*) unused_tables); + else + (void) my_hash_delete(&table_def_cache, (uchar*) share); + } +} - DBUG_PRINT("info", ("Removing table from table_def_cache")); - /* Remove table from table definition cache if it's not in use */ - if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key, - key_length))) - { - DBUG_PRINT("info", ("share version: %lu ref_count: %u", - share->version, share->ref_count)); - share->version= 0; // Mark for delete - if (share->ref_count == 0) - { - mysql_mutex_lock(&share->mutex); - my_hash_delete(&table_def_cache, (uchar*) share); - } - } - if (result && (flags & RTFC_WAIT_OTHER_THREAD_FLAG)) +/** + Wait until there are no old versions of tables in the table + definition cache for the metadata locks that we try to acquire. + + @param thd Thread context + @param context Metadata locking context with locks. +*/ + +static bool +tdc_wait_for_old_versions(THD *thd, MDL_request_list *mdl_requests) +{ + TABLE_SHARE *share; + const char *old_msg; + MDL_request *mdl_request; + + while (!thd->killed) + { + /* + Here we have situation as in mdl_wait_for_locks() we need to + get rid of offending HANDLERs to avoid deadlock. + TODO: We should also investigate in which situations we have + to broadcast on COND_refresh because of this. + */ + mysql_ha_flush(thd); + + mysql_mutex_lock(&LOCK_open); + + MDL_request_list::Iterator it(*mdl_requests); + while ((mdl_request= it++)) { - /* - Signal any thread waiting for tables to be freed to - reopen their tables - */ - broadcast_refresh(); - DBUG_PRINT("info", ("Waiting for refresh signal")); - if (!(flags & RTFC_CHECK_KILLED_FLAG) || !thd->killed) - { - dropping_tables++; - if (likely(signalled)) - mysql_cond_wait(&COND_refresh, &LOCK_open); - else - { - struct timespec abstime; - /* - It can happen that another thread has opened the - table but has not yet locked any table at all. Since - it can be locked waiting for a table that our thread - has done LOCK TABLE x WRITE on previously, we need to - ensure that the thread actually hears our signal - before we go to sleep. Thus we wait for a short time - and then we retry another loop in the - remove_table_from_cache routine. - */ - set_timespec(abstime, 10); - mysql_cond_timedwait(&COND_refresh, &LOCK_open, &abstime); - } - dropping_tables--; + /* Skip requests on non-TDC objects. */ + if (mdl_request->key.mdl_namespace() != MDL_key::TABLE) continue; - } + + if ((share= get_cached_table_share(mdl_request->key.db_name(), + mdl_request->key.name())) && + share->version != refresh_version) + break; } - break; + if (!mdl_request) + { + mysql_mutex_unlock(&LOCK_open); + break; + } + old_msg= thd->enter_cond(&COND_refresh, &LOCK_open, "Waiting for table"); + mysql_cond_wait(&COND_refresh, &LOCK_open); + /* LOCK_open mutex is unlocked by THD::exit_cond() as side-effect. */ + thd->exit_cond(old_msg); } - DBUG_RETURN(result); + return thd->killed; } @@ -8707,7 +8828,6 @@ open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, } err: - bzero(outparam, sizeof(TABLE)); // do not run repair DBUG_RETURN(1); } @@ -8740,149 +8860,21 @@ bool is_equal(const LEX_STRING *a, const LEX_STRING *b) int abort_and_upgrade_lock(ALTER_PARTITION_PARAM_TYPE *lpt) { - uint flags= RTFC_WAIT_OTHER_THREAD_FLAG | RTFC_CHECK_KILLED_FLAG; - DBUG_ENTER("abort_and_upgrade_locks"); + DBUG_ENTER("abort_and_upgrade_lock"); - lpt->old_lock_type= lpt->table->reginfo.lock_type; - mysql_mutex_lock(&LOCK_open); - /* If MERGE child, forward lock handling to parent. */ - mysql_lock_abort(lpt->thd, lpt->table->parent ? lpt->table->parent : - lpt->table, TRUE); - (void) remove_table_from_cache(lpt->thd, lpt->db, lpt->table_name, flags); - mysql_mutex_unlock(&LOCK_open); + if (wait_while_table_is_used(lpt->thd, lpt->table, HA_EXTRA_FORCE_REOPEN)) + DBUG_RETURN(1); DBUG_RETURN(0); } /* - SYNOPSIS - close_open_tables_and_downgrade() - RESULT VALUES - NONE - DESCRIPTION - We need to ensure that any thread that has managed to open the table - but not yet encountered our lock on the table is also thrown out to - ensure that no threads see our frm changes premature to the final - version. The intermediate versions are only meant for use after a - crash and later REPAIR TABLE. - We also downgrade locks after the upgrade to WRITE_ONLY -*/ + Tells if two (or more) tables have auto_increment columns and we want to + lock those tables with a write lock. -/* purecov: begin deadcode */ -void close_open_tables_and_downgrade(ALTER_PARTITION_PARAM_TYPE *lpt) -{ - mysql_mutex_lock(&LOCK_open); - remove_table_from_cache(lpt->thd, lpt->db, lpt->table_name, - RTFC_WAIT_OTHER_THREAD_FLAG); - mysql_mutex_unlock(&LOCK_open); - /* If MERGE child, forward lock handling to parent. */ - mysql_lock_downgrade_write(lpt->thd, lpt->table->parent ? lpt->table->parent : - lpt->table, lpt->old_lock_type); -} -/* purecov: end */ - - -/* SYNOPSIS - mysql_wait_completed_table() - lpt Parameter passing struct - my_table My table object - All parameters passed through the ALTER_PARTITION_PARAM object - RETURN VALUES - TRUE Failure - FALSE Success - DESCRIPTION - We have changed the frm file and now we want to wait for all users of - the old frm to complete before proceeding to ensure that no one - remains that uses the old frm definition. - Start by ensuring that all users of the table will be removed from cache - once they are done. Then abort all that have stumbled on locks and - haven't been started yet. - - thd Thread object - table Table object - db Database name - table_name Table name -*/ - -void mysql_wait_completed_table(ALTER_PARTITION_PARAM_TYPE *lpt, TABLE *my_table) -{ - char key[MAX_DBKEY_LENGTH]; - uint key_length; - TABLE *table; - DBUG_ENTER("mysql_wait_completed_table"); - - key_length=(uint) (strmov(strmov(key,lpt->db)+1,lpt->table_name)-key)+1; - mysql_mutex_lock(&LOCK_open); - HASH_SEARCH_STATE state; - for (table= (TABLE*) my_hash_first(&open_cache,(uchar*) key,key_length, - &state) ; - table; - table= (TABLE*) my_hash_next(&open_cache,(uchar*) key,key_length, - &state)) - { - THD *in_use= table->in_use; - table->s->version= 0L; - if (!in_use) - { - relink_unused(table); - } - else - { - /* Kill delayed insert threads */ - if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) && - ! in_use->killed) - { - in_use->killed= THD::KILL_CONNECTION; - mysql_mutex_lock(&in_use->mysys_var->mutex); - if (in_use->mysys_var->current_cond) - { - mysql_mutex_lock(in_use->mysys_var->current_mutex); - mysql_cond_broadcast(in_use->mysys_var->current_cond); - mysql_mutex_unlock(in_use->mysys_var->current_mutex); - } - mysql_mutex_unlock(&in_use->mysys_var->mutex); - } - /* - Now we must abort all tables locks used by this thread - as the thread may be waiting to get a lock for another table. - Note that we need to hold LOCK_open while going through the - list. So that the other thread cannot change it. The other - thread must also hold LOCK_open whenever changing the - open_tables list. Aborting the MERGE lock after a child was - closed and before the parent is closed would be fatal. - */ - for (TABLE *thd_table= in_use->open_tables; - thd_table ; - thd_table= thd_table->next) - { - /* Do not handle locks of MERGE children. */ - if (thd_table->db_stat && !thd_table->parent) // If table is open - mysql_lock_abort_for_thread(lpt->thd, thd_table); - } - } - } - /* - We start by removing all unused objects from the cache and marking - those in use for removal after completion. Now we also need to abort - all that are locked and are not progressing due to being locked - by our lock. We don't upgrade our lock here. - If MERGE child, forward lock handling to parent. - */ - mysql_lock_abort(lpt->thd, my_table->parent ? my_table->parent : my_table, - FALSE); - mysql_mutex_unlock(&LOCK_open); - DBUG_VOID_RETURN; -} - - -/* - Check if one (or more) write tables have auto_increment columns. - - @param[in] tables Table list - - @retval 0 if at least one write tables has an auto_increment column - @retval 1 otherwise + has_two_write_locked_tables_with_auto_increment + tables Table list NOTES: Call this function only when you have established the list of all tables @@ -8932,40 +8924,38 @@ has_write_table_with_auto_increment(TABLE_LIST *tables) bool open_system_tables_for_read(THD *thd, TABLE_LIST *table_list, - Open_tables_state *backup) + Open_tables_backup *backup) { + Query_tables_list query_tables_list_backup; + LEX *lex= thd->lex; + DBUG_ENTER("open_system_tables_for_read"); + /* + Besides using new Open_tables_state for opening system tables, + we also have to backup and reset/and then restore part of LEX + which is accessed by open_tables() in order to determine if + prelocking is needed and what tables should be added for it. + close_system_tables() doesn't require such treatment. + */ + lex->reset_n_backup_query_tables_list(&query_tables_list_backup); thd->reset_n_backup_open_tables_state(backup); - uint count= 0; - bool not_used; - for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global) + if (open_and_lock_tables_derived(thd, table_list, FALSE, + MYSQL_LOCK_IGNORE_FLUSH)) { - TABLE *table= open_table(thd, tables, thd->mem_root, ¬_used, - MYSQL_LOCK_IGNORE_FLUSH); - if (!table) - goto error; - - DBUG_ASSERT(table->s->table_category == TABLE_CATEGORY_SYSTEM); - - table->use_all_columns(); - table->reginfo.lock_type= tables->lock_type; - tables->table= table; - count++; + lex->restore_backup_query_tables_list(&query_tables_list_backup); + goto error; } + for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global) { - TABLE **list= (TABLE**) thd->alloc(sizeof(TABLE*) * count); - TABLE **ptr= list; - for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global) - *(ptr++)= tables->table; - - thd->lock= mysql_lock_tables(thd, list, count, - MYSQL_LOCK_IGNORE_FLUSH, ¬_used); + DBUG_ASSERT(tables->table->s->table_category == TABLE_CATEGORY_SYSTEM); + tables->table->use_all_columns(); } - if (thd->lock) - DBUG_RETURN(FALSE); + lex->restore_backup_query_tables_list(&query_tables_list_backup); + + DBUG_RETURN(FALSE); error: close_system_tables(thd, backup); @@ -8980,13 +8970,13 @@ error: SYNOPSIS close_system_tables() thd Thread context - backup Pointer to Open_tables_state instance which holds + backup Pointer to Open_tables_backup instance which holds information about tables which were open before we decided to access system tables. */ void -close_system_tables(THD *thd, Open_tables_state *backup) +close_system_tables(THD *thd, Open_tables_backup *backup) { close_thread_tables(thd); thd->restore_backup_open_tables_state(backup); @@ -9036,7 +9026,7 @@ open_system_table_for_update(THD *thd, TABLE_LIST *one_table) @param backup [out] Temporary storage used to save the thread context */ TABLE * -open_log_table(THD *thd, TABLE_LIST *one_table, Open_tables_state *backup) +open_log_table(THD *thd, TABLE_LIST *one_table, Open_tables_backup *backup) { uint flags= ( MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK | MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY | @@ -9085,49 +9075,9 @@ open_log_table(THD *thd, TABLE_LIST *one_table, Open_tables_state *backup) @param thd The current thread @param backup [in] the context to restore. */ -void close_log_table(THD *thd, Open_tables_state *backup) +void close_log_table(THD *thd, Open_tables_backup *backup) { - bool found_old_table; - - /* - If open_log_table() fails, - this function should not be called. - */ - DBUG_ASSERT(thd->lock != NULL); - - /* - Note: - We do not create explicitly a separate transaction for the - performance table I/O, but borrow the current transaction. - lock + unlock will autocommit the change done in the - performance schema table: this is the expected result. - The current transaction should not be affected by this code. - TODO: Note that if a transactional engine is used for log tables, - this code will need to be revised, as a separate transaction - might be needed. - */ - mysql_unlock_tables(thd, thd->lock); - thd->lock= 0; - - mysql_mutex_lock(&LOCK_open); - - found_old_table= false; - /* - Note that we need to hold LOCK_open while changing the - open_tables list. Another thread may work on it. - (See: remove_table_from_cache(), mysql_wait_completed_table()) - Closing a MERGE child before the parent would be fatal if the - other thread tries to abort the MERGE lock in between. - */ - while (thd->open_tables) - found_old_table|= close_thread_table(thd, &thd->open_tables); - - if (found_old_table) - broadcast_refresh(); - - mysql_mutex_unlock(&LOCK_open); - - thd->restore_backup_open_tables_state(backup); + close_system_tables(thd, backup); } /** diff --git a/sql/sql_binlog.cc b/sql/sql_binlog.cc index 58c309ef57b..31d4430cbe6 100644 --- a/sql/sql_binlog.cc +++ b/sql/sql_binlog.cc @@ -241,7 +241,7 @@ void mysql_client_binlog_statement(THD* thd) my_ok(thd); end: - rli->clear_tables_to_lock(); + rli->slave_close_thread_tables(thd); my_free(buf, MYF(MY_ALLOW_ZERO_PTR)); DBUG_VOID_RETURN; } diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index 0fd8d6e9b0f..f812ef862b0 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -1146,7 +1146,7 @@ void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used) See also a note on double-check locking usage above. */ - if (thd->locked_tables || query_cache_size == 0) + if (thd->locked_tables_mode || query_cache_size == 0) DBUG_VOID_RETURN; uint8 tables_type= 0; @@ -1380,7 +1380,7 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length) See also a note on double-check locking usage above. */ - if (is_disabled() || thd->locked_tables || + if (is_disabled() || thd->locked_tables_mode || thd->variables.query_cache_type == 0 || query_cache_size == 0) goto err; @@ -1535,7 +1535,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", } DBUG_PRINT("qcache", ("Query have result 0x%lx", (ulong) query)); - if ((thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) && + if (thd->in_multi_stmt_transaction() && (query->tables_type() & HA_CACHE_TBL_TRANSACT)) { DBUG_PRINT("qcache", @@ -1692,8 +1692,7 @@ void Query_cache::invalidate(THD *thd, TABLE_LIST *tables_used, if (is_disabled()) DBUG_VOID_RETURN; - using_transactions= using_transactions && - (thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); + using_transactions= using_transactions && thd->in_multi_stmt_transaction(); for (; tables_used; tables_used= tables_used->next_local) { DBUG_ASSERT(!using_transactions || tables_used->table!=0); @@ -1777,8 +1776,7 @@ void Query_cache::invalidate(THD *thd, TABLE *table, if (is_disabled()) DBUG_VOID_RETURN; - using_transactions= using_transactions && - (thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); + using_transactions= using_transactions && thd->in_multi_stmt_transaction(); if (using_transactions && (table->file->table_cache_type() == HA_CACHE_TBL_TRANSACT)) thd->add_changed_table(table); @@ -1796,8 +1794,7 @@ void Query_cache::invalidate(THD *thd, const char *key, uint32 key_length, if (is_disabled()) DBUG_VOID_RETURN; - using_transactions= using_transactions && - (thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); + using_transactions= using_transactions && thd->in_multi_stmt_transaction(); if (using_transactions) // used for innodb => has_transactions() is TRUE thd->add_changed_table(key, key_length); else @@ -3569,7 +3566,7 @@ Query_cache::is_cacheable(THD *thd, size_t query_len, const char *query, tables_type))) DBUG_RETURN(0); - if ((thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) && + if (thd->in_multi_stmt_transaction() && ((*tables_type)&HA_CACHE_TBL_TRANSACT)) { DBUG_PRINT("qcache", ("not in autocommin mode")); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 2a82b0058da..aaacce19b53 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -42,6 +42,7 @@ #include "sp_rcontext.h" #include "sp_cache.h" +#include "transaction.h" #include "debug_sync.h" /* @@ -202,12 +203,6 @@ bool foreign_key_prefix(Key *a, Key *b) ** Thread specific functions ****************************************************************************/ -Open_tables_state::Open_tables_state(ulong version_arg) - :version(version_arg), state_flags(0U) -{ - reset_open_tables_state(); -} - /* The following functions form part of the C plugin API */ @@ -252,13 +247,16 @@ int thd_tablespace_op(const THD *thd) extern "C" -const char *set_thd_proc_info(THD *thd, const char *info, - const char *calling_function, - const char *calling_file, +const char *set_thd_proc_info(THD *thd, const char *info, + const char *calling_function, + const char *calling_file, const unsigned int calling_line) { + if (!thd) + thd= current_thd; + const char *old_info= thd->proc_info; - DBUG_PRINT("proc_info", ("%s:%d %s", calling_file, calling_line, + DBUG_PRINT("proc_info", ("%s:%d %s", calling_file, calling_line, (info != NULL) ? info : "(null)")); #if defined(ENABLED_PROFILING) thd->profiling.status_change(info, calling_function, calling_file, calling_line); @@ -445,7 +443,7 @@ bool Drop_table_error_handler::handle_condition(THD *thd, THD::THD() :Statement(&main_lex, &main_mem_root, CONVENTIONAL_EXECUTION, /* statement id */ 0), - Open_tables_state(refresh_version), rli_fake(0), + rli_fake(0), lock_id(&main_lock_id), user_time(0), in_sub_stmt(0), sql_log_bin_toplevel(false), @@ -459,7 +457,6 @@ THD::THD() examined_row_count(0), warning_info(&main_warning_info), stmt_da(&main_da), - global_read_lock(0), is_fatal_error(0), transaction_rollback_request(0), is_fatal_sub_stmt_error(0), @@ -477,6 +474,7 @@ THD::THD() { ulong tmp; + mdl_context.init(this); /* Pass nominal parameters to init_alloc_root only to ensure that the destructor works OK in case of an error. The main_mem_root @@ -488,7 +486,7 @@ THD::THD() catalog= (char*)"std"; // the only catalog we have for now main_security_ctx.init(); security_ctx= &main_security_ctx; - some_tables_deleted=no_errors=password= 0; + no_errors=password= 0; query_start_used= 0; count_cuted_fields= CHECK_FIELD_IGNORE; killed= NOT_KILLED; @@ -546,6 +544,9 @@ THD::THD() command=COM_CONNECT; *scramble= '\0'; + /* Call to init() below requires fully initialized Open_tables_state. */ + init_open_tables_state(this, refresh_version); + init(); #if defined(ENABLED_PROFILING) profiling.set_thd(this); @@ -982,29 +983,41 @@ void THD::cleanup(void) } #endif { - ha_rollback(this); + transaction.xid_state.xa_state= XA_NOTR; + trans_rollback(this); xid_cache_delete(&transaction.xid_state); } - if (locked_tables) - { - lock=locked_tables; locked_tables=0; - close_thread_tables(this); - } + + locked_tables_list.unlock_locked_tables(this); + mysql_ha_cleanup(this); + + DBUG_ASSERT(open_tables == NULL); + /* + If the thread was in the middle of an ongoing transaction (rolled + back a few lines above) or under LOCK TABLES (unlocked the tables + and left the mode a few lines above), there will be outstanding + metadata locks. Release them. + */ + mdl_context.release_transactional_locks(); + + /* Release the global read lock, if acquired. */ + if (global_read_lock.is_acquired()) + global_read_lock.unlock_global_read_lock(this); + + /* All metadata locks must have been released by now. */ + DBUG_ASSERT(!mdl_context.has_locks()); #if defined(ENABLED_DEBUG_SYNC) /* End the Debug Sync Facility. See debug_sync.cc. */ debug_sync_end_thread(this); #endif /* defined(ENABLED_DEBUG_SYNC) */ - mysql_ha_cleanup(this); delete_dynamic(&user_var_events); my_hash_free(&user_vars); close_temporary_tables(this); sp_cache_clear(&sp_proc_cache); sp_cache_clear(&sp_func_cache); - if (global_read_lock) - unlock_global_read_lock(this); if (ull) { mysql_mutex_lock(&LOCK_user_locks); @@ -1040,6 +1053,7 @@ THD::~THD() if (!cleanup_done) cleanup(); + mdl_context.destroy(); ha_close_connection(this); plugin_thdvar_cleanup(this); @@ -1405,8 +1419,7 @@ void THD::add_changed_table(TABLE *table) { DBUG_ENTER("THD::add_changed_table(table)"); - DBUG_ASSERT(variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); - DBUG_ASSERT(table->file->has_transactions()); + DBUG_ASSERT(in_multi_stmt_transaction() && table->file->has_transactions()); add_changed_table(table->s->table_cache_key.str, (long) table->s->table_cache_key.length); DBUG_VOID_RETURN; @@ -1729,7 +1742,7 @@ bool select_send::send_eof() ha_release_temporary_latches(thd); /* Unlock tables before sending packet to gain some speed */ - if (thd->lock) + if (thd->lock && ! thd->locked_tables_mode) { mysql_unlock_tables(thd, thd->lock); thd->lock=0; @@ -3007,28 +3020,31 @@ bool Security_context::user_matches(Security_context *them) access to mysql.proc table to find definitions of stored routines. ****************************************************************************/ -void THD::reset_n_backup_open_tables_state(Open_tables_state *backup) +void THD::reset_n_backup_open_tables_state(Open_tables_backup *backup) { DBUG_ENTER("reset_n_backup_open_tables_state"); backup->set_open_tables_state(this); - reset_open_tables_state(); + backup->mdl_system_tables_svp= mdl_context.mdl_savepoint(); + reset_open_tables_state(this); state_flags|= Open_tables_state::BACKUPS_AVAIL; DBUG_VOID_RETURN; } -void THD::restore_backup_open_tables_state(Open_tables_state *backup) +void THD::restore_backup_open_tables_state(Open_tables_backup *backup) { DBUG_ENTER("restore_backup_open_tables_state"); + mdl_context.rollback_to_savepoint(backup->mdl_system_tables_svp); /* Before we will throw away current open tables state we want to be sure that it was properly cleaned up. */ DBUG_ASSERT(open_tables == 0 && temporary_tables == 0 && - handler_tables == 0 && derived_tables == 0 && - lock == 0 && locked_tables == 0 && - prelocked_mode == NON_PRELOCKED && + derived_tables == 0 && + lock == 0 && + locked_tables_mode == LTM_NONE && m_reprepare_observer == NULL); + set_open_tables_state(backup); DBUG_VOID_RETURN; } @@ -3944,7 +3960,7 @@ int THD::binlog_query(THD::enum_binlog_query_type qtype, char const *query_arg, If we are in prelocked mode, the flushing will be done inside the top-most close_thread_tables(). */ - if (this->prelocked_mode == NON_PRELOCKED) + if (this->locked_tables_mode <= LTM_LOCK_TABLES) if (int error= binlog_flush_pending_rows_event(TRUE)) DBUG_RETURN(error); diff --git a/sql/sql_class.h b/sql/sql_class.h index b3cc1fc68c0..a6cafb81d22 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -25,6 +25,7 @@ #include "log.h" #include "rpl_tblmap.h" +#include "mdl.h" class Reprepare_observer; @@ -37,6 +38,7 @@ class sp_rcontext; class sp_cache; class Parser_state; class Rows_log_event; +class Sroutine_hash_entry; enum enum_enable_or_disable { LEAVE_AS_IS, ENABLE, DISABLE }; enum enum_ha_read_modes { RFIRST, RNEXT, RPREV, RLAST, RKEY, RNEXT_SAME }; @@ -761,6 +763,8 @@ struct st_savepoint { char *name; uint length; Ha_trx_info *ha_list; + /** Last acquired lock before this savepoint was set. */ + MDL_ticket *mdl_savepoint; }; enum xa_states {XA_NOTR=0, XA_ACTIVE, XA_IDLE, XA_PREPARED, XA_ROLLBACK_ONLY}; @@ -844,12 +848,17 @@ typedef I_List<Item_change_record> Item_change_list; /** - Type of prelocked mode. - See comment for THD::prelocked_mode for complete description. + Type of locked tables mode. + See comment for THD::locked_tables_mode for complete description. */ -enum prelocked_mode_type {NON_PRELOCKED= 0, PRELOCKED= 1, - PRELOCKED_UNDER_LOCK_TABLES= 2}; +enum enum_locked_tables_mode +{ + LTM_NONE= 0, + LTM_LOCK_TABLES, + LTM_PRELOCKED, + LTM_PRELOCKED_UNDER_LOCK_TABLES +}; /** @@ -888,11 +897,6 @@ public: XXX Why are internal temporary tables added to this list? */ TABLE *temporary_tables; - /** - List of tables that were opened with HANDLER OPEN and are - still in use by this thread. - */ - TABLE *handler_tables; TABLE *derived_tables; /* During a MySQL session, one can lock tables in two modes: automatic @@ -902,19 +906,13 @@ public: statement ends. Manual mode comes into play when a user issues a 'LOCK TABLES' statement. In this mode the user can only use the locked tables. - Trying to use any other tables will give an error. The locked tables are - stored in 'locked_tables' member. Manual locking is described in + Trying to use any other tables will give an error. + The locked tables are also stored in this member, however, + thd->locked_tables_mode is turned on. Manual locking is described in the 'LOCK_TABLES' chapter of the MySQL manual. See also lock_tables() for details. */ MYSQL_LOCK *lock; - /* - Tables that were locked with explicit or implicit LOCK TABLES. - (Implicit LOCK TABLES happens when we are prelocking tables for - execution of statement which uses stored routines. See description - THD::prelocked_mode for more info.) - */ - MYSQL_LOCK *locked_tables; /* CREATE-SELECT keeps an extra lock for the table being @@ -924,29 +922,34 @@ public: MYSQL_LOCK *extra_lock; /* - prelocked_mode_type enum and prelocked_mode member are used for - indicating whenever "prelocked mode" is on, and what type of - "prelocked mode" is it. - - Prelocked mode is used for execution of queries which explicitly - or implicitly (via views or triggers) use functions, thus may need - some additional tables (mentioned in query table list) for their - execution. - - First open_tables() call for such query will analyse all functions - used by it and add all additional tables to table its list. It will - also mark this query as requiring prelocking. After that lock_tables() - will issue implicit LOCK TABLES for the whole table list and change - thd::prelocked_mode to non-0. All queries called in functions invoked - by the main query will use prelocked tables. Non-0 prelocked_mode - will also surpress mentioned analysys in those queries thus saving - cycles. Prelocked mode will be turned off once close_thread_tables() - for the main query will be called. - - Note: Since not all "tables" present in table list are really locked - thd::prelocked_mode does not imply thd::locked_tables. - */ - prelocked_mode_type prelocked_mode; + Enum enum_locked_tables_mode and locked_tables_mode member are + used to indicate whether the so-called "locked tables mode" is on, + and what kind of mode is active. + + Locked tables mode is used when it's necessary to open and + lock many tables at once, for usage across multiple + (sub-)statements. + This may be necessary either for queries that use stored functions + and triggers, in which case the statements inside functions and + triggers may be executed many times, or for implementation of + LOCK TABLES, in which case the opened tables are reused by all + subsequent statements until a call to UNLOCK TABLES. + + The kind of locked tables mode employed for stored functions and + triggers is also called "prelocked mode". + In this mode, first open_tables() call to open the tables used + in a statement analyses all functions used by the statement + and adds all indirectly used tables to the list of tables to + open and lock. + It also marks the parse tree of the statement as requiring + prelocking. After that, lock_tables() locks the entire list + of tables and changes THD::locked_tables_modeto LTM_PRELOCKED. + All statements executed inside functions or triggers + use the prelocked tables, instead of opening their own ones. + Prelocked mode is turned off automatically once close_thread_tables() + of the main statement is called. + */ + enum enum_locked_tables_mode locked_tables_mode; ulong version; uint current_tablenr; @@ -958,30 +961,58 @@ public: Flags with information about the open tables state. */ uint state_flags; - - /* - This constructor serves for creation of Open_tables_state instances - which are used as backup storage. + /** + This constructor initializes Open_tables_state instance which can only + be used as backup storage. To prepare Open_tables_state instance for + operations which open/lock/close tables (e.g. open_table()) one has to + call init_open_tables_state(). */ Open_tables_state() : state_flags(0U) { } - Open_tables_state(ulong version_arg); + /** + Prepare Open_tables_state instance for operations dealing with tables. + */ + void init_open_tables_state(THD *thd, ulong version_arg) + { + reset_open_tables_state(thd); + version= version_arg; + } void set_open_tables_state(Open_tables_state *state) { *this= *state; } - void reset_open_tables_state() + void reset_open_tables_state(THD *thd) { - open_tables= temporary_tables= handler_tables= derived_tables= 0; - extra_lock= lock= locked_tables= 0; - prelocked_mode= NON_PRELOCKED; + open_tables= temporary_tables= derived_tables= 0; + extra_lock= lock= 0; + locked_tables_mode= LTM_NONE; state_flags= 0U; m_reprepare_observer= NULL; } }; + +/** + Storage for backup of Open_tables_state. Must + be used only to open system tables (TABLE_CATEGORY_SYSTEM + and TABLE_CATEGORY_LOG). +*/ + +class Open_tables_backup: public Open_tables_state +{ +public: + /** + When we backup the open tables state to open a system + table or tables, points at the last metadata lock + acquired before the backup. Is used to release + metadata locks on system tables after they are + no longer used. + */ + MDL_ticket *mdl_system_tables_svp; +}; + /** @class Sub_statement_state @brief Used to save context when executing a function or trigger @@ -1144,6 +1175,217 @@ private: /** + An abstract class for a strategy specifying how the prelocking + algorithm should extend the prelocking set while processing + already existing elements in the set. +*/ + +class Prelocking_strategy +{ +public: + virtual ~Prelocking_strategy() { } + + virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx, + Sroutine_hash_entry *rt, sp_head *sp, + bool *need_prelocking) = 0; + virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking) = 0; + virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking)= 0; +}; + + +/** + A Strategy for prelocking algorithm suitable for DML statements. + + Ensures that all tables used by all statement's SF/SP/triggers and + required for foreign key checks are prelocked and SF/SPs used are + cached. +*/ + +class DML_prelocking_strategy : public Prelocking_strategy +{ +public: + virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx, + Sroutine_hash_entry *rt, sp_head *sp, + bool *need_prelocking); + virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking); + virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking); +}; + + +/** + A strategy for prelocking algorithm to be used for LOCK TABLES + statement. +*/ + +class Lock_tables_prelocking_strategy : public DML_prelocking_strategy +{ + virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking); +}; + + +/** + Strategy for prelocking algorithm to be used for ALTER TABLE statements. + + Unlike DML or LOCK TABLES strategy, it doesn't + prelock triggers, views or stored routines, since they are not + used during ALTER. +*/ + +class Alter_table_prelocking_strategy : public Prelocking_strategy +{ +public: + + Alter_table_prelocking_strategy(Alter_info *alter_info) + : m_alter_info(alter_info) + {} + + virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx, + Sroutine_hash_entry *rt, sp_head *sp, + bool *need_prelocking); + virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking); + virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list, bool *need_prelocking); + +private: + Alter_info *m_alter_info; +}; + + +/** + A context of open_tables() function, used to recover + from a failed open_table() or open_routine() attempt. + + Implemented in sql_base.cc. +*/ + +class Open_table_context +{ +public: + enum enum_open_table_action + { + OT_NO_ACTION= 0, + OT_WAIT_MDL_LOCK, + OT_WAIT_TDC, + OT_DISCOVER, + OT_REPAIR + }; + Open_table_context(THD *thd); + + bool recover_from_failed_open(THD *thd, MDL_request *mdl_request, + TABLE_LIST *table); + bool request_backoff_action(enum_open_table_action action_arg); + + void add_request(MDL_request *request) + { m_mdl_requests.push_front(request); } + + bool can_recover_from_failed_open() const + { return m_action != OT_NO_ACTION; } + + /** + When doing a back-off, we close all tables acquired by this + statement. Return an MDL savepoint taken at the beginning of + the statement, so that we can rollback to it before waiting on + locks. + */ + MDL_ticket *start_of_statement_svp() const + { + return m_start_of_statement_svp; + } + + MDL_request *get_global_mdl_request(THD *thd); + +private: + /** List of requests for all locks taken so far. Used for waiting on locks. */ + MDL_request_list m_mdl_requests; + /** Back off action. */ + enum enum_open_table_action m_action; + MDL_ticket *m_start_of_statement_svp; + /** + Whether we had any locks when this context was created. + If we did, they are from the previous statement of a transaction, + and we can't safely do back-off (and release them). + */ + bool m_has_locks; + /** + Request object for global intention exclusive lock which is acquired during + opening tables for statements which take upgradable shared metadata locks. + */ + MDL_request *m_global_mdl_request; +}; + + +/** + Tables that were locked with LOCK TABLES statement. + + Encapsulates a list of TABLE_LIST instances for tables + locked by LOCK TABLES statement, memory root for metadata locks, + and, generally, the context of LOCK TABLES statement. + + In LOCK TABLES mode, the locked tables are kept open between + statements. + Therefore, we can't allocate metadata locks on execution memory + root -- as well as tables, the locks need to stay around till + UNLOCK TABLES is called. + The locks are allocated in the memory root encapsulated in this + class. + + Some SQL commands, like FLUSH TABLE or ALTER TABLE, demand that + the tables they operate on are closed, at least temporarily. + This class encapsulates a list of TABLE_LIST instances, one + for each base table from LOCK TABLES list, + which helps conveniently close the TABLEs when it's necessary + and later reopen them. + + Implemented in sql_base.cc +*/ + +class Locked_tables_list +{ +private: + MEM_ROOT m_locked_tables_root; + TABLE_LIST *m_locked_tables; + TABLE_LIST **m_locked_tables_last; + /** An auxiliary array used only in reopen_tables(). */ + TABLE **m_reopen_array; + /** + Count the number of tables in m_locked_tables list. We can't + rely on thd->lock->table_count because it excludes + non-transactional temporary tables. We need to know + an exact number of TABLE objects. + */ + size_t m_locked_tables_count; +public: + Locked_tables_list() + :m_locked_tables(NULL), + m_locked_tables_last(&m_locked_tables), + m_reopen_array(NULL), + m_locked_tables_count(0) + { + init_sql_alloc(&m_locked_tables_root, MEM_ROOT_BLOCK_SIZE, 0); + } + void unlock_locked_tables(THD *thd); + ~Locked_tables_list() + { + unlock_locked_tables(0); + } + bool init_locked_tables(THD *thd); + TABLE_LIST *locked_tables() { return m_locked_tables; } + void unlink_from_list(THD *thd, TABLE_LIST *table_list, + bool remove_from_locked_tables); + void unlink_all_closed_tables(THD *thd, + MYSQL_LOCK *lock, + size_t reopen_count); + bool reopen_tables(THD *thd); +}; + + +/** Storage engine specific thread local data. */ @@ -1169,6 +1411,45 @@ struct Ha_data Ha_data() :ha_ptr(NULL) {} }; +/** + An instance of the global read lock in a connection. + Implemented in lock.cc. +*/ + +class Global_read_lock +{ +public: + enum enum_grl_state + { + GRL_NONE, + GRL_ACQUIRED, + GRL_ACQUIRED_AND_BLOCKS_COMMIT + }; + + Global_read_lock() + :m_protection_count(0), m_state(GRL_NONE), m_mdl_global_shared_lock(NULL) + {} + + bool lock_global_read_lock(THD *thd); + void unlock_global_read_lock(THD *thd); + bool wait_if_global_read_lock(THD *thd, bool abort_on_refresh, + bool is_not_commit); + void start_waiting_global_read_lock(THD *thd); + bool make_global_read_lock_block_commit(THD *thd); + bool is_acquired() const { return m_state != GRL_NONE; } + bool has_protection() const { return m_protection_count > 0; } + MDL_ticket *global_shared_lock() const { return m_mdl_global_shared_lock; } +private: + uint m_protection_count; // GRL protection count + /** + In order to acquire the global read lock, the connection must + acquire a global shared metadata lock, to prohibit all DDL. + */ + enum_grl_state m_state; + MDL_ticket *m_mdl_global_shared_lock; +}; + + extern "C" void my_message_sql(uint error, const char *str, myf MyFlags); /** @@ -1181,6 +1462,8 @@ class THD :public Statement, public Open_tables_state { public: + MDL_context mdl_context; + /* Used to execute base64 coded binlog events in MySQL server */ Relay_log_info* rli_fake; @@ -1424,6 +1707,7 @@ public: init_sql_alloc(&mem_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0); } } transaction; + Global_read_lock global_read_lock; Field *dup_field; #ifndef __WIN__ sigset_t signals; @@ -1643,7 +1927,7 @@ public: ulong rand_saved_seed1, rand_saved_seed2; pthread_t real_id; /* For debugging */ my_thread_id thread_id; - uint tmp_table, global_read_lock; + uint tmp_table; uint server_status,open_options; enum enum_thread_type system_thread; uint select_number; //number of select (used for EXPLAIN) @@ -1670,7 +1954,6 @@ public: bool slave_thread, one_shot_set; /* tells if current statement should binlog row-based(1) or stmt-based(0) */ bool current_stmt_binlog_row_based; - bool some_tables_deleted; bool last_cuted_field; bool no_errors, password; /** @@ -1780,6 +2063,8 @@ public: */ Parser_state *m_parser_state; + Locked_tables_list locked_tables_list; + #ifdef WITH_PARTITION_STORAGE_ENGINE partition_info *work_part_info; #endif @@ -1788,7 +2073,6 @@ public: /* Debug Sync facility. See debug_sync.cc. */ struct st_debug_sync_control *debug_sync_control; #endif /* defined(ENABLED_DEBUG_SYNC) */ - THD(); ~THD(); @@ -1908,6 +2192,21 @@ public: { return server_status & SERVER_STATUS_IN_TRANS; } + /** + Returns TRUE if session is in a multi-statement transaction mode. + + OPTION_NOT_AUTOCOMMIT: When autocommit is off, a multi-statement + transaction is implicitly started on the first statement after a + previous transaction has been ended. + + OPTION_BEGIN: Regardless of the autocommit status, a multi-statement + transaction can be explicitly started with the statements "START + TRANSACTION", "BEGIN [WORK]", "[COMMIT | ROLLBACK] AND CHAIN", etc. + */ + inline bool in_multi_stmt_transaction() + { + return variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN); + } inline bool fill_derived_tables() { return !stmt_arena->is_stmt_prepare() && !lex->only_view_structure(); @@ -1969,7 +2268,7 @@ public: */ inline void fatal_error() { - DBUG_ASSERT(main_da.is_error()); + DBUG_ASSERT(stmt_da->is_error() || killed); is_fatal_error= 1; DBUG_PRINT("error",("Fatal error set")); } @@ -2045,8 +2344,8 @@ public: void set_status_var_init(); bool is_context_analysis_only() { return stmt_arena->is_stmt_prepare() || lex->view_prepare_mode; } - void reset_n_backup_open_tables_state(Open_tables_state *backup); - void restore_backup_open_tables_state(Open_tables_state *backup); + void reset_n_backup_open_tables_state(Open_tables_backup *backup); + void restore_backup_open_tables_state(Open_tables_backup *backup); void reset_sub_statement_state(Sub_statement_state *backup, uint new_state); void restore_sub_statement_state(Sub_statement_state *backup); void set_n_backup_active_arena(Query_arena *set, Query_arena *backup); @@ -2301,6 +2600,20 @@ public: void set_query_and_id(char *query_arg, uint32 query_length_arg, query_id_t new_query_id); void set_query_id(query_id_t new_query_id); + void enter_locked_tables_mode(enum_locked_tables_mode mode_arg) + { + DBUG_ASSERT(locked_tables_mode == LTM_NONE); + DBUG_ASSERT(handler_tables_hash.records == 0); + + mdl_context.set_trans_sentinel(); + locked_tables_mode= mode_arg; + } + void leave_locked_tables_mode() + { + locked_tables_mode= LTM_NONE; + /* Make sure we don't release the global read lock when leaving LTM. */ + mdl_context.reset_trans_sentinel(global_read_lock.global_shared_lock()); + } private: /** The current internal error handler for this thread, or NULL. */ Internal_error_handler *m_internal_handler; @@ -2579,7 +2892,7 @@ public: {} int prepare(List<Item> &list, SELECT_LEX_UNIT *u); - int binlog_show_create_table(TABLE **tables, uint count); + void binlog_show_create_table(TABLE **tables, uint count); void store_values(List<Item> &values); void send_error(uint errcode,const char *err); bool send_eof(); @@ -3021,6 +3334,36 @@ public: joins are currently prohibited in these statements. */ #define CF_REEXECUTION_FRAGILE (1U << 5) +/** + Implicitly commit before the SQL statement is executed. + + Statements marked with this flag will cause any active + transaction to end (commit) before proceeding with the + command execution. + + This flag should be set for statements that probably can't + be rolled back or that do not expect any previously metadata + locked tables. +*/ +#define CF_IMPLICT_COMMIT_BEGIN (1U << 6) +/** + Implicitly commit after the SQL statement. + + Statements marked with this flag are automatically committed + at the end of the statement. + + This flag should be set for statements that will implicitly + open and take metadata locks on system tables that should not + be carried for the whole duration of a active transaction. +*/ +#define CF_IMPLICIT_COMMIT_END (1U << 7) +/** + CF_IMPLICT_COMMIT_BEGIN and CF_IMPLICIT_COMMIT_END are used + to ensure that the active transaction is implicitly committed + before and after every DDL statement and any statement that + modifies our currently non-transactional system tables. +*/ +#define CF_AUTO_COMMIT_TRANS (CF_IMPLICT_COMMIT_BEGIN | CF_IMPLICIT_COMMIT_END) /** Diagnostic statement. @@ -3032,6 +3375,33 @@ public: */ #define CF_DIAGNOSTIC_STMT (1U << 8) +/** + SQL statements that must be protected against impending global read lock + to prevent deadlock. This deadlock could otherwise happen if the statement + starts waiting for the GRL to go away inside mysql_lock_tables while at the + same time having "old" opened tables. The thread holding the GRL can be + waiting for these "old" opened tables to be closed, causing a deadlock + (FLUSH TABLES WITH READ LOCK). + */ +#define CF_PROTECT_AGAINST_GRL (1U << 10) + +/* Bits in server_command_flags */ + +/** + Skip the increase of the global query id counter. Commonly set for + commands that are stateless (won't cause any change on the server + internal states). +*/ +#define CF_SKIP_QUERY_ID (1U << 0) + +/** + Skip the increase of the number of statements that clients have + sent to the server. Commonly used for commands that will cause + a statement to be executed but the statement might have not been + sent by the user (ie: stored procedure). +*/ +#define CF_SKIP_QUESTIONS (1U << 1) + /* Functions in sql_class.cc */ void add_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var); diff --git a/sql/sql_cursor.cc b/sql/sql_cursor.cc index 66c4460c1cd..0391ce6dc72 100644 --- a/sql/sql_cursor.cc +++ b/sql/sql_cursor.cc @@ -289,7 +289,6 @@ Sensitive_cursor::Sensitive_cursor(THD *thd, select_result *result_arg) Save THD state into cursor. @todo - - XXX: thd->locked_tables is not changed. - What problems can we have with it if cursor is open? - TODO: must be fixed because of the prelocked mode. */ @@ -322,7 +321,7 @@ Sensitive_cursor::post_open(THD *thd) lock= thd->lock; query_id= thd->query_id; free_list= thd->free_list; - change_list= thd->change_list; + thd->change_list.move_elements_to(&change_list); reset_thd(thd); /* Now we have an active cursor and can cause a deadlock */ thd->lock_info.n_cursors++; @@ -342,7 +341,6 @@ Sensitive_cursor::post_open(THD *thd) } } /* - XXX: thd->locked_tables is not changed. What problems can we have with it if cursor is open? TODO: must be fixed because of the prelocked mode. */ @@ -439,7 +437,7 @@ Sensitive_cursor::fetch(ulong num_rows) thd->open_tables= open_tables; thd->lock= lock; thd->set_query_id(query_id); - thd->change_list= change_list; + change_list.move_elements_to(&thd->change_list); /* save references to memory allocated during fetch */ thd->set_n_backup_active_arena(this, &backup_arena); @@ -461,7 +459,7 @@ Sensitive_cursor::fetch(ulong num_rows) /* Grab free_list here to correctly free it in close */ thd->restore_active_arena(this, &backup_arena); - change_list= thd->change_list; + thd->change_list.move_elements_to(&change_list); reset_thd(thd); for (info= ht_info; info->read_view; info++) @@ -508,7 +506,7 @@ Sensitive_cursor::close() info->ht= 0; } - thd->change_list= change_list; + change_list.move_elements_to(&thd->change_list); { /* XXX: Another hack: we need to set THD state as if in a fetch to be @@ -534,7 +532,6 @@ Sensitive_cursor::close() join= 0; stmt_arena= 0; free_items(); - change_list.empty(); DBUG_VOID_RETURN; } diff --git a/sql/sql_db.cc b/sql/sql_db.cc index aa124a0a004..887fdf45232 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -178,13 +178,13 @@ uchar* dboptions_get_key(my_dbopt_t *opt, size_t *length, Helper function to write a query to binlog used by mysql_rm_db() */ -static inline int write_to_binlog(THD *thd, char *query, uint q_len, - char *db, uint db_len) +static inline void write_to_binlog(THD *thd, char *query, uint q_len, + char *db, uint db_len) { Query_log_event qinfo(thd, query, q_len, 0, 0, 0); qinfo.db= db; qinfo.db_len= db_len; - return mysql_bin_log.write(&qinfo); + mysql_bin_log.write(&qinfo); } @@ -663,7 +663,7 @@ int mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info, has the global read lock and refuses the operation with ER_CANT_UPDATE_WITH_READLOCK if applicable. */ - if (wait_if_global_read_lock(thd, 0, 1)) + if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) { error= -1; goto exit2; @@ -774,18 +774,14 @@ not_silent: qinfo.db_len = strlen(db); /* These DDL methods and logging protected with LOCK_mysql_create_db */ - if (mysql_bin_log.write(&qinfo)) - { - error= -1; - goto exit; - } + mysql_bin_log.write(&qinfo); } my_ok(thd, result); } exit: mysql_mutex_unlock(&LOCK_mysql_create_db); - start_waiting_global_read_lock(thd); + thd->global_read_lock.start_waiting_global_read_lock(thd); exit2: DBUG_RETURN(error); } @@ -812,7 +808,7 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info) has the global read lock and refuses the operation with ER_CANT_UPDATE_WITH_READLOCK if applicable. */ - if ((error=wait_if_global_read_lock(thd,0,1))) + if ((error= thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))) goto exit2; mysql_mutex_lock(&LOCK_mysql_create_db); @@ -855,14 +851,13 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info) qinfo.db_len = strlen(db); /* These DDL methods and logging protected with LOCK_mysql_create_db */ - if (error= mysql_bin_log.write(&qinfo)) - goto exit; + mysql_bin_log.write(&qinfo); } my_ok(thd, result); exit: mysql_mutex_unlock(&LOCK_mysql_create_db); - start_waiting_global_read_lock(thd); + thd->global_read_lock.start_waiting_global_read_lock(thd); exit2: DBUG_RETURN(error); } @@ -907,7 +902,7 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) has the global read lock and refuses the operation with ER_CANT_UPDATE_WITH_READLOCK if applicable. */ - if (wait_if_global_read_lock(thd, 0, 1)) + if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) { error= -1; goto exit2; @@ -935,10 +930,6 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) } else { - mysql_mutex_lock(&LOCK_open); - remove_db_from_cache(db); - mysql_mutex_unlock(&LOCK_open); - Drop_table_error_handler err_handler(thd->get_internal_handler()); thd->push_internal_handler(&err_handler); @@ -1006,11 +997,7 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) qinfo.db_len = strlen(db); /* These DDL methods and logging protected with LOCK_mysql_create_db */ - if (mysql_bin_log.write(&qinfo)) - { - error= -1; - goto exit; - } + mysql_bin_log.write(&qinfo); } thd->clear_error(); thd->server_status|= SERVER_STATUS_DB_DROPPED; @@ -1038,11 +1025,7 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) if (query_pos + tbl_name_len + 1 >= query_end) { /* These DDL methods and logging protected with LOCK_mysql_create_db */ - if (write_to_binlog(thd, query, query_pos -1 - query, db, db_len)) - { - error= -1; - goto exit; - } + write_to_binlog(thd, query, query_pos -1 - query, db, db_len); query_pos= query_data_start; } @@ -1055,11 +1038,7 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) if (query_pos != query_data_start) { /* These DDL methods and logging protected with LOCK_mysql_create_db */ - if (write_to_binlog(thd, query, query_pos -1 - query, db, db_len)) - { - error= -1; - goto exit; - } + write_to_binlog(thd, query, query_pos -1 - query, db, db_len); } } @@ -1073,7 +1052,7 @@ exit: if (thd->db && !strcmp(thd->db, db) && error == 0) mysql_change_db_impl(thd, NULL, 0, thd->variables.collation_server); mysql_mutex_unlock(&LOCK_mysql_create_db); - start_waiting_global_read_lock(thd); + thd->global_read_lock.start_waiting_global_read_lock(thd); exit2: DBUG_RETURN(error); } @@ -1188,6 +1167,11 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db, (void) filename_to_tablename(file->name, table_list->table_name, MYSQL50_TABLE_NAME_PREFIX_LENGTH + strlen(file->name) + 1); + + /* To be able to correctly look up the table in the table cache. */ + if (lower_case_table_names) + my_casedn_str(files_charset_info, table_list->table_name); + table_list->alias= table_list->table_name; // If lower_case_table_names=2 table_list->internal_tmp_table= is_prefix(file->name, tmp_file_prefix); /* Link into list */ @@ -1997,8 +1981,8 @@ bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db) /* Step7: drop the old database. - remove_db_from_cache(olddb) and query_cache_invalidate(olddb) - are done inside mysql_rm_db(), no needs to execute them again. + query_cache_invalidate(olddb) is done inside mysql_rm_db(), no need + to execute them again. mysql_rm_db() also "unuses" if we drop the current database. */ error= mysql_rm_db(thd, old_db->str, 0, 1); @@ -2010,7 +1994,7 @@ bool mysql_upgrade_db(THD *thd, LEX_STRING *old_db) Query_log_event qinfo(thd, thd->query(), thd->query_length(), 0, TRUE, errcode); thd->clear_error(); - error|= mysql_bin_log.write(&qinfo); + mysql_bin_log.write(&qinfo); } /* Step9: Let's do "use newdb" if we renamed the current database */ diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 086aacb495e..bae922a7411 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -23,6 +23,7 @@ #include "sql_select.h" #include "sp_head.h" #include "sql_trigger.h" +#include "transaction.h" /** Implement DELETE SQL word. @@ -850,10 +851,9 @@ void multi_delete::abort() if (mysql_bin_log.is_open()) { int errcode= query_error_code(thd, thd->killed == THD::NOT_KILLED); - /* possible error of writing binary log is ignored deliberately */ - (void) thd->binlog_query(THD::ROW_QUERY_TYPE, - thd->query(), thd->query_length(), - transactional_tables, FALSE, errcode); + thd->binlog_query(THD::ROW_QUERY_TYPE, + thd->query(), thd->query_length(), + transactional_tables, FALSE, errcode); } thd->transaction.all.modified_non_trans_table= true; } @@ -1064,9 +1064,18 @@ static bool mysql_truncate_by_delete(THD *thd, TABLE_LIST *table_list) table_list->lock_type= TL_WRITE; mysql_init_select(thd->lex); thd->clear_current_stmt_binlog_row_based(); + /* Delete all rows from table */ error= mysql_delete(thd, table_list, NULL, NULL, HA_POS_ERROR, LL(0), TRUE); - ha_autocommit_or_rollback(thd, error); - end_trans(thd, error ? ROLLBACK : COMMIT); + /* + All effects of a TRUNCATE TABLE operation are rolled back if a row by row + deletion fails. Otherwise, operation is automatically committed at the end. + */ + if (error) + { + DBUG_ASSERT(thd->stmt_da->is_error()); + trans_rollback_stmt(thd); + trans_rollback(thd); + } thd->current_stmt_binlog_row_based= save_binlog_row_based; DBUG_RETURN(error); } @@ -1081,7 +1090,8 @@ static bool mysql_truncate_by_delete(THD *thd, TABLE_LIST *table_list) normally can't safely do this. - We don't want an ok to be sent to the end user. - We don't want to log the truncate command - - If we want to have a name lock on the table on exit without errors. + - If we want to keep exclusive metadata lock on the table (obtained by + caller) on exit without errors. */ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) @@ -1089,15 +1099,22 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) HA_CREATE_INFO create_info; char path[FN_REFLEN + 1]; TABLE *table; - bool error; + bool error= TRUE; uint path_length; + /* + Is set if we're under LOCK TABLES, and used + to downgrade the exclusive lock after the + table was truncated. + */ + MDL_ticket *mdl_ticket= NULL; + bool has_mdl_lock= FALSE; bool is_temporary_table= false; DBUG_ENTER("mysql_truncate"); bzero((char*) &create_info,sizeof(create_info)); /* Remove tables from the HANDLER's hash. */ - mysql_ha_rm_tables(thd, table_list, FALSE); + mysql_ha_rm_tables(thd, table_list); /* If it is a temporary table, close and regenerate it */ if (!dont_send_ok && (table= find_temporary_table(thd, table_list))) @@ -1110,7 +1127,7 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) goto trunc_by_del; table->file->info(HA_STATUS_AUTO | HA_STATUS_NO_LOCK); - + close_temporary_table(thd, table, 0, 0); // Don't free share ha_create_table(thd, share->normalized_path.str, share->db.str, share->table_name.str, &create_info, 1); @@ -1137,6 +1154,12 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) if (!dont_send_ok) { enum legacy_db_type table_type; + /* + FIXME: Code of TRUNCATE breaks the meta-data + locking protocol since it tries to find out the table storage + engine and therefore accesses table in some way without holding + any kind of meta-data lock. + */ mysql_frm_type(thd, path, &table_type); if (table_type == DB_TYPE_UNKNOWN) { @@ -1162,13 +1185,55 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) thd->lex->alter_info.flags & ALTER_ADMIN_PARTITION) goto trunc_by_del; - if (lock_and_wait_for_table_name(thd, table_list)) - DBUG_RETURN(TRUE); + + if (thd->locked_tables_mode) + { + if (!(table= find_table_for_mdl_upgrade(thd->open_tables, table_list->db, + table_list->table_name, FALSE))) + DBUG_RETURN(TRUE); + mdl_ticket= table->mdl_ticket; + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + goto end; + close_all_tables_for_name(thd, table->s, FALSE); + } + else + { + MDL_request mdl_global_request, mdl_request; + MDL_request_list mdl_requests; + /* + Even though we could use the previous execution branch + here just as well, we must not try to open the table: + MySQL manual documents that TRUNCATE can be used to + repair a damaged table, i.e. a table that can not be + fully "opened". In particular MySQL manual says: + + As long as the table format file tbl_name.frm is valid, + the table can be re-created as an empty table with TRUNCATE + TABLE, even if the data or index files have become corrupted. + */ + + mdl_global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); + mdl_request.init(MDL_key::TABLE, table_list->db, table_list->table_name, + MDL_EXCLUSIVE); + mdl_requests.push_front(&mdl_request); + mdl_requests.push_front(&mdl_global_request); + + if (thd->mdl_context.acquire_locks(&mdl_requests)) + DBUG_RETURN(TRUE); + + has_mdl_lock= TRUE; + mysql_mutex_lock(&LOCK_open); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_list->db, + table_list->table_name); + mysql_mutex_unlock(&LOCK_open); + } } - // Remove the .frm extension AIX 5.2 64-bit compiler bug (BUG#16155): this - // crashes, replacement works. *(path + path_length - reg_ext_length)= - // '\0'; + /* + Remove the .frm extension AIX 5.2 64-bit compiler bug (BUG#16155): this + crashes, replacement works. *(path + path_length - reg_ext_length)= + '\0'; + */ path[path_length - reg_ext_length] = 0; mysql_mutex_lock(&LOCK_open); error= ha_create_table(thd, path, table_list->db, table_list->table_name, @@ -1179,24 +1244,26 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok) end: if (!dont_send_ok) { + if (thd->locked_tables_mode && thd->locked_tables_list.reopen_tables(thd)) + thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); + /* + Even if we failed to reopen some tables, + the operation itself succeeded, write the binlog. + */ if (!error) { /* In RBR, the statement is not binlogged if the table is temporary. */ if (!is_temporary_table || !thd->current_stmt_binlog_row_based) - error= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - if (!error) - my_ok(thd); // This should return record count + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + my_ok(thd); // This should return record count } - mysql_mutex_lock(&LOCK_open); - unlock_table_name(thd, table_list); - mysql_mutex_unlock(&LOCK_open); - } - else if (error) - { - mysql_mutex_lock(&LOCK_open); - unlock_table_name(thd, table_list); - mysql_mutex_unlock(&LOCK_open); + if (has_mdl_lock) + thd->mdl_context.release_transactional_locks(); + if (mdl_ticket) + mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE); } + + DBUG_PRINT("exit", ("error: %d", error)); DBUG_RETURN(error); trunc_by_del: diff --git a/sql/sql_do.cc b/sql/sql_do.cc index 8406a9eaf45..0f3a7e1ecef 100644 --- a/sql/sql_do.cc +++ b/sql/sql_do.cc @@ -17,6 +17,7 @@ /* Execute DO statement */ #include "mysql_priv.h" +#include "transaction.h" bool mysql_do(THD *thd, List<Item> &values) { @@ -36,7 +37,7 @@ bool mysql_do(THD *thd, List<Item> &values) will clear the error and the rollback in the end of dispatch_command() won't work. */ - ha_autocommit_or_rollback(thd, thd->is_error()); + trans_rollback_stmt(thd); thd->clear_error(); // DO always is OK } my_ok(thd); diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index 5b45c35dd22..4b22d5e8eec 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -34,27 +34,21 @@ */ /* - There are two containers holding information about open handler tables. - The first is 'thd->handler_tables'. It is a linked list of TABLE objects. - It is used like 'thd->open_tables' in the table cache. The trick is to - exchange these two lists during open and lock of tables. Thus the normal - table cache code can be used. - The second container is a HASH. It holds objects of the type TABLE_LIST. - Despite its name, no lists of tables but only single structs are hashed - (the 'next' pointer is always NULL). The reason for theis second container - is, that we want handler tables to survive FLUSH TABLE commands. A table - affected by FLUSH TABLE must be closed so that other threads are not - blocked by handler tables still in use. Since we use the normal table cache - functions with 'thd->handler_tables', the closed tables are removed from - this list. Hence we need the original open information for the handler - table in the case that it is used again. This information is handed over - to mysql_ha_open() as a TABLE_LIST. So we store this information in the - second container, where it is not affected by FLUSH TABLE. The second - container is implemented as a hash for performance reasons. Consequently, - we use it not only for re-opening a handler table, but also for the - HANDLER ... READ commands. For this purpose, we store a pointer to the - TABLE structure (in the first container) in the TBALE_LIST object in the - second container. When the table is flushed, the pointer is cleared. + The information about open HANDLER objects is stored in a HASH. + It holds objects of type TABLE_LIST, which are indexed by table + name/alias, and allows us to quickly find a HANDLER table for any + operation at hand - be it HANDLER READ or HANDLER CLOSE. + + It also allows us to maintain an "open" HANDLER even in cases + when there is no physically open cursor. E.g. a FLUSH TABLE + statement in this or some other connection demands that all open + HANDLERs against the flushed table are closed. In order to + preserve the information about an open HANDLER, we don't perform + a complete HANDLER CLOSE, but only close the TABLE object. The + corresponding TABLE_LIST is kept in the cache with 'table' + pointer set to NULL. The table will be reopened on next access + (this, however, leads to loss of cursor position, unless the + cursor points at the first record). */ #include "mysql_priv.h" @@ -117,41 +111,27 @@ static void mysql_ha_hash_free(TABLE_LIST *tables) @param thd Thread identifier. @param tables A list of tables with the first entry to close. - @param is_locked If LOCK_open is locked. @note Though this function takes a list of tables, only the first list entry will be closed. @note Broadcasts refresh if it closed a table with old version. */ -static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables, - bool is_locked) +static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) { - TABLE **table_ptr; - /* - Though we could take the table pointer from hash_tables->table, - we must follow the thd->handler_tables chain anyway, as we need the - address of the 'next' pointer referencing this table - for close_thread_table(). - */ - for (table_ptr= &(thd->handler_tables); - *table_ptr && (*table_ptr != tables->table); - table_ptr= &(*table_ptr)->next) - ; - - if (*table_ptr) + if (tables->table && !tables->table->s->tmp_table) { - (*table_ptr)->file->ha_index_or_rnd_end(); - if (! is_locked) - mysql_mutex_lock(&LOCK_open); - if (close_thread_table(thd, table_ptr)) + /* Non temporary table. */ + tables->table->file->ha_index_or_rnd_end(); + mysql_mutex_lock(&LOCK_open); + if (close_thread_table(thd, &tables->table)) { /* Tell threads waiting for refresh that something has happened */ broadcast_refresh(); } - if (! is_locked) - mysql_mutex_unlock(&LOCK_open); + mysql_mutex_unlock(&LOCK_open); + thd->mdl_context.release_lock(tables->mdl_request.ticket); } else if (tables->table) { @@ -160,10 +140,13 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables, table->file->ha_index_or_rnd_end(); table->query_id= thd->query_id; table->open_by_handler= 0; + mark_tmp_table_for_reuse(table); } /* Mark table as closed, ready for re-open if necessary. */ tables->table= NULL; + /* Safety, cleanup the pointer to satisfy MDL assertions. */ + tables->mdl_request.ticket= NULL; } /* @@ -193,13 +176,19 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) TABLE_LIST *hash_tables = NULL; char *db, *name, *alias; uint dblen, namelen, aliaslen, counter; - int error; + bool error; TABLE *backup_open_tables; + MDL_ticket *mdl_savepoint; DBUG_ENTER("mysql_ha_open"); DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d", tables->db, tables->table_name, tables->alias, (int) reopen)); + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + DBUG_RETURN(TRUE); + } if (tables->schema_table) { my_error(ER_WRONG_USAGE, MYF(0), "HANDLER OPEN", @@ -217,7 +206,10 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) HANDLER_TABLES_HASH_SIZE, 0, 0, (my_hash_get_key) mysql_ha_hash_get_key, (my_hash_free_key) mysql_ha_hash_free, 0)) - goto err; + { + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); + } } else if (! reopen) /* Otherwise we have 'tables' already. */ { @@ -225,122 +217,123 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) strlen(tables->alias) + 1)) { DBUG_PRINT("info",("duplicate '%s'", tables->alias)); + DBUG_PRINT("exit",("ERROR")); my_error(ER_NONUNIQ_TABLE, MYF(0), tables->alias); - goto err; + DBUG_RETURN(TRUE); } } + if (! reopen) + { + /* copy the TABLE_LIST struct */ + dblen= strlen(tables->db) + 1; + namelen= strlen(tables->table_name) + 1; + aliaslen= strlen(tables->alias) + 1; + if (!(my_multi_malloc(MYF(MY_WME), + &hash_tables, (uint) sizeof(*hash_tables), + &db, (uint) dblen, + &name, (uint) namelen, + &alias, (uint) aliaslen, + NullS))) + { + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); + } + /* structure copy */ + *hash_tables= *tables; + hash_tables->db= db; + hash_tables->table_name= name; + hash_tables->alias= alias; + memcpy(hash_tables->db, tables->db, dblen); + memcpy(hash_tables->table_name, tables->table_name, namelen); + memcpy(hash_tables->alias, tables->alias, aliaslen); + hash_tables->mdl_request.init(MDL_key::TABLE, db, name, MDL_SHARED); + /* for now HANDLER can be used only for real TABLES */ + hash_tables->required_type= FRMTYPE_TABLE; + + /* add to hash */ + if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables)) + { + my_free((char*) hash_tables, MYF(0)); + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); + } + } + else + hash_tables= tables; + /* Save and reset the open_tables list so that open_tables() won't be able to access (or know about) the previous list. And on return from open_tables(), thd->open_tables will contain only the opened table. - The thd->handler_tables list is kept as-is to avoid deadlocks if - open_table(), called by open_tables(), needs to back-off because - of a pending name-lock on the table being opened. - See open_table() back-off comments for more details. */ backup_open_tables= thd->open_tables; thd->open_tables= NULL; + mdl_savepoint= thd->mdl_context.mdl_savepoint(); /* - open_tables() will set 'tables->table' if successful. + open_tables() will set 'hash_tables->table' if successful. It must be NULL for a real open when calling open_tables(). */ - DBUG_ASSERT(! tables->table); + DBUG_ASSERT(! hash_tables->table); - /* for now HANDLER can be used only for real TABLES */ - tables->required_type= FRMTYPE_TABLE; /* We use open_tables() here, rather than, say, open_ltable() or open_table() because we would like to be able to open a temporary table. */ - error= open_tables(thd, &tables, &counter, 0); - if (thd->open_tables) + error= open_tables(thd, &hash_tables, &counter, 0); + + if (! error && + ! (hash_tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER)) { - if (thd->open_tables->next) - { - /* - We opened something that is more than a single table. - This happens with MERGE engine. Don't try to link - this mess into thd->handler_tables list, close it - and report an error. We must do it right away - because mysql_ha_close_table(), called down the road, - can close a single table only. - */ - close_thread_tables(thd); - my_error(ER_ILLEGAL_HA, MYF(0), tables->alias); - error= 1; - } - else - { - /* Merge the opened table into handler_tables list. */ - thd->open_tables->next= thd->handler_tables; - thd->handler_tables= thd->open_tables; - } + my_error(ER_ILLEGAL_HA, MYF(0), tables->alias); + error= TRUE; + } + if (!error && + hash_tables->mdl_request.ticket && + thd->mdl_context.has_lock(mdl_savepoint, + hash_tables->mdl_request.ticket)) + { + /* The ticket returned is within a savepoint. Make a copy. */ + error= thd->mdl_context.clone_ticket(&hash_tables->mdl_request); + hash_tables->table->mdl_ticket= hash_tables->mdl_request.ticket; } - - /* Restore the state. */ - thd->open_tables= backup_open_tables; - if (error) - goto err; - - /* There can be only one table in '*tables'. */ - if (! (tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER)) { - my_error(ER_ILLEGAL_HA, MYF(0), tables->alias); - goto err; + close_thread_tables(thd); + thd->open_tables= backup_open_tables; + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); + if (!reopen) + my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables); + else + hash_tables->table= NULL; + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); } - - if (! reopen) + thd->open_tables= backup_open_tables; + if (hash_tables->mdl_request.ticket) { - /* copy the TABLE_LIST struct */ - dblen= strlen(tables->db) + 1; - namelen= strlen(tables->table_name) + 1; - aliaslen= strlen(tables->alias) + 1; - if (!(my_multi_malloc(MYF(MY_WME), - &hash_tables, (uint) sizeof(*hash_tables), - &db, (uint) dblen, - &name, (uint) namelen, - &alias, (uint) aliaslen, - NullS))) - goto err; - /* structure copy */ - *hash_tables= *tables; - hash_tables->db= db; - hash_tables->table_name= name; - hash_tables->alias= alias; - memcpy(hash_tables->db, tables->db, dblen); - memcpy(hash_tables->table_name, tables->table_name, namelen); - memcpy(hash_tables->alias, tables->alias, aliaslen); - - /* add to hash */ - if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables)) - goto err; + thd->mdl_context. + move_ticket_after_trans_sentinel(hash_tables->mdl_request.ticket); + thd->mdl_context.set_needs_thr_lock_abort(TRUE); } + /* Assert that the above check prevent opening of views and merge tables. */ + DBUG_ASSERT(hash_tables->table->next == NULL); /* If it's a temp table, don't reset table->query_id as the table is being used by this handler. Otherwise, no meaning at all. */ - tables->table->open_by_handler= 1; + hash_tables->table->open_by_handler= 1; if (! reopen) my_ok(thd); DBUG_PRINT("exit",("OK")); DBUG_RETURN(FALSE); - -err: - if (hash_tables) - my_free((char*) hash_tables, MYF(0)); - if (tables->table) - mysql_ha_close_table(thd, tables, FALSE); - DBUG_PRINT("exit",("ERROR")); - DBUG_RETURN(TRUE); } @@ -368,11 +361,16 @@ bool mysql_ha_close(THD *thd, TABLE_LIST *tables) DBUG_PRINT("enter",("'%s'.'%s' as '%s'", tables->db, tables->table_name, tables->alias)); + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + DBUG_RETURN(TRUE); + } if ((hash_tables= (TABLE_LIST*) my_hash_search(&thd->handler_tables_hash, (uchar*) tables->alias, strlen(tables->alias) + 1))) { - mysql_ha_close_table(thd, hash_tables, FALSE); + mysql_ha_close_table(thd, hash_tables); my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables); } else @@ -382,6 +380,13 @@ bool mysql_ha_close(THD *thd, TABLE_LIST *tables) DBUG_RETURN(TRUE); } + /* + Mark MDL_context as no longer breaking protocol if we have + closed last HANDLER. + */ + if (! thd->handler_tables_hash.records) + thd->mdl_context.set_needs_thr_lock_abort(FALSE); + my_ok(thd); DBUG_PRINT("exit", ("OK")); DBUG_RETURN(FALSE); @@ -430,6 +435,12 @@ bool mysql_ha_read(THD *thd, TABLE_LIST *tables, DBUG_PRINT("enter",("'%s'.'%s' as '%s'", tables->db, tables->table_name, tables->alias)); + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + DBUG_RETURN(TRUE); + } + thd->lex->select_lex.context.resolve_in_table_list_only(tables); list.push_front(new Item_field(&thd->lex->select_lex.context, NULL, NULL, "*")); @@ -461,60 +472,41 @@ retry: hash_tables->db, hash_tables->table_name, hash_tables->alias, table)); } - table->pos_in_table_list= tables; -#if MYSQL_VERSION_ID < 40100 - if (*tables->db && strcmp(table->table_cache_key, tables->db)) - { - DBUG_PRINT("info",("wrong db")); - table= NULL; - } -#endif } else table= NULL; if (!table) { -#if MYSQL_VERSION_ID < 40100 - char buff[MAX_DBKEY_LENGTH]; - if (*tables->db) - strxnmov(buff, sizeof(buff)-1, tables->db, ".", tables->table_name, - NullS); - else - strncpy(buff, tables->alias, sizeof(buff)); - my_error(ER_UNKNOWN_TABLE, MYF(0), buff, "HANDLER"); -#else my_error(ER_UNKNOWN_TABLE, MYF(0), tables->alias, "HANDLER"); -#endif goto err0; } - tables->table=table; /* save open_tables state */ backup_open_tables= thd->open_tables; + /* Always a one-element list, see mysql_ha_open(). */ + DBUG_ASSERT(hash_tables->table->next == NULL); /* mysql_lock_tables() needs thd->open_tables to be set correctly to - be able to handle aborts properly. When the abort happens, it's - safe to not protect thd->handler_tables because it won't close any - tables. + be able to handle aborts properly. */ - thd->open_tables= thd->handler_tables; + thd->open_tables= hash_tables->table; + - lock= mysql_lock_tables(thd, &tables->table, 1, - MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN, &need_reopen); + lock= mysql_lock_tables(thd, &thd->open_tables, 1, 0, &need_reopen); - /* restore previous context */ + /* + In 5.1 and earlier, mysql_lock_tables() could replace the TABLE + object with another one (reopen it). This is no longer the case + with new MDL. + */ + DBUG_ASSERT(hash_tables->table == thd->open_tables); + /* Restore previous context. */ thd->open_tables= backup_open_tables; if (need_reopen) { - mysql_ha_close_table(thd, hash_tables, FALSE); - /* - The lock might have been aborted, we need to manually reset - thd->some_tables_deleted because handler's tables are closed - in a non-standard way. Otherwise we might loop indefinitely. - */ - thd->some_tables_deleted= 0; + mysql_ha_close_table(thd, hash_tables); goto retry; } @@ -522,7 +514,8 @@ retry: goto err0; // mysql_lock_tables() printed error message already // Always read all columns - tables->table->read_set= &tables->table->s->all_set; + hash_tables->table->read_set= &hash_tables->table->s->all_set; + tables->table= hash_tables->table; if (cond) { @@ -735,12 +728,11 @@ static TABLE_LIST *mysql_ha_find(THD *thd, TABLE_LIST *tables) @param thd Thread identifier. @param tables The list of tables to remove. - @param is_locked If LOCK_open is locked. @note Broadcasts refresh if it closed a table with old version. */ -void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables, bool is_locked) +void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables) { TABLE_LIST *hash_tables, *next; DBUG_ENTER("mysql_ha_rm_tables"); @@ -753,11 +745,18 @@ void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables, bool is_locked) { next= hash_tables->next_local; if (hash_tables->table) - mysql_ha_close_table(thd, hash_tables, is_locked); + mysql_ha_close_table(thd, hash_tables); my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables); hash_tables= next; } + /* + Mark MDL_context as no longer breaking protocol if we have + closed last HANDLER. + */ + if (! thd->handler_tables_hash.records) + thd->mdl_context.set_needs_thr_lock_abort(FALSE); + DBUG_VOID_RETURN; } @@ -776,13 +775,28 @@ void mysql_ha_flush(THD *thd) TABLE_LIST *hash_tables; DBUG_ENTER("mysql_ha_flush"); - mysql_mutex_assert_owner(&LOCK_open); + mysql_mutex_assert_not_owner(&LOCK_open); + + /* + Don't try to flush open HANDLERs when we're working with + system tables. The main MDL context is backed up and we can't + properly release HANDLER locks stored there. + */ + if (thd->state_flags & Open_tables_state::BACKUPS_AVAIL) + DBUG_VOID_RETURN; for (uint i= 0; i < thd->handler_tables_hash.records; i++) { hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i); - if (hash_tables->table && hash_tables->table->needs_reopen_or_name_lock()) - mysql_ha_close_table(thd, hash_tables, TRUE); + /* + TABLE::mdl_ticket is 0 for temporary tables so we need extra check. + */ + if (hash_tables->table && + ((hash_tables->table->mdl_ticket && + hash_tables->table->mdl_ticket->has_pending_conflicting_lock()) || + (!hash_tables->table->s->tmp_table && + hash_tables->table->s->needs_reopen()))) + mysql_ha_close_table(thd, hash_tables); } DBUG_VOID_RETURN; @@ -806,7 +820,7 @@ void mysql_ha_cleanup(THD *thd) { hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i); if (hash_tables->table) - mysql_ha_close_table(thd, hash_tables, FALSE); + mysql_ha_close_table(thd, hash_tables); } my_hash_free(&thd->handler_tables_hash); diff --git a/sql/sql_help.cc b/sql/sql_help.cc index 003741a7ddc..e9b15e07e9d 100644 --- a/sql/sql_help.cc +++ b/sql/sql_help.cc @@ -653,8 +653,14 @@ bool mysqld_help(THD *thd, const char *mask) tables[3].alias= tables[3].table_name= (char*) "help_keyword"; tables[3].lock_type= TL_READ; tables[0].db= tables[1].db= tables[2].db= tables[3].db= (char*) "mysql"; + init_mdl_requests(tables); - Open_tables_state open_tables_state_backup; + /* + HELP must be available under LOCK TABLES. + Reset and backup the current open tables state to + make it possible. + */ + Open_tables_backup open_tables_state_backup; if (open_system_tables_for_read(thd, tables, &open_tables_state_backup)) goto error2; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index c774cb4f3ad..2cb873c823d 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -61,6 +61,7 @@ #include "sql_show.h" #include "slave.h" #include "rpl_mi.h" +#include "transaction.h" #ifndef EMBEDDED_LIBRARY static bool delayed_get_table(THD *thd, TABLE_LIST *table_list); @@ -441,7 +442,7 @@ void upgrade_lock_type(THD *thd, thr_lock_type *lock_type, */ if (specialflag & (SPECIAL_NO_NEW_FUNC | SPECIAL_SAFE_MODE) || thd->variables.max_insert_delayed_threads == 0 || - thd->prelocked_mode || + thd->locked_tables_mode > LTM_LOCK_TABLES || thd->lex->uses_stored_routines()) { *lock_type= TL_WRITE; @@ -518,7 +519,7 @@ bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list) DBUG_ENTER("open_and_lock_for_insert_delayed"); #ifndef EMBEDDED_LIBRARY - if (thd->locked_tables && thd->global_read_lock) + if (thd->locked_tables_mode && thd->global_read_lock.is_acquired()) { /* If this connection has the global read lock, the handler thread @@ -627,8 +628,10 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, never be able to get a lock on the table. QQQ: why not upgrade the lock here instead? */ - if (table_list->lock_type == TL_WRITE_DELAYED && thd->locked_tables && - find_locked_table(thd, table_list->db, table_list->table_name)) + if (table_list->lock_type == TL_WRITE_DELAYED && + thd->locked_tables_mode && + find_locked_table(thd->open_tables, table_list->db, + table_list->table_name)) { my_error(ER_DELAYED_INSERT_TABLE_LOCKED, MYF(0), table_list->table_name); @@ -756,7 +759,14 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, { if (duplic != DUP_ERROR || ignore) table->file->extra(HA_EXTRA_IGNORE_DUP_KEY); - if (!thd->prelocked_mode) + /** + This is a simple check for the case when the table has a trigger + that reads from it, or when the statement invokes a stored function + that reads from the table being inserted to. + Engines can't handle a bulk insert in parallel with a read form the + same table in the same connection. + */ + if (thd->locked_tables_mode <= LTM_LOCK_TABLES) table->file->ha_start_bulk_insert(values_list.elements); } @@ -880,7 +890,8 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, auto_inc values from the delayed_insert thread as they share TABLE. */ table->file->ha_release_auto_increment(); - if (!thd->prelocked_mode && table->file->ha_end_bulk_insert() && !error) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES && + table->file->ha_end_bulk_insert() && !error) { table->file->print_error(my_errno,MYF(0)); error=1; @@ -1775,7 +1786,7 @@ public: mysql_mutex_t mutex; mysql_cond_t cond, cond_client; volatile uint tables_in_use,stacked_inserts; - volatile bool status,dead; + volatile bool status; COPY_INFO info; I_List<delayed_row> rows; ulong group_count; @@ -1783,8 +1794,7 @@ public: Delayed_insert() :locks_in_memory(0), - table(0),tables_in_use(0),stacked_inserts(0), status(0), dead(0), - group_count(0) + table(0),tables_in_use(0),stacked_inserts(0), status(0), group_count(0) { thd.security_ctx->user=thd.security_ctx->priv_user=(char*) delayed_user; thd.security_ctx->host=(char*) my_localhost; @@ -1858,6 +1868,7 @@ public: inline uint lock_count() { return locks_in_memory; } TABLE* get_local_table(THD* client_thd); + bool open_and_lock_table(); bool handle_inserts(void); }; @@ -1931,9 +1942,8 @@ Delayed_insert *find_handler(THD *thd, TABLE_LIST *table_list) a given consumer (delayed insert thread), only at different stages of producer-consumer relationship. - 'dead' and 'status' variables in Delayed_insert are redundant - too, since there is already 'di->thd.killed' and - di->stacked_inserts. + The 'status' variable in Delayed_insert is redundant + too, since there is already di->stacked_inserts. */ static @@ -2005,6 +2015,11 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list) } mysql_mutex_unlock(&di->mutex); thd_proc_info(thd, "got old table"); + if (thd->killed) + { + di->unlock(); + goto end_create; + } if (di->thd.killed) { if (di->thd.is_error()) @@ -2012,20 +2027,19 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list) /* Copy the error message. Note that we don't treat fatal errors in the delayed thread as fatal errors in the - main thread. Use of my_message will enable stored - procedures continue handlers. + main thread. If delayed thread was killed, we don't + want to send "Server shutdown in progress" in the + INSERT THREAD. */ - my_message(di->thd.stmt_da->sql_errno(), di->thd.stmt_da->message(), - MYF(0)); - } - di->unlock(); + if (di->thd.stmt_da->sql_errno() == ER_SERVER_SHUTDOWN) + my_message(ER_QUERY_INTERRUPTED, ER(ER_QUERY_INTERRUPTED), MYF(0)); + else + my_message(di->thd.stmt_da->sql_errno(), di->thd.stmt_da->message(), + MYF(0)); + } + di->unlock(); goto end_create; } - if (thd->killed) - { - di->unlock(); - goto end_create; - } mysql_mutex_lock(&LOCK_delayed_insert); delayed_threads.append(di); mysql_mutex_unlock(&LOCK_delayed_insert); @@ -2082,17 +2096,33 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) if (!thd.lock) // Table is not locked { thd_proc_info(client_thd, "waiting for handler lock"); - mysql_cond_signal(&cond); // Tell handler to lock table - while (!dead && !thd.lock && ! client_thd->killed) + mysql_cond_signal(&cond); // Tell handler to lock table + while (!thd.killed && !thd.lock && ! client_thd->killed) { mysql_cond_wait(&cond_client, &mutex); } thd_proc_info(client_thd, "got handler lock"); if (client_thd->killed) goto error; - if (dead) + if (thd.killed) { - my_message(thd.stmt_da->sql_errno(), thd.stmt_da->message(), MYF(0)); + /* + Copy the error message. Note that we don't treat fatal + errors in the delayed thread as fatal errors in the + main thread. If delayed thread was killed, we don't + want to send "Server shutdown in progress" in the + INSERT THREAD. + + The thread could be killed with an error message if + di->handle_inserts() or di->open_and_lock_table() fails. + The thread could be killed without an error message if + killed using mysql_notify_thread_having_shared_lock() or + kill_delayed_threads_for_table(). + */ + if (!thd.is_error() || thd.stmt_da->sql_errno() == ER_SERVER_SHUTDOWN) + my_message(ER_QUERY_INTERRUPTED, ER(ER_QUERY_INTERRUPTED), MYF(0)); + else + my_message(thd.stmt_da->sql_errno(), thd.stmt_da->message(), MYF(0)); goto error; } } @@ -2334,6 +2364,42 @@ void kill_delayed_threads(void) } +/** + Open and lock table for use by delayed thread and check that + this table is suitable for delayed inserts. + + @retval FALSE - Success. + @retval TRUE - Failure. +*/ + +bool Delayed_insert::open_and_lock_table() +{ + if (!(table= open_n_lock_single_table(&thd, &table_list, + TL_WRITE_DELAYED, 0))) + { + thd.fatal_error(); // Abort waiting inserts + return TRUE; + } + if (!(table->file->ha_table_flags() & HA_CAN_INSERT_DELAYED)) + { + my_error(ER_DELAYED_NOT_SUPPORTED, MYF(ME_FATALERROR), + table_list.table_name); + return TRUE; + } + if (table->triggers) + { + /* + Table has triggers. This is not an error, but we do + not support triggers with delayed insert. Terminate the delayed + thread without an error and thus request lock upgrade. + */ + return TRUE; + } + table->copy_blobs= 1; + return FALSE; +} + + /* * Create a new delayed insert thread */ @@ -2396,29 +2462,10 @@ pthread_handler_t handle_delayed_insert(void *arg) thd->lex->set_stmt_unsafe(); thd->set_current_stmt_binlog_row_based_if_mixed(); - /* Open table */ - if (!(di->table= open_n_lock_single_table(thd, &di->table_list, - TL_WRITE_DELAYED))) - { - thd->fatal_error(); // Abort waiting inserts - goto err; - } - if (!(di->table->file->ha_table_flags() & HA_CAN_INSERT_DELAYED)) - { - my_error(ER_DELAYED_NOT_SUPPORTED, MYF(ME_FATALERROR), - di->table_list.table_name); - goto err; - } - if (di->table->triggers) - { - /* - Table has triggers. This is not an error, but we do - not support triggers with delayed insert. Terminate the delayed - thread without an error and thus request lock upgrade. - */ + init_mdl_requests(&di->table_list); + + if (di->open_and_lock_table()) goto err; - } - di->table->copy_blobs=1; /* Tell client that the thread is initialized */ mysql_cond_signal(&di->cond_client); @@ -2428,7 +2475,7 @@ pthread_handler_t handle_delayed_insert(void *arg) for (;;) { - if (thd->killed == THD::KILL_CONNECTION) + if (thd->killed) { uint lock_count; /* @@ -2445,7 +2492,8 @@ pthread_handler_t handle_delayed_insert(void *arg) break; // Time to die } - if (!di->status && !di->stacked_inserts) + /* Shouldn't wait if killed or an insert is waiting. */ + if (!thd->killed && !di->status && !di->stacked_inserts) { struct timespec abstime; set_timespec(abstime, delayed_insert_timeout); @@ -2456,7 +2504,7 @@ pthread_handler_t handle_delayed_insert(void *arg) thd_proc_info(&(di->thd), "Waiting for INSERT"); DBUG_PRINT("info",("Waiting for someone to insert rows")); - while (!thd->killed) + while (!thd->killed && !di->status) { int error; #if defined(HAVE_BROKEN_COND_TIMEDWAIT) @@ -2472,13 +2520,8 @@ pthread_handler_t handle_delayed_insert(void *arg) } #endif #endif - if (thd->killed || di->status) - break; if (error == ETIMEDOUT || error == ETIME) - { thd->killed= THD::KILL_CONNECTION; - break; - } } /* We can't lock di->mutex and mysys_var->mutex at the same time */ mysql_mutex_unlock(&di->mutex); @@ -2490,9 +2533,9 @@ pthread_handler_t handle_delayed_insert(void *arg) } thd_proc_info(&(di->thd), 0); - if (di->tables_in_use && ! thd->lock) + if (di->tables_in_use && ! thd->lock && !thd->killed) { - bool not_used; + bool need_reopen; /* Request for new delayed insert. Lock the table, but avoid to be blocked by a global read lock. @@ -2505,11 +2548,28 @@ pthread_handler_t handle_delayed_insert(void *arg) */ if (! (thd->lock= mysql_lock_tables(thd, &di->table, 1, MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK, - ¬_used))) + &need_reopen))) { - /* Fatal error */ - di->dead= 1; - thd->killed= THD::KILL_CONNECTION; + if (need_reopen && !thd->killed) + { + /* + We were waiting to obtain TL_WRITE_DELAYED (probably due to + someone having or requesting TL_WRITE_ALLOW_READ) and got + aborted. Try to reopen table and if it fails die. + */ + TABLE_LIST *tl_ptr = &di->table_list; + close_tables_for_reopen(thd, &tl_ptr, NULL); + di->table= 0; + if (di->open_and_lock_table()) + { + thd->killed= THD::KILL_CONNECTION; + } + } + else + { + /* Fatal error */ + thd->killed= THD::KILL_CONNECTION; + } } mysql_cond_broadcast(&di->cond_client); } @@ -2518,7 +2578,6 @@ pthread_handler_t handle_delayed_insert(void *arg) if (di->handle_inserts()) { /* Some fatal error */ - di->dead= 1; thd->killed= THD::KILL_CONNECTION; } } @@ -2538,7 +2597,7 @@ pthread_handler_t handle_delayed_insert(void *arg) */ di->table->file->ha_release_auto_increment(); mysql_unlock_tables(thd, lock); - ha_autocommit_or_rollback(thd, 0); + trans_commit_stmt(thd); di->group_count=0; mysql_mutex_lock(&di->mutex); } @@ -2558,7 +2617,7 @@ pthread_handler_t handle_delayed_insert(void *arg) first call to ha_*_row() instead. Remove code that are used to cover for the case outlined above. */ - ha_autocommit_or_rollback(thd, 1); + trans_rollback_stmt(thd); DBUG_LEAVE; } @@ -2570,7 +2629,6 @@ pthread_handler_t handle_delayed_insert(void *arg) close_thread_tables(thd); // Free the table di->table=0; - di->dead= 1; // If error thd->killed= THD::KILL_CONNECTION; // If error mysql_cond_broadcast(&di->cond_client); // Safety mysql_mutex_unlock(&di->mutex); @@ -2646,7 +2704,7 @@ bool Delayed_insert::handle_inserts(void) thd_proc_info(&thd, "insert"); max_rows= delayed_insert_limit; - if (thd.killed || table->needs_reopen_or_name_lock()) + if (thd.killed || table->s->needs_reopen()) { thd.killed= THD::KILL_CONNECTION; max_rows= ULONG_MAX; // Do as much as possible @@ -2770,11 +2828,10 @@ bool Delayed_insert::handle_inserts(void) will be binlogged together as one single Table_map event and one single Rows event. */ - if (thd.binlog_query(THD::ROW_QUERY_TYPE, - row->query.str, row->query.length, - FALSE, FALSE, errcode)) - goto err; - + thd.binlog_query(THD::ROW_QUERY_TYPE, + row->query.str, row->query.length, + FALSE, FALSE, errcode); + thd.time_zone_used = backup_time_zone_used; thd.variables.time_zone = backup_time_zone; } @@ -2848,9 +2905,8 @@ bool Delayed_insert::handle_inserts(void) TODO: Move the logging to last in the sequence of rows. */ - if (thd.current_stmt_binlog_row_based && - thd.binlog_flush_pending_rows_event(TRUE)) - goto err; + if (thd.current_stmt_binlog_row_based) + thd.binlog_flush_pending_rows_event(TRUE); if ((error=table->file->extra(HA_EXTRA_NO_CACHE))) { // This shouldn't happen @@ -3086,7 +3142,7 @@ select_insert::prepare(List<Item> &values, SELECT_LEX_UNIT *u) lex->current_select->join->select_options|= OPTION_BUFFER_RESULT; } else if (!(lex->current_select->options & OPTION_BUFFER_RESULT) && - !thd->prelocked_mode) + thd->locked_tables_mode <= LTM_LOCK_TABLES) { /* We must not yet prepare the result table if it is the same as one of the @@ -3152,7 +3208,7 @@ int select_insert::prepare2(void) { DBUG_ENTER("select_insert::prepare2"); if (thd->lex->current_select->options & OPTION_BUFFER_RESULT && - !thd->prelocked_mode) + thd->locked_tables_mode <= LTM_LOCK_TABLES) table->file->ha_start_bulk_insert((ha_rows) 0); DBUG_RETURN(0); } @@ -3279,7 +3335,8 @@ bool select_insert::send_eof() DBUG_PRINT("enter", ("trans_table=%d, table_type='%s'", trans_table, table->file->table_type())); - error= (!thd->prelocked_mode) ? table->file->ha_end_bulk_insert():0; + error= (thd->locked_tables_mode <= LTM_LOCK_TABLES ? + table->file->ha_end_bulk_insert() : 0); table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE); @@ -3303,21 +3360,16 @@ bool select_insert::send_eof() events are in the transaction cache and will be written when ha_autocommit_or_rollback() is issued below. */ - if (mysql_bin_log.is_open() && - (!error || thd->transaction.stmt.modified_non_trans_table)) + if (mysql_bin_log.is_open()) { int errcode= 0; if (!error) thd->clear_error(); else errcode= query_error_code(thd, killed_status == THD::NOT_KILLED); - if (thd->binlog_query(THD::ROW_QUERY_TYPE, + thd->binlog_query(THD::ROW_QUERY_TYPE, thd->query(), thd->query_length(), - trans_table, FALSE, errcode)) - { - table->file->ha_release_auto_increment(); - DBUG_RETURN(1); - } + trans_table, FALSE, errcode); } table->file->ha_release_auto_increment(); @@ -3364,7 +3416,7 @@ void select_insert::abort() { If we are not in prelocked mode, we end the bulk insert started before. */ - if (!thd->prelocked_mode) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES) table->file->ha_end_bulk_insert(); /* @@ -3388,10 +3440,9 @@ void select_insert::abort() { if (mysql_bin_log.is_open()) { int errcode= query_error_code(thd, thd->killed == THD::NOT_KILLED); - /* error of writing binary log is ignored */ - (void) thd->binlog_query(THD::ROW_QUERY_TYPE, thd->query(), - thd->query_length(), - transactional_table, FALSE, errcode); + thd->binlog_query(THD::ROW_QUERY_TYPE, thd->query(), + thd->query_length(), + transactional_table, FALSE, errcode); } if (!thd->current_stmt_binlog_row_based && !can_rollback_data()) thd->transaction.all.modified_non_trans_table= TRUE; @@ -3411,46 +3462,43 @@ void select_insert::abort() { CREATE TABLE (SELECT) ... ***************************************************************************/ -/* +/** Create table from lists of fields and items (or just return TABLE object for pre-opened existing table). - SYNOPSIS - create_table_from_items() - thd in Thread object - create_info in Create information (like MAX_ROWS, ENGINE or - temporary table flag) - create_table in Pointer to TABLE_LIST object providing database - and name for table to be created or to be open - alter_info in/out Initial list of columns and indexes for the table - to be created - items in List of items which should be used to produce rest - of fields for the table (corresponding fields will - be added to the end of alter_info->create_list) - lock out Pointer to the MYSQL_LOCK object for table created - (or open temporary table) will be returned in this - parameter. Since this table is not included in - THD::lock caller is responsible for explicitly - unlocking this table. - hooks - - NOTES - This function behaves differently for base and temporary tables: - - For base table we assume that either table exists and was pre-opened - and locked at open_and_lock_tables() stage (and in this case we just - emit error or warning and return pre-opened TABLE object) or special - placeholder was put in table cache that guarantees that this table - won't be created or opened until the placeholder will be removed - (so there is an exclusive lock on this table). - - We don't pre-open existing temporary table, instead we either open - or create and then open table in this function. - + @param thd [in] Thread object + @param create_info [in] Create information (like MAX_ROWS, ENGINE or + temporary table flag) + @param create_table [in] Pointer to TABLE_LIST object providing database + and name for table to be created or to be open + @param alter_info [in/out] Initial list of columns and indexes for the + table to be created + @param items [in] List of items which should be used to produce + rest of fields for the table (corresponding + fields will be added to the end of + alter_info->create_list) + @param lock [out] Pointer to the MYSQL_LOCK object for table + created will be returned in this parameter. + Since this table is not included in THD::lock + caller is responsible for explicitly unlocking + this table. + @param hooks [in] Hooks to be invoked before and after obtaining + table lock on the table being created. + + @note + This function assumes that either table exists and was pre-opened and + locked at open_and_lock_tables() stage (and in this case we just emit + error or warning and return pre-opened TABLE object) or an exclusive + metadata lock was acquired on table so we can safely create, open and + lock table in it (we don't acquire metadata lock if this create is + for temporary table). + + @note Since this function contains some logic specific to CREATE TABLE ... SELECT it should be changed before it can be used in other contexts. - RETURN VALUES - non-zero Pointer to TABLE object for table created or opened - 0 Error + @retval non-zero Pointer to TABLE object for table created or opened + @retval 0 Error */ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, @@ -3525,14 +3573,12 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, open_table(). */ { - tmp_disable_binlog(thd); if (!mysql_create_table_no_lock(thd, create_table->db, create_table->table_name, create_info, alter_info, 0, select_field_count)) { - if (create_info->table_existed && - !(create_info->options & HA_LEX_CREATE_TMP_TABLE)) + if (create_info->table_existed) { /* This means that someone created table underneath server @@ -3547,22 +3593,28 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { - mysql_mutex_lock(&LOCK_open); - if (reopen_name_locked_table(thd, create_table, FALSE)) + Open_table_context ot_ctx_unused(thd); + /* + Here we open the destination table, on which we already have + an exclusive metadata lock. + */ + if (open_table(thd, create_table, thd->mem_root, + &ot_ctx_unused, MYSQL_OPEN_REOPEN)) { + mysql_mutex_lock(&LOCK_open); quick_rm_table(create_info->db_type, create_table->db, table_case_name(create_info, create_table->table_name), 0); + mysql_mutex_unlock(&LOCK_open); } else table= create_table->table; - mysql_mutex_unlock(&LOCK_open); } else { - if (!(table= open_table(thd, create_table, thd->mem_root, (bool*) 0, - MYSQL_OPEN_TEMPORARY_ONLY)) && - !create_info->table_existed) + Open_table_context ot_ctx_unused(thd); + if (open_table(thd, create_table, thd->mem_root, &ot_ctx_unused, + MYSQL_OPEN_TEMPORARY_ONLY)) { /* This shouldn't happen as creation of temporary table should make @@ -3571,9 +3623,10 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, */ drop_temporary_table(thd, create_table); } + else + table= create_table->table; } } - reenable_binlog(thd); if (!table) // open failed DBUG_RETURN(0); } @@ -3582,6 +3635,11 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, table->reginfo.lock_type=TL_WRITE; hooks->prelock(&table, 1); // Call prelock hooks + /* + mysql_lock_tables() below should never fail with request to reopen table + since it won't wait for the table lock (we have exclusive metadata lock on + the table) and thus can't get aborted. + */ if (! ((*lock)= mysql_lock_tables(thd, &table, 1, MYSQL_LOCK_IGNORE_FLUSH, ¬_used)) || hooks->postlock(&table, 1)) @@ -3591,9 +3649,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, mysql_unlock_tables(thd, *lock); *lock= 0; } - - if (!create_info->table_existed) - drop_open_table(thd, table, create_table->db, create_table->table_name); + drop_open_table(thd, table, create_table->db, create_table->table_name); DBUG_RETURN(0); } DBUG_RETURN(table); @@ -3627,18 +3683,28 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u) */ class MY_HOOKS : public TABLEOP_HOOKS { public: - MY_HOOKS(select_create *x, TABLE_LIST *create_table, - TABLE_LIST *select_tables) - : ptr(x), all_tables(*create_table) + MY_HOOKS(select_create *x, TABLE_LIST *create_table_arg, + TABLE_LIST *select_tables_arg) + : ptr(x), + create_table(create_table_arg), + select_tables(select_tables_arg) { - all_tables.next_global= select_tables; } private: virtual int do_postlock(TABLE **tables, uint count) { + int error; THD *thd= const_cast<THD*>(ptr->get_thd()); - if (int error= decide_logging_format(thd, &all_tables)) + TABLE_LIST *save_next_global= create_table->next_global; + + create_table->next_global= select_tables; + + error= decide_logging_format(thd, create_table); + + create_table->next_global= save_next_global; + + if (error) return error; TABLE const *const table = *tables; @@ -3646,14 +3712,14 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u) !table->s->tmp_table && !ptr->get_create_info()->table_existed) { - if (int error= ptr->binlog_show_create_table(tables, count)) - return error; + ptr->binlog_show_create_table(tables, count); } return 0; } select_create *ptr; - TABLE_LIST all_tables; + TABLE_LIST *create_table; + TABLE_LIST *select_tables; }; MY_HOOKS hooks(this, create_table, select_tables); @@ -3675,8 +3741,7 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u) DBUG_EXECUTE_IF("sleep_create_select_before_check_if_exists", my_sleep(6000000);); - if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE) && - (create_table->table && create_table->table->db_stat)) + if (create_table->table) { /* Table already exists and was open at open_and_lock_tables() stage. */ if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) @@ -3741,7 +3806,7 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u) table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE); if (info.handle_duplicates == DUP_UPDATE) table->file->extra(HA_EXTRA_INSERT_WITH_UPDATE); - if (!thd->prelocked_mode) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES) table->file->ha_start_bulk_insert((ha_rows) 0); thd->abort_on_warning= (!info.ignore && (thd->variables.sql_mode & @@ -3754,7 +3819,7 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u) DBUG_RETURN(0); } -int +void select_create::binlog_show_create_table(TABLE **tables, uint count) { /* @@ -3793,13 +3858,12 @@ select_create::binlog_show_create_table(TABLE **tables, uint count) if (mysql_bin_log.is_open()) { int errcode= query_error_code(thd, thd->killed == THD::NOT_KILLED); - result= thd->binlog_query(THD::STMT_QUERY_TYPE, - query.ptr(), query.length(), - /* is_trans */ TRUE, - /* suppress_use */ FALSE, - errcode); + thd->binlog_query(THD::STMT_QUERY_TYPE, + query.ptr(), query.length(), + /* is_trans */ TRUE, + /* suppress_use */ FALSE, + errcode); } - return result; } void select_create::store_values(List<Item> &values) @@ -3857,8 +3921,8 @@ bool select_create::send_eof() */ if (!table->s->tmp_table) { - ha_autocommit_or_rollback(thd, 0); - end_active_trans(thd); + trans_commit_stmt(thd); + trans_commit_implicit(thd); } table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); @@ -3897,8 +3961,7 @@ void select_create::abort() select_insert::abort(); thd->transaction.stmt.modified_non_trans_table= FALSE; reenable_binlog(thd); - /* possible error of writing binary log is ignored deliberately */ - (void)thd->binlog_flush_pending_rows_event(TRUE); + thd->binlog_flush_pending_rows_event(TRUE); if (m_plock) { diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 27f099ef9ef..1e6c3ce7db3 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -1076,6 +1076,17 @@ public: } } + /** Return a pointer to the last element in query table list. */ + TABLE_LIST *last_table() + { + /* Don't use offsetof() macro in order to avoid warnings. */ + return query_tables ? + (TABLE_LIST*) ((char*) query_tables_last - + ((char*) &(query_tables->next_global) - + (char*) query_tables)) : + 0; + } + /** Has the parser/scanner detected that this statement is unsafe? */ @@ -1786,6 +1797,12 @@ struct LEX: public Query_tables_list bool subqueries, ignore; st_parsing_options parsing_options; Alter_info alter_info; + /* + For CREATE TABLE statement last element of table list which is not + part of SELECT or LIKE part (i.e. either element for table we are + creating or last of tables referenced by foreign keys). + */ + TABLE_LIST *create_last_non_select_table; /* Prepared statements SQL syntax:*/ LEX_STRING prepared_stmt_name; /* Statement name (in all queries) */ /* diff --git a/sql/sql_list.h b/sql/sql_list.h index e1bf05fff23..fdc80b116a7 100644 --- a/sql/sql_list.h +++ b/sql/sql_list.h @@ -504,15 +504,12 @@ public: template <class T> class I_List_iterator; -/* - WARNING: copy constructor of this class does not create a usable - copy, as its members may point at each other. -*/ class base_ilist { + struct ilink *first; + struct ilink last; public: - struct ilink *first,last; inline void empty() { first= &last; last.prev= &first; } base_ilist() { empty(); } inline bool is_empty() { return first == &last; } @@ -540,7 +537,31 @@ public: { return (first != &last) ? first : 0; } - friend class base_list_iterator; + + /** + Moves list elements to new owner, and empties current owner (i.e. this). + + @param[in,out] new_owner The new owner of the list elements. + Should be empty in input. + */ + + void move_elements_to(base_ilist *new_owner) + { + DBUG_ASSERT(new_owner->is_empty()); + new_owner->first= first; + new_owner->last= last; + empty(); + } + + friend class base_ilist_iterator; + private: + /* + We don't want to allow copying of this class, as that would give us + two list heads containing the same elements. + So we declare, but don't define copy CTOR and assignment operator. + */ + base_ilist(const base_ilist&); + void operator=(const base_ilist&); }; @@ -573,6 +594,9 @@ public: inline void push_back(T* a) { base_ilist::push_back(a); } inline T* get() { return (T*) base_ilist::get(); } inline T* head() { return (T*) base_ilist::head(); } + inline void move_elements_to(I_List<T>* new_owner) { + base_ilist::move_elements_to(new_owner); + } #ifndef _lint friend class I_List_iterator<T>; #endif diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 71ea1a37cdc..e8a9e58ef55 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -155,7 +155,7 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, char name[FN_REFLEN]; File file; TABLE *table= NULL; - int error= 0; + int error; String *field_term=ex->field_term,*escaped=ex->escaped; String *enclosed=ex->enclosed; bool is_fifo=0; @@ -454,7 +454,7 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, (!table->triggers || !table->triggers->has_delete_triggers())) table->file->extra(HA_EXTRA_WRITE_CAN_REPLACE); - if (!thd->prelocked_mode) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES) table->file->ha_start_bulk_insert((ha_rows) 0); table->copy_blobs=1; @@ -475,7 +475,8 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, error= read_sep_field(thd, info, table_list, fields_vars, set_fields, set_values, read_info, *enclosed, skip_lines, ignore); - if (!thd->prelocked_mode && table->file->ha_end_bulk_insert() && !error) + if (thd->locked_tables_mode <= LTM_LOCK_TABLES && + table->file->ha_end_bulk_insert() && !error) { table->file->print_error(my_errno, MYF(0)); error= 1; @@ -542,20 +543,18 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, { int errcode= query_error_code(thd, killed_status == THD::NOT_KILLED); - /* since there is already an error, the possible error of - writing binary log will be ignored */ if (thd->transaction.stmt.modified_non_trans_table) - (void) write_execute_load_query_log_event(thd, ex, - table_list->db, - table_list->table_name, - handle_duplicates, ignore, - transactional_table, - errcode); + write_execute_load_query_log_event(thd, ex, + table_list->db, + table_list->table_name, + handle_duplicates, ignore, + transactional_table, + errcode); else { Delete_file_log_event d(thd, db, transactional_table); d.flags|= LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F; - (void) mysql_bin_log.write(&d); + mysql_bin_log.write(&d); } } } @@ -582,7 +581,7 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, after this point. */ if (thd->current_stmt_binlog_row_based) - error= thd->binlog_flush_pending_rows_event(true); + thd->binlog_flush_pending_rows_event(true); else { /* @@ -594,15 +593,13 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, if (lf_info.wrote_create_file) { int errcode= query_error_code(thd, killed_status == THD::NOT_KILLED); - error= write_execute_load_query_log_event(thd, ex, - table_list->db, table_list->table_name, - handle_duplicates, ignore, - transactional_table, - errcode); + write_execute_load_query_log_event(thd, ex, + table_list->db, table_list->table_name, + handle_duplicates, ignore, + transactional_table, + errcode); } } - if (error) - goto err; } #endif /*!EMBEDDED_LIBRARY*/ diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 49d18cbe5d7..6d7c0013365 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -28,6 +28,7 @@ #include "sp_cache.h" #include "events.h" #include "sql_trigger.h" +#include "transaction.h" #include "sql_prepare.h" #include "probes_mysql.h" #include "set_var.h" @@ -89,127 +90,6 @@ const char *xa_state_names[]={ "NON-EXISTING", "ACTIVE", "IDLE", "PREPARED", "ROLLBACK ONLY" }; -/** - Mark a XA transaction as rollback-only if the RM unilaterally - rolled back the transaction branch. - - @note If a rollback was requested by the RM, this function sets - the appropriate rollback error code and transits the state - to XA_ROLLBACK_ONLY. - - @return TRUE if transaction was rolled back or if the transaction - state is XA_ROLLBACK_ONLY. FALSE otherwise. -*/ -static bool xa_trans_rolled_back(XID_STATE *xid_state) -{ - if (xid_state->rm_error) - { - switch (xid_state->rm_error) { - case ER_LOCK_WAIT_TIMEOUT: - my_error(ER_XA_RBTIMEOUT, MYF(0)); - break; - case ER_LOCK_DEADLOCK: - my_error(ER_XA_RBDEADLOCK, MYF(0)); - break; - default: - my_error(ER_XA_RBROLLBACK, MYF(0)); - } - xid_state->xa_state= XA_ROLLBACK_ONLY; - } - - return (xid_state->xa_state == XA_ROLLBACK_ONLY); -} - -/** - Rollback work done on behalf of at ransaction branch. -*/ -static bool xa_trans_rollback(THD *thd) -{ - /* - Resource Manager error is meaningless at this point, as we perform - explicit rollback request by user. We must reset rm_error before - calling ha_rollback(), so thd->transaction.xid structure gets reset - by ha_rollback()/THD::transaction::cleanup(). - */ - thd->transaction.xid_state.rm_error= 0; - - bool status= test(ha_rollback(thd)); - - thd->variables.option_bits&= ~(ulong) OPTION_BEGIN; - thd->transaction.all.modified_non_trans_table= FALSE; - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - xid_cache_delete(&thd->transaction.xid_state); - thd->transaction.xid_state.xa_state= XA_NOTR; - - return status; -} - -static void unlock_locked_tables(THD *thd) -{ - if (thd->locked_tables) - { - thd->lock=thd->locked_tables; - thd->locked_tables=0; // Will be automatically closed - close_thread_tables(thd); // Free tables - } -} - - -bool end_active_trans(THD *thd) -{ - int error=0; - DBUG_ENTER("end_active_trans"); - if (unlikely(thd->in_sub_stmt)) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - DBUG_RETURN(1); - } - if (thd->transaction.xid_state.xa_state != XA_NOTR) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - DBUG_RETURN(1); - } - if (thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN | - OPTION_TABLE_LOCK)) - { - DBUG_PRINT("info",("options: 0x%llx", thd->variables.option_bits)); - /* Safety if one did "drop table" on locked tables */ - if (!thd->locked_tables) - thd->variables.option_bits&= ~OPTION_TABLE_LOCK; - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - if (ha_commit(thd)) - error=1; - } - thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - DBUG_RETURN(error); -} - - -bool begin_trans(THD *thd) -{ - int error=0; - if (unlikely(thd->in_sub_stmt)) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - return 1; - } - if (thd->locked_tables) - { - thd->lock=thd->locked_tables; - thd->locked_tables=0; // Will be automatically closed - close_thread_tables(thd); // Free tables - } - if (end_active_trans(thd)) - error= -1; - else - { - thd->variables.option_bits|= OPTION_BEGIN; - thd->server_status|= SERVER_STATUS_IN_TRANS; - } - return error; -} #ifdef HAVE_REPLICATION /** @@ -236,6 +116,42 @@ static bool some_non_temp_table_to_be_updated(THD *thd, TABLE_LIST *tables) } +/* + Implicitly commit a active transaction if statement requires so. + + @param thd Thread handle. + @param mask Bitmask used for the SQL command match. + +*/ +static bool stmt_causes_implicit_commit(THD *thd, uint mask) +{ + LEX *lex= thd->lex; + bool skip= FALSE; + DBUG_ENTER("stmt_causes_implicit_commit"); + + if (!(sql_command_flags[lex->sql_command] & mask)) + DBUG_RETURN(FALSE); + + switch (lex->sql_command) { + case SQLCOM_DROP_TABLE: + skip= lex->drop_temporary; + break; + case SQLCOM_ALTER_TABLE: + case SQLCOM_CREATE_TABLE: + /* If CREATE TABLE of non-temporary table, do implicit commit */ + skip= (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE); + break; + case SQLCOM_SET_OPTION: + skip= lex->autocommit ? FALSE : TRUE; + break; + default: + break; + } + + DBUG_RETURN(!skip); +} + + /** Mark all commands that somehow changes a table. @@ -250,45 +166,67 @@ static bool some_non_temp_table_to_be_updated(THD *thd, TABLE_LIST *tables) */ uint sql_command_flags[SQLCOM_END+1]; +uint server_command_flags[COM_END+1]; void init_update_queries(void) { - bzero((uchar*) &sql_command_flags, sizeof(sql_command_flags)); - - sql_command_flags[SQLCOM_CREATE_TABLE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_CREATE_INDEX]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_ALTER_TABLE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND; - sql_command_flags[SQLCOM_TRUNCATE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND; - sql_command_flags[SQLCOM_DROP_TABLE]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_LOAD]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_RENAME_TABLE]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_DROP_INDEX]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_CREATE_VIEW]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_DROP_VIEW]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_CREATE_EVENT]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_ALTER_EVENT]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_DROP_EVENT]= CF_CHANGES_DATA; + /* Initialize the server command flags array. */ + memset(server_command_flags, 0, sizeof(server_command_flags)); + + server_command_flags[COM_STATISTICS]= CF_SKIP_QUERY_ID | CF_SKIP_QUESTIONS; + server_command_flags[COM_PING]= CF_SKIP_QUERY_ID | CF_SKIP_QUESTIONS; + server_command_flags[COM_STMT_PREPARE]= CF_SKIP_QUESTIONS; + server_command_flags[COM_STMT_CLOSE]= CF_SKIP_QUESTIONS; + server_command_flags[COM_STMT_RESET]= CF_SKIP_QUESTIONS; + + /* Initialize the sql command flags array. */ + memset(sql_command_flags, 0, sizeof(sql_command_flags)); + + sql_command_flags[SQLCOM_CREATE_TABLE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL; + sql_command_flags[SQLCOM_CREATE_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_TABLE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND | + CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL; + sql_command_flags[SQLCOM_TRUNCATE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND | + CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_LOAD]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_PROTECT_AGAINST_GRL; + sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_RENAME_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_VIEW]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_VIEW]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_TRIGGER]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_TRIGGER]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_TRIGGER]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_TRIGGER]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_UPDATE]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; + CF_REEXECUTION_FRAGILE | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_UPDATE_MULTI]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; + CF_REEXECUTION_FRAGILE | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_INSERT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; + CF_REEXECUTION_FRAGILE | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_INSERT_SELECT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; + CF_REEXECUTION_FRAGILE | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_DELETE]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; + CF_REEXECUTION_FRAGILE | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_DELETE_MULTI]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; + CF_REEXECUTION_FRAGILE | CF_PROTECT_AGAINST_GRL; sql_command_flags[SQLCOM_REPLACE]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_REPLACE_SELECT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SELECT]= CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_SET_OPTION]= CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SET_OPTION]= CF_REEXECUTION_FRAGILE | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_DO]= CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_STATUS_PROC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; @@ -303,34 +241,35 @@ void init_update_queries(void) sql_command_flags[SQLCOM_SHOW_VARIABLES]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_CHARSETS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_COLLATIONS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_SHOW_NEW_MASTER]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_BINLOGS]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_NEW_MASTER]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_BINLOGS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_SLAVE_HOSTS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_BINLOG_EVENTS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_STORAGE_ENGINES]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_AUTHORS]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_AUTHORS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CONTRIBUTORS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_PRIVILEGES]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_WARNS]= CF_STATUS_COMMAND | CF_DIAGNOSTIC_STMT; - sql_command_flags[SQLCOM_SHOW_ERRORS]= CF_STATUS_COMMAND | CF_DIAGNOSTIC_STMT; + sql_command_flags[SQLCOM_SHOW_PRIVILEGES]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_WARNS]= CF_STATUS_COMMAND | CF_DIAGNOSTIC_STMT; + sql_command_flags[SQLCOM_SHOW_ERRORS]= CF_STATUS_COMMAND | CF_DIAGNOSTIC_STMT; sql_command_flags[SQLCOM_SHOW_ENGINE_STATUS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_ENGINE_MUTEX]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_ENGINE_LOGS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_PROCESSLIST]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_GRANTS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_CREATE_DB]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_GRANTS]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_CREATE_DB]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CREATE]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_MASTER_STAT]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_MASTER_STAT]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_SLAVE_STAT]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_CREATE_PROC]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_CREATE_FUNC]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_CREATE_PROC]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_CREATE_FUNC]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CREATE_TRIGGER]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_STATUS_FUNC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_SHOW_PROC_CODE]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_FUNC_CODE]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_CREATE_EVENT]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_PROFILES]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_PROFILE]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_STATUS_FUNC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SHOW_PROC_CODE]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_FUNC_CODE]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_CREATE_EVENT]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_PROFILES]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_PROFILE]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_BINLOG_BASE64_EVENT]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_TABLES]= (CF_STATUS_COMMAND | CF_SHOW_TABLE_COMMAND | @@ -339,22 +278,53 @@ void init_update_queries(void) CF_SHOW_TABLE_COMMAND | CF_REEXECUTION_FRAGILE); + + sql_command_flags[SQLCOM_CREATE_USER]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; + sql_command_flags[SQLCOM_RENAME_USER]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; + sql_command_flags[SQLCOM_DROP_USER]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL; + sql_command_flags[SQLCOM_GRANT]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_REVOKE]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_REVOKE_ALL]= CF_PROTECT_AGAINST_GRL; + sql_command_flags[SQLCOM_OPTIMIZE]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_INSTALL_PLUGIN]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]= CF_CHANGES_DATA; + /* The following is used to preserver CF_ROW_COUNT during the a CALL or EXECUTE statement, so the value generated by the last called (or executed) statement is preserved. See mysql_execute_command() for how CF_ROW_COUNT is used. */ - sql_command_flags[SQLCOM_CALL]= CF_HAS_ROW_COUNT | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_EXECUTE]= CF_HAS_ROW_COUNT; + sql_command_flags[SQLCOM_CALL]= CF_HAS_ROW_COUNT | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_EXECUTE]= CF_HAS_ROW_COUNT; /* The following admin table operations are allowed on log tables. */ - sql_command_flags[SQLCOM_REPAIR]= CF_WRITE_LOGS_COMMAND; - sql_command_flags[SQLCOM_OPTIMIZE]= CF_WRITE_LOGS_COMMAND; - sql_command_flags[SQLCOM_ANALYZE]= CF_WRITE_LOGS_COMMAND; + sql_command_flags[SQLCOM_REPAIR]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_OPTIMIZE]|= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ANALYZE]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS; + + sql_command_flags[SQLCOM_CREATE_USER]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_USER]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_RENAME_USER]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_REVOKE_ALL]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_REVOKE]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_GRANT]|= CF_AUTO_COMMIT_TRANS; + + sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_PRELOAD_KEYS]= CF_AUTO_COMMIT_TRANS; + + sql_command_flags[SQLCOM_FLUSH]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CHECK]= CF_AUTO_COMMIT_TRANS; } @@ -633,80 +603,6 @@ void cleanup_items(Item *item) DBUG_VOID_RETURN; } -/** - Ends the current transaction and (maybe) begin the next. - - @param thd Current thread - @param completion Completion type - - @retval - 0 OK -*/ - -int end_trans(THD *thd, enum enum_mysql_completiontype completion) -{ - bool do_release= 0; - int res= 0; - DBUG_ENTER("end_trans"); - - if (unlikely(thd->in_sub_stmt)) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - DBUG_RETURN(1); - } - if (thd->transaction.xid_state.xa_state != XA_NOTR) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - DBUG_RETURN(1); - } - switch (completion) { - case COMMIT: - /* - We don't use end_active_trans() here to ensure that this works - even if there is a problem with the OPTION_AUTO_COMMIT flag - (Which of course should never happen...) - */ - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - res= ha_commit(thd); - thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - break; - case COMMIT_RELEASE: - do_release= 1; /* fall through */ - case COMMIT_AND_CHAIN: - res= end_active_trans(thd); - if (!res && completion == COMMIT_AND_CHAIN) - res= begin_trans(thd); - break; - case ROLLBACK_RELEASE: - do_release= 1; /* fall through */ - case ROLLBACK: - case ROLLBACK_AND_CHAIN: - { - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - if (ha_rollback(thd)) - res= -1; - thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - if (!res && (completion == ROLLBACK_AND_CHAIN)) - res= begin_trans(thd); - break; - } - default: - res= -1; - my_error(ER_UNKNOWN_COM_ERROR, MYF(0)); - DBUG_RETURN(-1); - } - - if (res < 0) - my_error(thd->killed_errno(), MYF(0)); - else if ((res == 0) && do_release) - thd->killed= THD::KILL_CONNECTION; - - DBUG_RETURN(res); -} - #ifndef EMBEDDED_LIBRARY /** @@ -926,29 +822,13 @@ bool dispatch_command(enum enum_server_command command, THD *thd, thd->enable_slow_log= TRUE; thd->lex->sql_command= SQLCOM_END; /* to avoid confusing VIEW detectors */ thd->set_time(); - { - query_id_t query_id; - switch( command ) { - /* Ignore these statements. */ - case COM_STATISTICS: - case COM_PING: - query_id= get_query_id(); - break; - /* Only increase id on these statements but don't count them. */ - case COM_STMT_PREPARE: - case COM_STMT_CLOSE: - case COM_STMT_RESET: - query_id= next_query_id() - 1; - break; - /* Increase id and count all other statements. */ - default: - statistic_increment(thd->status_var.questions, &LOCK_status); - query_id= next_query_id() - 1; - } - thd->set_query_id(query_id); - } + thd->set_query_id(get_query_id()); + if (!(server_command_flags[command] & CF_SKIP_QUERY_ID)) + next_query_id(); inc_thread_running(); - /* TODO: set thd->lex->sql_command to SQLCOM_END here */ + + if (!(server_command_flags[command] & CF_SKIP_QUESTIONS)) + statistic_increment(thd->status_var.questions, &LOCK_status); /** Clear the set of flags that are expected to be cleared at the @@ -1250,6 +1130,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, select_lex.table_list.link_in_list((uchar*) &table_list, (uchar**) &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 */ thd->lex->sql_command= SQLCOM_SHOW_FIELDS; @@ -1300,6 +1181,10 @@ bool dispatch_command(enum enum_server_command command, THD *thd, bool not_used; status_var_increment(thd->status_var.com_stat[SQLCOM_FLUSH]); 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; general_log_print(thd, command, NullS); @@ -1319,13 +1204,18 @@ bool dispatch_command(enum enum_server_command command, THD *thd, res= reload_acl_and_cache(NULL, options | REFRESH_FAST, NULL, ¬_used); my_pthread_setspecific_ptr(THR_THD, thd); - if (!res) - my_ok(thd); - break; + if (res) + break; } + else #endif - if (!reload_acl_and_cache(thd, options, NULL, ¬_used)) - my_ok(thd); + if (reload_acl_and_cache(thd, options, (TABLE_LIST*) 0, ¬_used)) + break; + if (trans_commit_implicit(thd)) + break; + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + my_ok(thd); break; } #ifndef EMBEDDED_LIBRARY @@ -1481,7 +1371,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, /* If commit fails, we should be able to reset the OK status. */ thd->stmt_da->can_overwrite_status= TRUE; - ha_autocommit_or_rollback(thd, thd->is_error()); + thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); thd->stmt_da->can_overwrite_status= FALSE; thd->transaction.stmt.reset(); @@ -1872,7 +1762,6 @@ int mysql_execute_command(THD *thd) { int res= FALSE; - bool need_start_waiting= FALSE; // have protection against global read lock int up_result= 0; LEX *lex= thd->lex; /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ @@ -2056,10 +1945,40 @@ mysql_execute_command(THD *thd) #ifdef HAVE_REPLICATION } /* endif unlikely slave */ #endif + status_var_increment(thd->status_var.com_stat[lex->sql_command]); DBUG_ASSERT(thd->transaction.stmt.modified_non_trans_table == FALSE); - + + /* + End a active transaction so that this command will have it's + own transaction and will also sync the binary log. If a DDL is + not run in it's own transaction it may simply never appear on + the slave in case the outside transaction rolls back. + */ + if (stmt_causes_implicit_commit(thd, CF_IMPLICT_COMMIT_BEGIN)) + { + /* 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. */ + if (trans_commit_implicit(thd)) + goto error; + /* Close tables and release metadata locks. */ + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + } + + /* + Check if this command needs protection against the global read lock + to avoid deadlock. See CF_PROTECT_AGAINST_GRL. + start_waiting_global_read_lock() is called at the end of + mysql_execute_command(). + */ + if (((sql_command_flags[lex->sql_command] & CF_PROTECT_AGAINST_GRL) != 0) && + !thd->locked_tables_mode) + if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) + goto error; + switch (lex->sql_command) { case SQLCOM_SHOW_EVENTS: @@ -2069,9 +1988,10 @@ mysql_execute_command(THD *thd) #endif case SQLCOM_SHOW_STATUS_PROC: case SQLCOM_SHOW_STATUS_FUNC: - if (!(res= check_table_access(thd, SELECT_ACL, all_tables, FALSE, + if ((res= check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE))) - res= execute_sqlcom_select(thd, all_tables); + goto error; + res= execute_sqlcom_select(thd, all_tables); break; case SQLCOM_SHOW_STATUS: { @@ -2128,8 +2048,8 @@ mysql_execute_command(THD *thd) if (res) break; - if (!thd->locked_tables && lex->protect_against_global_read_lock && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + if (!thd->locked_tables_mode && lex->protect_against_global_read_lock && + thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) break; res= execute_sqlcom_select(thd, all_tables); @@ -2336,20 +2256,11 @@ case SQLCOM_PREPARE: } case SQLCOM_CREATE_TABLE: { - /* If CREATE TABLE of non-temporary table, do implicit commit */ - if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)) - { - if (end_active_trans(thd)) - { - res= -1; - break; - } - } DBUG_ASSERT(first_table == all_tables && first_table != 0); bool link_to_local; - // Skip first table, which is the table we are creating - TABLE_LIST *create_table= lex->unlink_first_table(&link_to_local); - TABLE_LIST *select_tables= lex->query_tables; + TABLE_LIST *create_table= first_table; + TABLE_LIST *select_tables= lex->create_last_non_select_table->next_global; + /* Code below (especially in mysql_create_table() and select_create methods) may modify HA_CREATE_INFO structure in LEX, so we have to @@ -2410,14 +2321,10 @@ case SQLCOM_PREPARE: read lock when it succeeds. This needs to be released by start_waiting_global_read_lock(). We protect the normal CREATE TABLE in the same way. That way we avoid that a new table is - created during a gobal read lock. + created during a global read lock. + Protection against grl is covered by the CF_PROTECT_AGAINST_GRL flag. */ - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - { - res= 1; - goto end_with_restore_list; - } + #ifdef WITH_PARTITION_STORAGE_ENGINE { partition_info *part_info= thd->lex->part_info; @@ -2429,6 +2336,16 @@ case SQLCOM_PREPARE: thd->work_part_info= part_info; } #endif + + /* Set strategies: reset default or 'prepared' values. */ + create_table->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; + create_table->lock_strategy= TABLE_LIST::EXCLUSIVE_DOWNGRADABLE_MDL; + + /* + Close any open handlers for the table + */ + mysql_ha_rm_tables(thd, create_table); + if (select_lex->item_list.elements) // With select { select_result *result; @@ -2486,13 +2403,7 @@ case SQLCOM_PREPARE: goto end_with_restore_list; } - if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) - { - lex->link_first_table_back(create_table, link_to_local); - create_table->create= TRUE; - } - - if (!(res= open_and_lock_tables(thd, lex->query_tables))) + if (!(res= open_and_lock_tables_derived(thd, lex->query_tables, TRUE, 0))) { /* Is table which we are changing used somewhere in other parts @@ -2501,7 +2412,6 @@ case SQLCOM_PREPARE: if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) { TABLE_LIST *duplicate; - create_table= lex->unlink_first_table(&link_to_local); if ((duplicate= unique_table(thd, create_table, select_tables, 0))) { update_non_unique_table_error(create_table, "CREATE", duplicate); @@ -2528,6 +2438,13 @@ case SQLCOM_PREPARE: } /* + Remove target table from main select and name resolution + context. This can't be done earlier as it will break view merging in + statements like "CREATE TABLE IF NOT EXISTS existing_view SELECT". + */ + lex->unlink_first_table(&link_to_local); + + /* select_create is currently not re-execution friendly and needs to be created for every execution of a PS/SP. */ @@ -2546,10 +2463,9 @@ case SQLCOM_PREPARE: res= handle_select(thd, lex, result, 0); delete result; } + + lex->link_first_table_back(create_table, link_to_local); } - else if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) - create_table= lex->unlink_first_table(&link_to_local); - } else { @@ -2558,21 +2474,22 @@ case SQLCOM_PREPARE: thd->variables.option_bits|= OPTION_KEEP_LOG; /* regular create */ if (create_info.options & HA_LEX_CREATE_TABLE_LIKE) + { + /* CREATE TABLE ... LIKE ... */ res= mysql_create_like_table(thd, create_table, select_tables, &create_info); + } else { - res= mysql_create_table(thd, create_table->db, - create_table->table_name, &create_info, - &alter_info, 0, 0); + /* Regular CREATE TABLE */ + res= mysql_create_table(thd, create_table, + &create_info, &alter_info); } if (!res) - my_ok(thd); + my_ok(thd); } - /* put tables back for PS rexecuting */ end_with_restore_list: - lex->link_first_table_back(create_table, link_to_local); break; } case SQLCOM_CREATE_INDEX: @@ -2597,8 +2514,6 @@ end_with_restore_list: DBUG_ASSERT(first_table == all_tables && first_table != 0); if (check_one_table_access(thd, INDEX_ACL, all_tables)) goto error; /* purecov: inspected */ - if (end_active_trans(thd)) - goto error; /* Currently CREATE INDEX or DROP INDEX cause a full table rebuild and thus classify as slow administrative statements just like @@ -2638,7 +2553,8 @@ end_with_restore_list: To prevent that, refuse SLAVE STOP if the client thread has locked tables */ - if (thd->locked_tables || thd->active_transaction() || thd->global_read_lock) + if (thd->locked_tables_mode || + thd->active_transaction() || thd->global_read_lock.is_acquired()) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); @@ -2713,16 +2629,6 @@ end_with_restore_list: WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), "INDEX DIRECTORY"); create_info.data_file_name= create_info.index_file_name= NULL; - /* ALTER TABLE ends previous transaction */ - if (end_active_trans(thd)) - goto error; - - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - { - res= 1; - break; - } thd->enable_slow_log= opt_log_slow_admin_statements; res= mysql_alter_table(thd, select_lex->db, lex->name.str, @@ -2764,7 +2670,7 @@ end_with_restore_list: goto error; } - if (end_active_trans(thd) || mysql_rename_tables(thd, first_table, 0)) + if (mysql_rename_tables(thd, first_table, 0)) goto error; break; } @@ -2811,7 +2717,8 @@ end_with_restore_list: } /* Ignore temporary tables if this is "SHOW CREATE VIEW" */ - first_table->skip_temporary= 1; + first_table->open_type= OT_BASE_ONLY; + } else { @@ -2859,7 +2766,7 @@ end_with_restore_list: /* Presumably, REPAIR and binlog writing doesn't require synchronization */ - res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } select_lex->table_list.first= (uchar*) first_table; lex->query_tables=all_tables; @@ -2891,7 +2798,7 @@ end_with_restore_list: /* Presumably, ANALYZE and binlog writing doesn't require synchronization */ - res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } select_lex->table_list.first= (uchar*) first_table; lex->query_tables=all_tables; @@ -2914,7 +2821,7 @@ end_with_restore_list: /* Presumably, OPTIMIZE and binlog writing doesn't require synchronization */ - res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } select_lex->table_list.first= (uchar*) first_table; lex->query_tables=all_tables; @@ -2926,9 +2833,6 @@ end_with_restore_list: DBUG_ASSERT(first_table == all_tables && first_table != 0); if (update_precheck(thd, all_tables)) break; - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - goto error; DBUG_ASSERT(select_lex->offset_limit == 0); unit->set_limit(select_lex); MYSQL_UPDATE_START(thd->query()); @@ -2959,15 +2863,6 @@ end_with_restore_list: else res= 0; - /* - Protection might have already been risen if its a fall through - from the SQLCOM_UPDATE case above. - */ - if (!thd->locked_tables && - lex->sql_command == SQLCOM_UPDATE_MULTI && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - goto error; - res= mysql_multi_update_prepare(thd); #ifdef HAVE_REPLICATION @@ -3055,7 +2950,7 @@ end_with_restore_list: if (incident) { Incident_log_event ev(thd, incident); - (void) mysql_bin_log.write(&ev); /* error is ignored */ + mysql_bin_log.write(&ev); mysql_bin_log.rotate_and_purge(RP_FORCE_ROTATE); } DBUG_PRINT("debug", ("Just after generate_incident()")); @@ -3067,12 +2962,6 @@ end_with_restore_list: if ((res= insert_precheck(thd, all_tables))) break; - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - { - res= 1; - break; - } MYSQL_INSERT_START(thd->query()); res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values, lex->update_list, lex->value_list, @@ -3107,12 +2996,6 @@ end_with_restore_list: unit->set_limit(select_lex); - if (! thd->locked_tables && - ! (need_start_waiting= ! wait_if_global_read_lock(thd, 0, 1))) - { - res= 1; - break; - } if (!(res= open_and_lock_tables(thd, all_tables))) { MYSQL_INSERT_SELECT_START(thd->query()); @@ -3165,11 +3048,6 @@ end_with_restore_list: break; } case SQLCOM_TRUNCATE: - if (end_active_trans(thd)) - { - res= -1; - break; - } DBUG_ASSERT(first_table == all_tables && first_table != 0); if (check_one_table_access(thd, DROP_ACL, all_tables)) goto error; @@ -3177,13 +3055,13 @@ end_with_restore_list: Don't allow this within a transaction because we want to use re-generate table */ - if (thd->locked_tables || thd->active_transaction()) + if (thd->active_transaction()) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); goto error; } - if (!(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) goto error; res= mysql_truncate(thd, first_table, 0); break; @@ -3195,12 +3073,6 @@ end_with_restore_list: DBUG_ASSERT(select_lex->offset_limit == 0); unit->set_limit(select_lex); - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - { - res= 1; - break; - } MYSQL_DELETE_START(thd->query()); res = mysql_delete(thd, all_tables, select_lex->where, &select_lex->order_list, @@ -3216,13 +3088,6 @@ end_with_restore_list: (TABLE_LIST *)thd->lex->auxiliary_table_list.first; multi_delete *del_result; - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - { - res= 1; - break; - } - if ((res= multi_delete_precheck(thd, all_tables))) break; @@ -3277,8 +3142,6 @@ end_with_restore_list: { if (check_table_access(thd, DROP_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; /* purecov: inspected */ - if (end_active_trans(thd)) - goto error; } else { @@ -3363,10 +3226,6 @@ end_with_restore_list: if (check_one_table_access(thd, privilege, all_tables)) goto error; - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - goto error; - res= mysql_load(thd, lex->exchange, first_table, lex->field_list, lex->update_list, lex->value_list, lex->duplicates, lex->ignore, (bool) lex->local_file); @@ -3377,9 +3236,6 @@ end_with_restore_list: { List<set_var_base> *lex_var_list= &lex->var_list; - if (lex->autocommit && end_active_trans(thd)) - goto error; - if ((check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE) || open_and_lock_tables(thd, all_tables))) goto error; @@ -3414,51 +3270,81 @@ end_with_restore_list: done FLUSH TABLES WITH READ LOCK + BEGIN. If this assumption becomes false, mysqldump will not work. */ - unlock_locked_tables(thd); + thd->locked_tables_list.unlock_locked_tables(thd); if (thd->variables.option_bits & OPTION_TABLE_LOCK) { - end_active_trans(thd); - thd->variables.option_bits&= ~OPTION_TABLE_LOCK; + trans_commit_implicit(thd); + thd->mdl_context.release_transactional_locks(); + thd->variables.option_bits&= ~(OPTION_TABLE_LOCK); } - if (thd->global_read_lock) - unlock_global_read_lock(thd); + if (thd->global_read_lock.is_acquired()) + thd->global_read_lock.unlock_global_read_lock(thd); my_ok(thd); break; case SQLCOM_LOCK_TABLES: - unlock_locked_tables(thd); + thd->locked_tables_list.unlock_locked_tables(thd); + /* + As of 5.5, entering LOCK TABLES mode implicitly closes all + open HANDLERs. Both HANDLER code and LOCK TABLES mode use + the sentinel mechanism in MDL subsystem and thus could not be + used at the same time. All HANDLER operations are prohibited + under LOCK TABLES anyway. + */ + mysql_ha_cleanup(thd); /* we must end the trasaction first, regardless of anything */ - if (end_active_trans(thd)) + if (trans_commit_implicit(thd)) goto error; + /* release transactional metadata locks. */ + thd->mdl_context.release_transactional_locks(); if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; if (lex->protect_against_global_read_lock && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) goto error; + + init_mdl_requests(all_tables); + + thd->variables.option_bits|= OPTION_TABLE_LOCK; thd->in_lock_tables=1; - thd->variables.option_bits |= OPTION_TABLE_LOCK; - if (!(res= simple_open_n_lock_tables(thd, all_tables))) { -#ifdef HAVE_QUERY_CACHE - if (thd->variables.query_cache_wlock_invalidate) - query_cache.invalidate_locked_for_write(first_table); -#endif /*HAVE_QUERY_CACHE*/ - thd->locked_tables=thd->lock; - thd->lock=0; - my_ok(thd); + Lock_tables_prelocking_strategy lock_tables_prelocking_strategy; + + res= (open_and_lock_tables_derived(thd, all_tables, FALSE, + MYSQL_OPEN_TAKE_UPGRADABLE_MDL, + &lock_tables_prelocking_strategy) || + thd->locked_tables_list.init_locked_tables(thd)); } - else + + thd->in_lock_tables= 0; + + if (res) { - /* + /* 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 */ - ha_autocommit_or_rollback(thd, 1); - end_active_trans(thd); + 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_thread_tables(thd); + DBUG_ASSERT(!thd->locked_tables_mode); + thd->mdl_context.release_transactional_locks(); + thd->variables.option_bits&= ~(OPTION_TABLE_LOCK); + } + else + { +#ifdef HAVE_QUERY_CACHE + if (thd->variables.query_cache_wlock_invalidate) + query_cache.invalidate_locked_for_write(first_table); +#endif /*HAVE_QUERY_CACHE*/ + my_ok(thd); } - thd->in_lock_tables=0; break; case SQLCOM_CREATE_DB: { @@ -3468,11 +3354,6 @@ end_with_restore_list: prepared statement- safe. */ HA_CREATE_INFO create_info(lex->create_info); - if (end_active_trans(thd)) - { - res= -1; - break; - } char *alias; if (!(alias=thd->strmake(lex->name.str, lex->name.length)) || check_db_name(&lex->name)) @@ -3498,17 +3379,18 @@ end_with_restore_list: #endif if (check_access(thd, CREATE_ACL, lex->name.str, NULL, NULL, 1, 0)) break; + if (thd->locked_tables_mode) + { + my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, + ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); + goto error; + } res= mysql_create_db(thd,(lower_case_table_names == 2 ? alias : lex->name.str), &create_info, 0); break; } case SQLCOM_DROP_DB: { - if (end_active_trans(thd)) - { - res= -1; - break; - } if (check_db_name(&lex->name)) { my_error(ER_WRONG_DB_NAME, MYF(0), lex->name.str); @@ -3532,7 +3414,7 @@ end_with_restore_list: #endif if (check_access(thd, DROP_ACL, lex->name.str, NULL, NULL, 1, 0)) break; - if (thd->locked_tables || thd->active_transaction()) + if (thd->locked_tables_mode) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); @@ -3544,11 +3426,6 @@ end_with_restore_list: case SQLCOM_ALTER_DB_UPGRADE: { LEX_STRING *db= & lex->name; - if (end_active_trans(thd)) - { - res= 1; - break; - } #ifdef HAVE_REPLICATION if (thd->slave_thread && (!rpl_filter->db_ok(db->str) || @@ -3571,7 +3448,7 @@ end_with_restore_list: res= 1; break; } - if (thd->locked_tables || thd->active_transaction()) + if (thd->locked_tables_mode) { res= 1; my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, @@ -3611,7 +3488,7 @@ end_with_restore_list: #endif if (check_access(thd, ALTER_ACL, db->str, NULL, NULL, 1, 0)) break; - if (thd->locked_tables || thd->active_transaction()) + if (thd->locked_tables_mode) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); @@ -3711,8 +3588,6 @@ end_with_restore_list: if (check_access(thd, INSERT_ACL, "mysql", NULL, NULL, 1, 1) && check_global_access(thd,CREATE_USER_ACL)) break; - if (end_active_trans(thd)) - goto error; /* Conditionally writes to binlog */ if (!(res= mysql_create_user(thd, lex->users_list))) my_ok(thd); @@ -3723,8 +3598,6 @@ end_with_restore_list: if (check_access(thd, DELETE_ACL, "mysql", NULL, NULL, 1, 1) && check_global_access(thd,CREATE_USER_ACL)) break; - if (end_active_trans(thd)) - goto error; /* Conditionally writes to binlog */ if (!(res= mysql_drop_user(thd, lex->users_list))) my_ok(thd); @@ -3735,8 +3608,6 @@ end_with_restore_list: if (check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 1) && check_global_access(thd,CREATE_USER_ACL)) break; - if (end_active_trans(thd)) - goto error; /* Conditionally writes to binlog */ if (!(res= mysql_rename_user(thd, lex->users_list))) my_ok(thd); @@ -3744,8 +3615,6 @@ end_with_restore_list: } case SQLCOM_REVOKE_ALL: { - if (end_active_trans(thd)) - goto error; if (check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 1) && check_global_access(thd,CREATE_USER_ACL)) break; @@ -3757,9 +3626,6 @@ end_with_restore_list: case SQLCOM_REVOKE: case SQLCOM_GRANT: { - if (end_active_trans(thd)) - goto error; - if (check_access(thd, lex->grant | lex->grant_tot_col | GRANT_ACL, first_table ? first_table->db : select_lex->db, first_table ? &first_table->grant.privilege : NULL, @@ -3887,8 +3753,7 @@ end_with_restore_list: */ if (!lex->no_write_to_binlog && write_to_binlog) { - if (res= write_bin_log(thd, FALSE, thd->query(), thd->query_length())) - break; + write_bin_log(thd, FALSE, thd->query(), thd->query_length()); } my_ok(thd); } @@ -3954,128 +3819,52 @@ end_with_restore_list: break; case SQLCOM_BEGIN: - if (thd->transaction.xid_state.xa_state != XA_NOTR) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (begin_trans(thd)) + if (trans_begin(thd, lex->start_transaction_opt)) goto error; - if (lex->start_transaction_opt & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT) - { - if (ha_start_consistent_snapshot(thd)) - goto error; - } my_ok(thd); break; case SQLCOM_COMMIT: - if (end_trans(thd, lex->tx_release ? COMMIT_RELEASE : - lex->tx_chain ? COMMIT_AND_CHAIN : COMMIT)) + DBUG_ASSERT(thd->lock == NULL || + thd->locked_tables_mode == LTM_LOCK_TABLES); + if (trans_commit(thd)) + goto error; + thd->mdl_context.release_transactional_locks(); + /* Begin transaction with the same isolation level. */ + if (lex->tx_chain && trans_begin(thd)) goto error; + /* Disconnect the current client connection. */ + if (lex->tx_release) + thd->killed= THD::KILL_CONNECTION; my_ok(thd); break; case SQLCOM_ROLLBACK: - if (end_trans(thd, lex->tx_release ? ROLLBACK_RELEASE : - lex->tx_chain ? ROLLBACK_AND_CHAIN : ROLLBACK)) + DBUG_ASSERT(thd->lock == NULL || + thd->locked_tables_mode == LTM_LOCK_TABLES); + if (trans_rollback(thd)) goto error; + thd->mdl_context.release_transactional_locks(); + /* Begin transaction with the same isolation level. */ + if (lex->tx_chain && trans_begin(thd)) + goto error; + /* Disconnect the current client connection. */ + if (lex->tx_release) + thd->killed= THD::KILL_CONNECTION; my_ok(thd); break; case SQLCOM_RELEASE_SAVEPOINT: - { - SAVEPOINT *sv; - for (sv=thd->transaction.savepoints; sv; sv=sv->prev) - { - if (my_strnncoll(system_charset_info, - (uchar *)lex->ident.str, lex->ident.length, - (uchar *)sv->name, sv->length) == 0) - break; - } - if (sv) - { - if (ha_release_savepoint(thd, sv)) - res= TRUE; // cannot happen - else - my_ok(thd); - thd->transaction.savepoints=sv->prev; - } - else - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", lex->ident.str); + if (trans_release_savepoint(thd, lex->ident)) + goto error; + my_ok(thd); break; - } case SQLCOM_ROLLBACK_TO_SAVEPOINT: - { - SAVEPOINT *sv; - for (sv=thd->transaction.savepoints; sv; sv=sv->prev) - { - if (my_strnncoll(system_charset_info, - (uchar *)lex->ident.str, lex->ident.length, - (uchar *)sv->name, sv->length) == 0) - break; - } - if (sv) - { - if (ha_rollback_to_savepoint(thd, sv)) - res= TRUE; // cannot happen - else - { - if (((thd->variables.option_bits & OPTION_KEEP_LOG) || - thd->transaction.all.modified_non_trans_table) && - !thd->slave_thread) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARNING_NOT_COMPLETE_ROLLBACK, - ER(ER_WARNING_NOT_COMPLETE_ROLLBACK)); - my_ok(thd); - } - thd->transaction.savepoints=sv; - } - else - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", lex->ident.str); + if (trans_rollback_to_savepoint(thd, lex->ident)) + goto error; + my_ok(thd); break; - } case SQLCOM_SAVEPOINT: - if (!(thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN) || - thd->in_sub_stmt) || !opt_using_transactions) - my_ok(thd); - else - { - SAVEPOINT **sv, *newsv; - for (sv=&thd->transaction.savepoints; *sv; sv=&(*sv)->prev) - { - if (my_strnncoll(system_charset_info, - (uchar *)lex->ident.str, lex->ident.length, - (uchar *)(*sv)->name, (*sv)->length) == 0) - break; - } - if (*sv) /* old savepoint of the same name exists */ - { - newsv=*sv; - ha_release_savepoint(thd, *sv); // it cannot fail - *sv=(*sv)->prev; - } - else if ((newsv=(SAVEPOINT *) alloc_root(&thd->transaction.mem_root, - savepoint_alloc_size)) == 0) - { - my_error(ER_OUT_OF_RESOURCES, MYF(0)); - break; - } - newsv->name=strmake_root(&thd->transaction.mem_root, - lex->ident.str, lex->ident.length); - newsv->length=lex->ident.length; - /* - if we'll get an error here, don't add new savepoint to the list. - we'll lose a little bit of memory in transaction mem_root, but it'll - be free'd when transaction ends anyway - */ - if (ha_savepoint(thd, newsv)) - res= TRUE; - else - { - newsv->prev=thd->transaction.savepoints; - thd->transaction.savepoints=newsv; - my_ok(thd); - } - } + if (trans_savepoint(thd, lex->ident)) + goto error; + my_ok(thd); break; case SQLCOM_CREATE_PROCEDURE: case SQLCOM_CREATE_SPFUNCTION: @@ -4111,9 +3900,6 @@ end_with_restore_list: NULL, NULL, 0, 0)) goto create_sp_error; - if (end_active_trans(thd)) - goto create_sp_error; - name= lex->sphead->name(&namelen); #ifdef HAVE_DLOPEN if (lex->sphead->m_type == TYPE_ENUM_FUNCTION) @@ -4131,7 +3917,7 @@ end_with_restore_list: if (sp_process_definer(thd)) goto create_sp_error; - res= (sp_result= lex->sphead->create(thd)); + res= (sp_result= sp_create_routine(thd, lex->sphead->m_type, lex->sphead)); switch (sp_result) { case SP_OK: { #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -4142,6 +3928,16 @@ end_with_restore_list: Security_context *backup= NULL; LEX_USER *definer= thd->lex->definer; /* + We're going to issue an implicit GRANT statement. + It takes metadata locks and updates system tables. + Make sure that sp_create_routine() did not leave any + 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(); + /* Check if the definer exists on slave, then use definer privilege to insert routine privileges to mysql.procs_priv. @@ -4213,7 +4009,6 @@ create_sp_error: case SQLCOM_CALL: { sp_head *sp; - /* This will cache all SP and SF and open and lock all tables required for execution. @@ -4309,67 +4104,22 @@ create_sp_error: case SQLCOM_ALTER_FUNCTION: { int sp_result; - sp_head *sp; - st_sp_chistics chistics; - - memcpy(&chistics, &lex->sp_chistics, sizeof(chistics)); - if (lex->sql_command == SQLCOM_ALTER_PROCEDURE) - sp= sp_find_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname, - &thd->sp_proc_cache, FALSE); - else - sp= sp_find_routine(thd, TYPE_ENUM_FUNCTION, lex->spname, - &thd->sp_func_cache, FALSE); - thd->warning_info->opt_clear_warning_info(thd->query_id); - if (! sp) - { - if (lex->spname->m_db.str) - sp_result= SP_KEY_NOT_FOUND; - else - { - my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); - goto error; - } - } - else - { - if (check_routine_access(thd, ALTER_PROC_ACL, sp->m_db.str, - sp->m_name.str, - lex->sql_command == SQLCOM_ALTER_PROCEDURE, 0)) - goto error; - - if (end_active_trans(thd)) - goto error; - memcpy(&lex->sp_chistics, &chistics, sizeof(lex->sp_chistics)); - if ((sp->m_type == TYPE_ENUM_FUNCTION) && - !trust_function_creators && mysql_bin_log.is_open() && - !sp->m_chistics->detistic && - (chistics.daccess == SP_CONTAINS_SQL || - chistics.daccess == SP_MODIFIES_SQL_DATA)) - { - my_message(ER_BINLOG_UNSAFE_ROUTINE, - ER(ER_BINLOG_UNSAFE_ROUTINE), MYF(0)); - sp_result= SP_INTERNAL_ERROR; - } - else - { - /* - Note that if you implement the capability of ALTER FUNCTION to - alter the body of the function, this command should be made to - follow the restrictions that log-bin-trust-function-creators=0 - already puts on CREATE FUNCTION. - */ - /* Conditionally writes to binlog */ + int type= (lex->sql_command == SQLCOM_ALTER_PROCEDURE ? + TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION); - int type= lex->sql_command == SQLCOM_ALTER_PROCEDURE ? - TYPE_ENUM_PROCEDURE : - TYPE_ENUM_FUNCTION; + if (check_routine_access(thd, ALTER_PROC_ACL, lex->spname->m_db.str, + lex->spname->m_name.str, + lex->sql_command == SQLCOM_ALTER_PROCEDURE, 0)) + goto error; - sp_result= sp_update_routine(thd, - type, - lex->spname, - &lex->sp_chistics); - } - } + /* + Note that if you implement the capability of ALTER FUNCTION to + alter the body of the function, this command should be made to + follow the restrictions that log-bin-trust-function-creators=0 + already puts on CREATE FUNCTION. + */ + /* Conditionally writes to binlog */ + sp_result= sp_update_routine(thd, type, lex->spname, &lex->sp_chistics); switch (sp_result) { case SP_OK: @@ -4393,6 +4143,12 @@ create_sp_error: int type= (lex->sql_command == SQLCOM_DROP_PROCEDURE ? TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION); + /* + @todo: here we break the metadata locking protocol by + looking up the information about the routine without + a metadata lock. Rewrite this piece to make sp_drop_routine + return whether the routine existed or not. + */ sp_result= sp_routine_exists_in_table(thd, type, lex->spname); thd->warning_info->opt_clear_warning_info(thd->query_id); if (sp_result == SP_OK) @@ -4404,25 +4160,30 @@ create_sp_error: lex->sql_command == SQLCOM_DROP_PROCEDURE, 0)) goto error; - if (end_active_trans(thd)) - goto error; + /* Conditionally writes to binlog */ + sp_result= sp_drop_routine(thd, type, lex->spname); + #ifndef NO_EMBEDDED_ACCESS_CHECKS + /* + We're going to issue an implicit REVOKE statement. + It takes metadata locks and updates system tables. + Make sure that sp_create_routine() did not leave any + 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(); + if (sp_automatic_privileges && !opt_noacl && - sp_revoke_privileges(thd, db, name, + sp_revoke_privileges(thd, db, name, lex->sql_command == SQLCOM_DROP_PROCEDURE)) { - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_PROC_AUTO_REVOKE_FAIL, ER(ER_PROC_AUTO_REVOKE_FAIL)); } #endif - /* Conditionally writes to binlog */ - - int type= lex->sql_command == SQLCOM_DROP_PROCEDURE ? - TYPE_ENUM_PROCEDURE : - TYPE_ENUM_FUNCTION; - - sp_result= sp_drop_routine(thd, type, lex->spname); } else { @@ -4460,12 +4221,12 @@ create_sp_error: case SP_KEY_NOT_FOUND: if (lex->drop_if_exists) { - res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST), SP_COM_STRING(lex), lex->spname->m_name.str); - if (!res) - my_ok(thd); + res= FALSE; + my_ok(thd); break; } my_error(ER_SP_DOES_NOT_EXIST, MYF(0), @@ -4481,21 +4242,13 @@ create_sp_error: case SQLCOM_SHOW_CREATE_PROC: { if (sp_show_create_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname)) - { - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), - SP_COM_STRING(lex), lex->spname->m_name.str); - goto error; - } + goto error; break; } case SQLCOM_SHOW_CREATE_FUNC: { if (sp_show_create_routine(thd, TYPE_ENUM_FUNCTION, lex->spname)) - { - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), - SP_COM_STRING(lex), lex->spname->m_name.str); goto error; - } break; } case SQLCOM_SHOW_PROC_CODE: @@ -4503,13 +4256,11 @@ create_sp_error: { #ifndef DBUG_OFF sp_head *sp; + int type= (lex->sql_command == SQLCOM_SHOW_PROC_CODE ? + TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION); - if (lex->sql_command == SQLCOM_SHOW_PROC_CODE) - sp= sp_find_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname, - &thd->sp_proc_cache, FALSE); - else - sp= sp_find_routine(thd, TYPE_ENUM_FUNCTION, lex->spname, - &thd->sp_func_cache, FALSE); + if (sp_cache_routine(thd, type, lex->spname, FALSE, &sp)) + goto error; if (!sp || sp->show_routine_code(thd)) { /* We don't distinguish between errors for now */ @@ -4543,16 +4294,12 @@ create_sp_error: Note: SQLCOM_CREATE_VIEW also handles 'ALTER VIEW' commands as specified through the thd->lex->create_view_mode flag. */ - if (end_active_trans(thd)) - goto error; - res= mysql_create_view(thd, first_table, thd->lex->create_view_mode); break; } case SQLCOM_DROP_VIEW: { - if (check_table_access(thd, DROP_ACL, all_tables, FALSE, UINT_MAX, FALSE) - || end_active_trans(thd)) + if (check_table_access(thd, DROP_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; /* Conditionally writes to binlog. */ res= mysql_drop_view(thd, first_table, thd->lex->drop_mode); @@ -4560,9 +4307,6 @@ create_sp_error: } case SQLCOM_CREATE_TRIGGER: { - if (end_active_trans(thd)) - goto error; - /* Conditionally writes to binlog. */ res= mysql_create_or_drop_trigger(thd, all_tables, 1); @@ -4570,193 +4314,36 @@ create_sp_error: } case SQLCOM_DROP_TRIGGER: { - if (end_active_trans(thd)) - goto error; - /* Conditionally writes to binlog. */ res= mysql_create_or_drop_trigger(thd, all_tables, 0); break; } case SQLCOM_XA_START: - if (thd->transaction.xid_state.xa_state == XA_IDLE && - thd->lex->xa_opt == XA_RESUME) - { - if (! thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - my_error(ER_XAER_NOTA, MYF(0)); - break; - } - thd->transaction.xid_state.xa_state=XA_ACTIVE; - my_ok(thd); - break; - } - if (thd->lex->xa_opt != XA_NONE) - { /// @todo JOIN is not supported yet. - my_error(ER_XAER_INVAL, MYF(0)); - break; - } - if (thd->transaction.xid_state.xa_state != XA_NOTR) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (thd->active_transaction() || thd->locked_tables) - { - my_error(ER_XAER_OUTSIDE, MYF(0)); - break; - } - if (xid_cache_search(thd->lex->xid)) - { - my_error(ER_XAER_DUPID, MYF(0)); - break; - } - DBUG_ASSERT(thd->transaction.xid_state.xid.is_null()); - thd->transaction.xid_state.xa_state=XA_ACTIVE; - thd->transaction.xid_state.rm_error= 0; - thd->transaction.xid_state.xid.set(thd->lex->xid); - xid_cache_insert(&thd->transaction.xid_state); - thd->transaction.all.modified_non_trans_table= FALSE; - thd->variables.option_bits= ((thd->variables.option_bits & ~(OPTION_KEEP_LOG)) | OPTION_BEGIN); - thd->server_status|= SERVER_STATUS_IN_TRANS; + if (trans_xa_start(thd)) + goto error; my_ok(thd); break; case SQLCOM_XA_END: - /* fake it */ - if (thd->lex->xa_opt != XA_NONE) - { /// @todo SUSPEND and FOR MIGRATE are not supported yet. - my_error(ER_XAER_INVAL, MYF(0)); - break; - } - if (thd->transaction.xid_state.xa_state != XA_ACTIVE) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - my_error(ER_XAER_NOTA, MYF(0)); - break; - } - if (xa_trans_rolled_back(&thd->transaction.xid_state)) - break; - thd->transaction.xid_state.xa_state=XA_IDLE; + if (trans_xa_end(thd)) + goto error; my_ok(thd); break; case SQLCOM_XA_PREPARE: - if (thd->transaction.xid_state.xa_state != XA_IDLE) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - my_error(ER_XAER_NOTA, MYF(0)); - break; - } - if (ha_prepare(thd)) - { - my_error(ER_XA_RBROLLBACK, MYF(0)); - xid_cache_delete(&thd->transaction.xid_state); - thd->transaction.xid_state.xa_state=XA_NOTR; - break; - } - thd->transaction.xid_state.xa_state=XA_PREPARED; + if (trans_xa_prepare(thd)) + goto error; my_ok(thd); break; case SQLCOM_XA_COMMIT: - if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - XID_STATE *xs=xid_cache_search(thd->lex->xid); - if (!xs || xs->in_thd) - my_error(ER_XAER_NOTA, MYF(0)); - else if (xa_trans_rolled_back(xs)) - { - ha_commit_or_rollback_by_xid(thd->lex->xid, 0); - xid_cache_delete(xs); - break; - } - else - { - ha_commit_or_rollback_by_xid(thd->lex->xid, 1); - xid_cache_delete(xs); - my_ok(thd); - } - break; - } - if (xa_trans_rolled_back(&thd->transaction.xid_state)) - { - xa_trans_rollback(thd); - break; - } - if (thd->transaction.xid_state.xa_state == XA_IDLE && - thd->lex->xa_opt == XA_ONE_PHASE) - { - int r; - if ((r= ha_commit(thd))) - my_error(r == 1 ? ER_XA_RBROLLBACK : ER_XAER_RMERR, MYF(0)); - else - my_ok(thd); - } - else if (thd->transaction.xid_state.xa_state == XA_PREPARED && - thd->lex->xa_opt == XA_NONE) - { - if (wait_if_global_read_lock(thd, 0, 0)) - { - ha_rollback(thd); - my_error(ER_XAER_RMERR, MYF(0)); - } - else - { - if (ha_commit_one_phase(thd, 1)) - my_error(ER_XAER_RMERR, MYF(0)); - else - my_ok(thd); - start_waiting_global_read_lock(thd); - } - } - else - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - xid_cache_delete(&thd->transaction.xid_state); - thd->transaction.xid_state.xa_state=XA_NOTR; + if (trans_xa_commit(thd)) + goto error; + thd->mdl_context.release_transactional_locks(); + my_ok(thd); break; case SQLCOM_XA_ROLLBACK: - if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - XID_STATE *xs=xid_cache_search(thd->lex->xid); - if (!xs || xs->in_thd) - my_error(ER_XAER_NOTA, MYF(0)); - else - { - bool ok= !xa_trans_rolled_back(xs); - ha_commit_or_rollback_by_xid(thd->lex->xid, 0); - xid_cache_delete(xs); - if (ok) - my_ok(thd); - } - break; - } - if (thd->transaction.xid_state.xa_state != XA_IDLE && - thd->transaction.xid_state.xa_state != XA_PREPARED && - thd->transaction.xid_state.xa_state != XA_ROLLBACK_ONLY) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (xa_trans_rollback(thd)) - my_error(ER_XAER_RMERR, MYF(0)); - else - my_ok(thd); + if (trans_xa_rollback(thd)) + goto error; + thd->mdl_context.release_transactional_locks(); + my_ok(thd); break; case SQLCOM_XA_RECOVER: res= mysql_xa_recover(thd); @@ -4891,14 +4478,29 @@ error: res= TRUE; finish: - if (need_start_waiting) + if (thd->global_read_lock.has_protection()) { /* Release the protection against the global read lock and wake everyone, who might want to set a global read lock. */ - start_waiting_global_read_lock(thd); + thd->global_read_lock.start_waiting_global_read_lock(thd); } + + if (stmt_causes_implicit_commit(thd, CF_IMPLICIT_COMMIT_END)) + { + /* 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(); + } + DBUG_RETURN(res || thd->is_error()); } @@ -5694,7 +5296,7 @@ void mysql_reset_thd_for_next_command(THD *thd) OPTION_STATUS_NO_TRANS_UPDATE | OPTION_KEEP_LOG to not get warnings in ha_rollback_trans() about some tables couldn't be rolled back. */ - if (!(thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) + if (!thd->in_multi_stmt_transaction()) { thd->variables.option_bits&= ~OPTION_KEEP_LOG; thd->transaction.all.modified_non_trans_table= FALSE; @@ -5935,9 +5537,6 @@ void mysql_parse(THD *thd, const char *inBuf, uint length, { LEX *lex= thd->lex; - sp_cache_flush_obsolete(&thd->sp_proc_cache); - sp_cache_flush_obsolete(&thd->sp_func_cache); - Parser_state parser_state(thd, inBuf, length); bool err= parse_sql(thd, & parser_state, NULL); @@ -6365,6 +5964,9 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->next_name_resolution_table= NULL; /* Link table in global list (all used tables) */ lex->add_to_query_tables(ptr); + ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name, + (ptr->lock_type >= TL_WRITE_ALLOW_WRITE) ? + MDL_SHARED_WRITE : MDL_SHARED_READ); DBUG_RETURN(ptr); } @@ -6600,6 +6202,8 @@ void st_select_lex::set_lock_for_tables(thr_lock_type lock_type) { tables->lock_type= lock_type; tables->updating= for_update; + tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? + MDL_SHARED_WRITE : MDL_SHARED_READ); } DBUG_VOID_RETURN; } @@ -6892,6 +6496,12 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, query_cache.flush(); // RESET QUERY CACHE } #endif /*HAVE_QUERY_CACHE*/ + + DBUG_ASSERT(!thd || thd->locked_tables_mode || + !thd->mdl_context.has_locks() || + thd->handler_tables_hash.records || + thd->global_read_lock.is_acquired()); + /* Note that if REFRESH_READ_LOCK bit is set then REFRESH_TABLES is set too (see sql_yacc.yy) @@ -6901,46 +6511,65 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, if ((options & REFRESH_READ_LOCK) && thd) { /* - We must not try to aspire a global read lock if we have a write - locked table. This would lead to a deadlock when trying to - reopen (and re-lock) the table after the flush. + On the first hand we need write lock on the tables to be flushed, + on the other hand we must not try to aspire a global read lock + if we have a write locked table as this would lead to a deadlock + when trying to reopen (and re-lock) the table after the flush. */ - if (thd->locked_tables) + if (thd->locked_tables_mode) { - THR_LOCK_DATA **lock_p= thd->locked_tables->locks; - THR_LOCK_DATA **end_p= lock_p + thd->locked_tables->lock_count; - - for (; lock_p < end_p; lock_p++) - { - if ((*lock_p)->type >= TL_WRITE_ALLOW_WRITE) - { - my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); - return 1; - } - } + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + return 1; } /* Writing to the binlog could cause deadlocks, as we don't log UNLOCK TABLES */ tmp_write_to_binlog= 0; - if (lock_global_read_lock(thd)) + if (thd->global_read_lock.lock_global_read_lock(thd)) return 1; // Killed if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ? - FALSE : TRUE, TRUE)) + FALSE : TRUE)) result= 1; - if (make_global_read_lock_block_commit(thd)) // Killed + if (thd->global_read_lock.make_global_read_lock_block_commit(thd)) // Killed { /* Don't leave things in a half-locked state */ - unlock_global_read_lock(thd); + thd->global_read_lock.unlock_global_read_lock(thd); return 1; } } else { + if (thd && thd->locked_tables_mode) + { + /* + If we are under LOCK TABLES we should have a write + lock on tables which we are going to flush. + */ + if (tables) + { + for (TABLE_LIST *t= tables; t; t= t->next_local) + if (!find_table_for_mdl_upgrade(thd->open_tables, t->db, + t->table_name, FALSE)) + return 1; + } + else + { + for (TABLE *tab= thd->open_tables; tab; tab= tab->next) + { + if (! tab->mdl_ticket->is_upgradable_or_exclusive()) + { + my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), + tab->s->table_name.str); + return 1; + } + } + } + } + if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ? - FALSE : TRUE, FALSE)) + FALSE : TRUE)) result= 1; } my_dbopt_cleanup(); @@ -7501,6 +7130,34 @@ bool insert_precheck(THD *thd, TABLE_LIST *tables) /** + Set proper open mode and table type for element representing target table + of CREATE TABLE statement, also adjust statement table list if necessary. +*/ + +void create_table_set_open_action_and_adjust_tables(LEX *lex) +{ + TABLE_LIST *create_table= lex->query_tables; + + if (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) + create_table->open_type= OT_TEMPORARY_ONLY; + else if (!lex->select_lex.item_list.elements) + create_table->open_type= OT_BASE_ONLY; + + if (!lex->select_lex.item_list.elements) + { + /* + Avoid opening and locking target table for ordinary CREATE TABLE + or CREATE TABLE LIKE for write (unlike in CREATE ... SELECT we + won't do any insertions in it anyway). Not doing this causes + problems when running CREATE TABLE IF NOT EXISTS for already + existing log table. + */ + create_table->lock_type= TL_READ; + } +} + + +/** CREATE TABLE query pre-check. @param thd Thread handler diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 91cf0333fb5..d2ee8870d34 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -50,6 +50,7 @@ #include <errno.h> #include <m_ctype.h> #include "my_md5.h" +#include "transaction.h" #ifdef WITH_PARTITION_STORAGE_ENGINE #include "ha_partition.h" @@ -4233,39 +4234,22 @@ bool mysql_unpack_partition(THD *thd, ha_resolve_storage_engine_name(default_db_type))); if (is_create_table_ind && old_lex->sql_command == SQLCOM_CREATE_TABLE) { - if (old_lex->create_info.options & HA_LEX_CREATE_TABLE_LIKE) - { - /* - This code is executed when we create table in CREATE TABLE t1 LIKE t2. - old_lex->query_tables contains table list element for t2 and the table - we are opening has name t1. - */ - if (partition_default_handling(table, part_info, FALSE, - old_lex->query_tables->table->s->path.str)) - { - result= TRUE; - goto end; - } - } - else - { - /* - When we come here we are doing a create table. In this case we - have already done some preparatory work on the old part_info - object. We don't really need this new partition_info object. - Thus we go back to the old partition info object. - We need to free any memory objects allocated on item_free_list - by the parser since we are keeping the old info from the first - parser call in CREATE TABLE. - We'll ensure that this object isn't put into table cache also - just to ensure we don't get into strange situations with the - item objects. - */ - thd->free_items(); - part_info= thd->work_part_info; - table->s->version= 0UL; - *work_part_info_used= true; - } + /* + When we come here we are doing a create table. In this case we + have already done some preparatory work on the old part_info + object. We don't really need this new partition_info object. + Thus we go back to the old partition info object. + We need to free any memory objects allocated on item_free_list + by the parser since we are keeping the old info from the first + parser call in CREATE TABLE. + + This table object can not be used any more. However, since + this is CREATE TABLE, we know that it will be destroyed by the + caller, and rely on that. + */ + thd->free_items(); + part_info= thd->work_part_info; + *work_part_info_used= true; } table->part_info= part_info; table->file->set_part_info(part_info); @@ -4372,7 +4356,6 @@ set_engine_all_partitions(partition_info *part_info, static int fast_end_partition(THD *thd, ulonglong copied, ulonglong deleted, - TABLE *table, TABLE_LIST *table_list, bool is_empty, ALTER_PARTITION_PARAM_TYPE *lpt, bool written_bin_log) @@ -4386,21 +4369,16 @@ static int fast_end_partition(THD *thd, ulonglong copied, if (!is_empty) query_cache_invalidate3(thd, table_list, 0); - error= ha_autocommit_or_rollback(thd, 0); - if (end_active_trans(thd)) + error= trans_commit_stmt(thd); + if (trans_commit_implicit(thd)) error= 1; if (error) - { - /* If error during commit, no need to rollback, it's done. */ - table->file->print_error(error, MYF(0)); - DBUG_RETURN(TRUE); - } + DBUG_RETURN(TRUE); /* The error has been reported */ if ((!is_empty) && (!written_bin_log) && - (!thd->lex->no_write_to_binlog) && - write_bin_log(thd, FALSE, thd->query(), thd->query_length())) - DBUG_RETURN(TRUE); + (!thd->lex->no_write_to_binlog)) + write_bin_log(thd, FALSE, thd->query(), thd->query_length()); my_snprintf(tmp_name, sizeof(tmp_name), ER(ER_INSERT_INFO), (ulong) (copied + deleted), @@ -4562,12 +4540,11 @@ uint prep_alter_part_table(THD *thd, TABLE *table, Alter_info *alter_info, /* We are going to manipulate the partition info on the table object - so we need to ensure that the data structure of the table object - is freed by setting version to 0. table->s->version= 0 forces a - flush of the table object in close_thread_tables(). + so we need to ensure that the table instance is removed from the + table cache. */ if (table->part_info) - table->s->version= 0L; + table->m_needs_reopen= TRUE; thd->work_part_info= thd->lex->part_info; if (thd->work_part_info && @@ -6274,30 +6251,13 @@ static void release_log_entries(partition_info *part_info) */ static void alter_partition_lock_handling(ALTER_PARTITION_PARAM_TYPE *lpt) { - int err; - if (lpt->thd->locked_tables) - { - /* - When we have the table locked, it is necessary to reopen the table - since all table objects were closed and removed as part of the - ALTER TABLE of partitioning structure. - */ - mysql_mutex_lock(&LOCK_open); - lpt->thd->in_lock_tables= 1; - err= reopen_tables(lpt->thd, 1, 1); - lpt->thd->in_lock_tables= 0; - if (err) - { - /* - Issue a warning since we weren't able to regain the lock again. - We also need to unlink table from thread's open list and from - table_cache - */ - unlink_open_table(lpt->thd, lpt->table, FALSE); - sql_print_warning("We failed to reacquire LOCKs in ALTER TABLE"); - } - mysql_mutex_unlock(&LOCK_open); - } + THD *thd= lpt->thd; + + close_all_tables_for_name(thd, lpt->table->s, FALSE); + lpt->table= 0; + lpt->table_list->table= 0; + if (thd->locked_tables_list.reopen_tables(thd)) + sql_print_warning("We failed to reacquire LOCKs in ALTER TABLE"); } /* @@ -6311,17 +6271,39 @@ static void alter_partition_lock_handling(ALTER_PARTITION_PARAM_TYPE *lpt) static int alter_close_tables(ALTER_PARTITION_PARAM_TYPE *lpt) { + TABLE_SHARE *share= lpt->table->s; THD *thd= lpt->thd; - const char *db= lpt->db; - const char *table_name= lpt->table_name; + TABLE *table; DBUG_ENTER("alter_close_tables"); /* - We need to also unlock tables and close all handlers. - We set lock to zero to ensure we don't do this twice - and we set db_stat to zero to ensure we don't close twice. + We must keep LOCK_open while manipulating with thd->open_tables. + Another thread may be working on it. */ mysql_mutex_lock(&LOCK_open); - close_data_files_and_morph_locks(thd, db, table_name); + /* + We can safely remove locks for all tables with the same name: + later they will all be closed anyway in + alter_partition_lock_handling(). + */ + for (table= thd->open_tables; table ; table= table->next) + { + if (!strcmp(table->s->table_name.str, share->table_name.str) && + !strcmp(table->s->db.str, share->db.str)) + { + mysql_lock_remove(thd, thd->lock, table); + table->file->close(); + table->db_stat= 0; // Mark file closed + /* + Ensure that we won't end up with a crippled table instance + in the table cache if an error occurs before we reach + alter_partition_lock_handling() and the table is closed + by close_thread_tables() instead. + */ + tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, + table->s->db.str, + table->s->table_name.str); + } + } mysql_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } @@ -6488,6 +6470,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, DBUG_ENTER("fast_alter_partition_table"); lpt->thd= thd; + lpt->table_list= table_list; lpt->part_info= part_info; lpt->alter_info= alter_info; lpt->create_info= create_info; @@ -6587,10 +6570,10 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, 3) Lock the table in TL_WRITE_ONLY to ensure all other accesses to the table have completed. This ensures that other threads can not execute on the table in parallel. - 4) Get a name lock on the table. This ensures that we can release all - locks on the table and since no one can open the table, there can - be no new threads accessing the table. They will be hanging on the - name lock. + 4) Get an exclusive metadata lock on the table. This ensures that we + can release all other locks on the table and since no one can open + the table, there can be no new threads accessing the table. They + will be hanging on this exclusive lock. 5) Close all tables that have already been opened but didn't stumble on the abort locked previously. This is done as part of the close_data_files_and_morph_locks call. @@ -6623,7 +6606,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, write_log_drop_partition(lpt) || ERROR_INJECT_CRASH("crash_drop_partition_3") || (not_completed= FALSE) || - abort_and_upgrade_lock(lpt) || /* Always returns 0 */ + abort_and_upgrade_lock(lpt) || ERROR_INJECT_CRASH("crash_drop_partition_4") || alter_close_tables(lpt) || ERROR_INJECT_CRASH("crash_drop_partition_5") || @@ -6665,14 +6648,15 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, 3) Lock all partitions in TL_WRITE_ONLY to ensure that no users are still using the old partitioning scheme. Wait until all ongoing users have completed before progressing. - 4) Get a name lock on the table. This ensures that we can release all - locks on the table and since no one can open the table, there can - be no new threads accessing the table. They will be hanging on the - name lock. + 4) Get an exclusive metadata lock on the table. This ensures that we + can release all other locks on the table and since no one can open + the table, there can be no new threads accessing the table. They + will be hanging on this exclusive lock. 5) Close all tables that have already been opened but didn't stumble on the abort locked previously. This is done as part of the close_data_files_and_morph_locks call. - 6) Close all table handlers and unlock all handlers but retain name lock + 6) Close all table handlers and unlock all handlers but retain + metadata lock. 7) Write binlog 8) Now the change is completed except for the installation of the new frm file. We thus write an action in the log to change to @@ -6690,7 +6674,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, ERROR_INJECT_CRASH("crash_add_partition_2") || mysql_change_partitions(lpt) || ERROR_INJECT_CRASH("crash_add_partition_3") || - abort_and_upgrade_lock(lpt) || /* Always returns 0 */ + abort_and_upgrade_lock(lpt) || ERROR_INJECT_CRASH("crash_add_partition_4") || alter_close_tables(lpt) || ERROR_INJECT_CRASH("crash_add_partition_5") || @@ -6706,7 +6690,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, ERROR_INJECT_CRASH("crash_add_partition_8") || (write_log_completed(lpt, FALSE), FALSE) || ERROR_INJECT_CRASH("crash_add_partition_9") || - (alter_partition_lock_handling(lpt), FALSE)) + (alter_partition_lock_handling(lpt), FALSE)) { handle_alter_part_error(lpt, not_completed, FALSE, frm_install); goto err; @@ -6753,23 +6737,18 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, Copy from the reorganised partitions to the new partitions 4) Log that operation is completed and log all complete actions needed to complete operation from here - 5) Lock all partitions in TL_WRITE_ONLY to ensure that no users - are still using the old partitioning scheme. Wait until all - ongoing users have completed before progressing. - 6) Get a name lock of the table - 7) Close all tables opened but not yet locked, after this call we are - certain that no other thread is in the lock wait queue or has - opened the table. The name lock will ensure that they are blocked - on the open call. - This is achieved also by close_data_files_and_morph_locks call. - 8) Close all partitions opened by this thread, but retain name lock. - 9) Write bin log - 10) Prepare handlers for rename and delete of partitions - 11) Rename and drop the reorged partitions such that they are no - longer used and rename those added to their real new names. - 12) Install the shadow frm file - 13) Reopen the table if under lock tables - 14) Complete query + 5) Upgrade shared metadata lock on the table to an exclusive one. + After this we can be sure that there is no other connection + using this table (they will be waiting for metadata lock). + 6) Close all table instances opened by this thread, but retain + exclusive metadata lock. + 7) Write bin log + 8) Prepare handlers for rename and delete of partitions + 9) Rename and drop the reorged partitions such that they are no + longer used and rename those added to their real new names. + 10) Install the shadow frm file + 11) Reopen the table if under lock tables + 12) Complete query */ if (write_log_add_change_partition(lpt) || ERROR_INJECT_CRASH("crash_change_partition_1") || @@ -6780,7 +6759,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, write_log_final_change_partition(lpt) || ERROR_INJECT_CRASH("crash_change_partition_4") || (not_completed= FALSE) || - abort_and_upgrade_lock(lpt) || /* Always returns 0 */ + abort_and_upgrade_lock(lpt) || ERROR_INJECT_CRASH("crash_change_partition_5") || alter_close_tables(lpt) || ERROR_INJECT_CRASH("crash_change_partition_6") || @@ -6808,7 +6787,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, user */ DBUG_RETURN(fast_end_partition(thd, lpt->copied, lpt->deleted, - table, table_list, FALSE, NULL, + table_list, FALSE, NULL, written_bin_log)); err: close_thread_tables(thd); diff --git a/sql/sql_plist.h b/sql/sql_plist.h new file mode 100644 index 00000000000..eb239a63467 --- /dev/null +++ b/sql/sql_plist.h @@ -0,0 +1,206 @@ +#ifndef SQL_PLIST_H +#define SQL_PLIST_H +/* Copyright (C) 2008 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#include <my_global.h> + +template <typename T, typename B, typename C> class I_P_List_iterator; +class I_P_List_null_counter; + + +/** + Intrusive parameterized list. + + Unlike I_List does not require its elements to be descendant of ilink + class and therefore allows them to participate in several such lists + simultaneously. + + Unlike List is doubly-linked list and thus supports efficient deletion + of element without iterator. + + @param T Type of elements which will belong to list. + @param B Class which via its methods specifies which members + of T should be used for participating in this list. + Here is typical layout of such class: + + struct B + { + static inline T **next_ptr(T *el) + { + return &el->next; + } + static inline T ***prev_ptr(T *el) + { + return &el->prev; + } + }; + @param C Policy class specifying how counting of elements in the list + should be done. Instance of this class is also used as a place + where information about number of list elements is stored. + @sa I_P_List_null_counter, I_P_List_counter +*/ + +template <typename T, typename B, typename C = I_P_List_null_counter> +class I_P_List : public C +{ + T *first; + + /* + Do not prohibit copying of I_P_List object to simplify their usage in + backup/restore scenarios. Note that performing any operations on such + is a bad idea. + */ +public: + I_P_List() : first(NULL) { }; + inline void empty() { first= NULL; C::reset(); } + inline bool is_empty() const { return (first == NULL); } + inline void push_front(T* a) + { + *B::next_ptr(a)= first; + if (first) + *B::prev_ptr(first)= B::next_ptr(a); + first= a; + *B::prev_ptr(a)= &first; + C::inc(); + } + inline void push_back(T *a) + { + insert_after(back(), a); + } + inline T *back() + { + T *t= front(); + if (t) + { + while (*B::next_ptr(t)) + t= *B::next_ptr(t); + } + return t; + } + inline void insert_after(T *pos, T *a) + { + if (pos == NULL) + push_front(a); + else + { + *B::next_ptr(a)= *B::next_ptr(pos); + *B::prev_ptr(a)= B::next_ptr(pos); + *B::next_ptr(pos)= a; + if (*B::next_ptr(a)) + { + T *old_next= *B::next_ptr(a); + *B::prev_ptr(old_next)= B::next_ptr(a); + } + } + } + inline void remove(T *a) + { + T *next= *B::next_ptr(a); + if (next) + *B::prev_ptr(next)= *B::prev_ptr(a); + **B::prev_ptr(a)= next; + C::dec(); + } + inline T* front() { return first; } + inline const T *front() const { return first; } + void swap(I_P_List<T, B, C> &rhs) + { + swap_variables(T *, first, rhs.first); + if (first) + *B::prev_ptr(first)= &first; + if (rhs.first) + *B::prev_ptr(rhs.first)= &rhs.first; + C::swap(rhs); + } +#ifndef _lint + friend class I_P_List_iterator<T, B, C>; +#endif + typedef I_P_List_iterator<T, B, C> Iterator; +}; + + +/** + Iterator for I_P_List. +*/ + +template <typename T, typename B, typename C = I_P_List_null_counter> +class I_P_List_iterator +{ + const I_P_List<T, B, C> *list; + T *current; +public: + I_P_List_iterator(const I_P_List<T, B, C> &a) : list(&a), current(a.first) {} + I_P_List_iterator(const I_P_List<T, B, C> &a, T* current_arg) : list(&a), current(current_arg) {} + inline void init(const I_P_List<T, B, C> &a) + { + list= &a; + current= a.first; + } + inline T* operator++(int) + { + T *result= current; + if (result) + current= *B::next_ptr(current); + return result; + } + inline T* operator++() + { + current= *B::next_ptr(current); + return current; + } + inline void rewind() + { + current= list->first; + } +}; + + +/** + Element counting policy class for I_P_List to be used in + cases when no element counting should be done. +*/ + +class I_P_List_null_counter +{ +protected: + void reset() {} + void inc() {} + void dec() {} + void swap(I_P_List_null_counter &rhs) {} +}; + + +/** + Element counting policy class for I_P_List which provides + basic element counting. +*/ + +class I_P_List_counter +{ + uint m_counter; +protected: + I_P_List_counter() : m_counter (0) {} + void reset() {m_counter= 0;} + void inc() {m_counter++;} + void dec() {m_counter--;} + void swap(I_P_List_counter &rhs) + { swap_variables(uint, m_counter, rhs.m_counter); } +public: + uint elements() const { return m_counter; } +}; + +#endif diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 0a7864631ae..c9f42b37d43 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -1442,10 +1442,7 @@ static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv) new_thd->store_globals(); new_thd->db= my_strdup("mysql", MYF(0)); new_thd->db_length= 5; - bzero((uchar*)&tables, sizeof(tables)); - tables.alias= tables.table_name= (char*)"plugin"; - tables.lock_type= TL_READ; - tables.db= new_thd->db; + tables.init_one_table("mysql", 5, "plugin", 6, "plugin", TL_READ); #ifdef EMBEDDED_LIBRARY /* @@ -1733,9 +1730,7 @@ bool mysql_install_plugin(THD *thd, const LEX_STRING *name, const LEX_STRING *dl struct st_plugin_int *tmp; DBUG_ENTER("mysql_install_plugin"); - bzero(&tables, sizeof(tables)); - tables.db= (char *)"mysql"; - tables.table_name= tables.alias= (char *)"plugin"; + tables.init_one_table("mysql", 5, "plugin", 6, "plugin", TL_WRITE); if (check_table_access(thd, INSERT_ACL, &tables, FALSE, 1, FALSE)) DBUG_RETURN(TRUE); @@ -1809,9 +1804,7 @@ bool mysql_uninstall_plugin(THD *thd, const LEX_STRING *name) struct st_plugin_int *plugin; DBUG_ENTER("mysql_uninstall_plugin"); - bzero(&tables, sizeof(tables)); - tables.db= (char *)"mysql"; - tables.table_name= tables.alias= (char *)"plugin"; + tables.init_one_table("mysql", 5, "plugin", 6, "plugin", TL_WRITE); /* need to open before acquiring LOCK_plugin or it will deadlock */ if (! (table= open_ltable(thd, &tables, TL_WRITE, 0))) diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 63b7649bad5..254baf24c71 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -174,8 +174,6 @@ private: SELECT_LEX and other classes). */ MEM_ROOT main_mem_root; - /* Version of the stored functions cache at the time of prepare. */ - ulong m_sp_cache_version; private: bool set_db(const char *db, uint db_length); bool set_parameters(String *expanded_query, @@ -1207,7 +1205,8 @@ static bool mysql_test_insert(Prepared_statement *stmt, If we would use locks, then we have to ensure we are not using TL_WRITE_DELAYED as having two such locks can cause table corruption. */ - if (open_normal_and_derived_tables(thd, table_list, 0)) + if (open_normal_and_derived_tables(thd, table_list, + MYSQL_OPEN_FORCE_SHARED_MDL)) goto error; if ((values= its++)) @@ -1288,7 +1287,7 @@ static int mysql_test_update(Prepared_statement *stmt, DBUG_ENTER("mysql_test_update"); if (update_precheck(thd, table_list) || - open_tables(thd, &table_list, &table_count, 0)) + open_tables(thd, &table_list, &table_count, MYSQL_OPEN_FORCE_SHARED_MDL)) goto error; if (table_list->multitable_view) @@ -1365,7 +1364,8 @@ static bool mysql_test_delete(Prepared_statement *stmt, DBUG_ENTER("mysql_test_delete"); if (delete_precheck(thd, table_list) || - open_normal_and_derived_tables(thd, table_list, 0)) + open_normal_and_derived_tables(thd, table_list, + MYSQL_OPEN_FORCE_SHARED_MDL)) goto error; if (!table_list->table) @@ -1423,7 +1423,7 @@ static int mysql_test_select(Prepared_statement *stmt, goto error; } - if (open_normal_and_derived_tables(thd, tables, 0)) + if (open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL)) goto error; thd->used_tables= 0; // Updated by setup_fields @@ -1484,7 +1484,7 @@ static bool mysql_test_do_fields(Prepared_statement *stmt, UINT_MAX, FALSE)) DBUG_RETURN(TRUE); - if (open_normal_and_derived_tables(thd, tables, 0)) + if (open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL)) DBUG_RETURN(TRUE); DBUG_RETURN(setup_fields(thd, 0, *values, MARK_COLUMNS_NONE, 0, 0)); } @@ -1514,7 +1514,7 @@ static bool mysql_test_set_fields(Prepared_statement *stmt, if ((tables && check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE)) || - open_normal_and_derived_tables(thd, tables, 0)) + open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL)) goto error; while ((var= it++)) @@ -1551,7 +1551,7 @@ static bool mysql_test_call_fields(Prepared_statement *stmt, if ((tables && check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE)) || - open_normal_and_derived_tables(thd, tables, 0)) + open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL)) goto err; while ((item= it++)) @@ -1634,7 +1634,8 @@ select_like_stmt_test_with_open(Prepared_statement *stmt, prepared EXPLAIN yet so derived tables will clean up after themself. */ - if (open_normal_and_derived_tables(stmt->thd, tables, 0)) + if (open_normal_and_derived_tables(stmt->thd, tables, + MYSQL_OPEN_FORCE_SHARED_MDL)) DBUG_RETURN(TRUE); DBUG_RETURN(select_like_stmt_test(stmt, specific_prepare, @@ -1661,33 +1662,36 @@ static bool mysql_test_create_table(Prepared_statement *stmt) LEX *lex= stmt->lex; SELECT_LEX *select_lex= &lex->select_lex; bool res= FALSE; - /* Skip first table, which is the table we are creating */ bool link_to_local; - TABLE_LIST *create_table= lex->unlink_first_table(&link_to_local); - TABLE_LIST *tables= lex->query_tables; + TABLE_LIST *create_table= lex->query_tables; + TABLE_LIST *tables= lex->create_last_non_select_table->next_global; if (create_table_precheck(thd, tables, create_table)) DBUG_RETURN(TRUE); + /* + The open and lock strategies will be set again once the + statement is executed. These values are only meaningful + for the prepare phase. + */ + create_table->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; + create_table->lock_strategy= TABLE_LIST::SHARED_MDL; + if (select_lex->item_list.elements) { - if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)) - { - lex->link_first_table_back(create_table, link_to_local); - create_table->create= TRUE; - } - - if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0)) + if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, + MYSQL_OPEN_FORCE_SHARED_MDL)) DBUG_RETURN(TRUE); - if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)) - create_table= lex->unlink_first_table(&link_to_local); - select_lex->context.resolve_in_select_list= TRUE; + lex->unlink_first_table(&link_to_local); + res= select_like_stmt_test(stmt, 0, 0); + + lex->link_first_table_back(create_table, &link_to_local); } - else if (lex->create_info.options & HA_LEX_CREATE_TABLE_LIKE) + else { /* Check that the source table exist, and also record @@ -1695,12 +1699,11 @@ static bool mysql_test_create_table(Prepared_statement *stmt) we validate metadata of all CREATE TABLE statements, which keeps metadata validation code simple. */ - if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0)) + if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, + MYSQL_OPEN_FORCE_SHARED_MDL)) DBUG_RETURN(TRUE); } - /* put tables back for PS rexecuting */ - lex->link_first_table_back(create_table, link_to_local); DBUG_RETURN(res); } @@ -1730,7 +1733,7 @@ static bool mysql_test_create_view(Prepared_statement *stmt) if (create_view_precheck(thd, tables, view, lex->create_view_mode)) goto err; - if (open_normal_and_derived_tables(thd, tables, 0)) + if (open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL)) goto err; lex->view_prepare_mode= 1; @@ -2139,9 +2142,6 @@ void mysqld_stmt_prepare(THD *thd, const char *packet, uint packet_length) DBUG_VOID_RETURN; } - sp_cache_flush_obsolete(&thd->sp_proc_cache); - sp_cache_flush_obsolete(&thd->sp_func_cache); - thd->protocol= &thd->protocol_binary; if (stmt->prepare(packet, packet_length)) @@ -2420,6 +2420,13 @@ void reinit_stmt_before_use(THD *thd, LEX *lex) { tables->reinit_before_use(thd); } + + /* Reset MDL tickets for procedures/functions */ + for (Sroutine_hash_entry *rt= + (Sroutine_hash_entry*)thd->lex->sroutines_list.first; + rt; rt= rt->next) + rt->mdl_request.ticket= NULL; + /* Cleanup of the special case of DELETE t1, t2 FROM t1, t2, t3 ... (multi-delete). We do a full clean up, although at the moment all we @@ -2513,9 +2520,6 @@ void mysqld_stmt_execute(THD *thd, char *packet_arg, uint packet_length) DBUG_PRINT("exec_query", ("%s", stmt->query())); DBUG_PRINT("info",("stmt: 0x%lx", (long) stmt)); - sp_cache_flush_obsolete(&thd->sp_proc_cache); - sp_cache_flush_obsolete(&thd->sp_func_cache); - open_cursor= test(flags & (ulong) CURSOR_TYPE_READ_ONLY); thd->protocol= &thd->protocol_binary; @@ -2965,8 +2969,7 @@ Prepared_statement::Prepared_statement(THD *thd_arg) param_array(0), param_count(0), last_errno(0), - flags((uint) IS_IN_USE), - m_sp_cache_version(0) + flags((uint) IS_IN_USE) { init_sql_alloc(&main_mem_root, thd_arg->variables.query_alloc_block_size, thd_arg->variables.query_prealloc_size); @@ -3131,6 +3134,7 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) bool error; Statement stmt_backup; Query_arena *old_stmt_arena; + MDL_ticket *mdl_savepoint= NULL; DBUG_ENTER("Prepared_statement::prepare"); /* If this is an SQLCOM_PREPARE, we also increase Com_prepare_sql. @@ -3189,6 +3193,12 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) */ DBUG_ASSERT(thd->change_list.is_empty()); + /* + Marker used to release metadata locks acquired while the prepared + statement is being checked. + */ + mdl_savepoint= thd->mdl_context.mdl_savepoint(); + /* The only case where we should have items in the thd->free_list is after stmt->set_params_from_vars(), which may in some cases create @@ -3212,6 +3222,13 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) 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()) + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); thd->restore_backup_statement(this, &stmt_backup); thd->stmt_arena= old_stmt_arena; @@ -3221,20 +3238,6 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) init_stmt_after_parse(lex); state= Query_arena::PREPARED; flags&= ~ (uint) IS_IN_USE; - /* - This is for prepared statement validation purposes. - A statement looks up and pre-loads all its stored functions - at prepare. Later on, if a function is gone from the cache, - execute may fail. - Remember the cache version to be able to invalidate the prepared - statement at execute if it changes. - We only need to care about version of the stored functions cache: - if a prepared statement uses a stored procedure, it's indirect, - via a stored function. The only exception is SQLCOM_CALL, - but the latter one looks up the stored procedure each time - it's invoked, rather than once at prepare. - */ - m_sp_cache_version= sp_cache_version(&thd->sp_func_cache); /* Log COM_EXECUTE to the general log. Note, that in case of SQL @@ -3410,7 +3413,7 @@ Prepared_statement::execute_server_runnable(Server_runnable *server_runnable) bool error; Query_arena *save_stmt_arena= thd->stmt_arena; Item_change_list save_change_list; - thd->change_list= save_change_list; + thd->change_list.move_elements_to(&save_change_list); state= CONVENTIONAL_EXECUTION; @@ -3434,7 +3437,7 @@ Prepared_statement::execute_server_runnable(Server_runnable *server_runnable) thd->restore_backup_statement(this, &stmt_backup); thd->stmt_arena= save_stmt_arena; - save_change_list= thd->change_list; + save_change_list.move_elements_to(&thd->change_list); /* Items and memory will freed in destructor */ @@ -3575,13 +3578,12 @@ Prepared_statement::swap_prepared_statement(Prepared_statement *copy) is allocated in the old arena. */ swap_variables(Item_param **, param_array, copy->param_array); - /* Swap flags: this is perhaps unnecessary */ - swap_variables(uint, flags, copy->flags); + /* Don't swap flags: the copy has IS_SQL_PREPARE always set. */ + /* swap_variables(uint, flags, copy->flags); */ /* Swap names, the old name is allocated in the wrong memory root */ swap_variables(LEX_STRING, name, copy->name); /* Ditto */ swap_variables(char *, db, copy->db); - swap_variables(ulong, m_sp_cache_version, copy->m_sp_cache_version); DBUG_ASSERT(db_length == copy->db_length); DBUG_ASSERT(param_count == copy->param_count); @@ -3641,19 +3643,6 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) } /* - Reprepare the statement if we're using stored functions - and the version of the stored routines cache has changed. - */ - if (lex->uses_stored_routines() && - m_sp_cache_version != sp_cache_version(&thd->sp_func_cache) && - thd->m_reprepare_observer && - thd->m_reprepare_observer->report_error(thd)) - { - return TRUE; - } - - - /* For SHOW VARIABLES lex->result is NULL, as it's a non-SELECT command. For such queries we don't return an error and don't open a cursor -- the client library will recognize this case and diff --git a/sql/sql_profile.cc b/sql/sql_profile.cc index 84ee0768b25..2356af439e8 100644 --- a/sql/sql_profile.cc +++ b/sql/sql_profile.cc @@ -38,9 +38,6 @@ #define MAX_QUERY_LENGTH 300 -/* Reserved for systems that can't record the function name in source. */ -const char * const _unknown_func_ = "<unknown>"; - /** Connects Information_Schema and Profiling. */ diff --git a/sql/sql_profile.h b/sql/sql_profile.h index bffe1cb576b..e9c5686efab 100644 --- a/sql/sql_profile.h +++ b/sql/sql_profile.h @@ -16,14 +16,6 @@ #ifndef _SQL_PROFILE_H #define _SQL_PROFILE_H -#ifndef __func__ -#ifdef __FUNCTION__ -#define __func__ __FUNCTION__ -#else -#define __func__ "unknown function" -#endif -#endif - extern ST_FIELD_INFO query_profile_statistics_info[]; int fill_query_profile_statistics_info(THD *thd, TABLE_LIST *tables, Item *cond); int make_profile_table_for_show(THD *thd, ST_SCHEMA_TABLE *schema_table); diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc index 7c52a12c072..cf6ab684ead 100644 --- a/sql/sql_rename.cc +++ b/sql/sql_rename.cc @@ -34,7 +34,6 @@ static TABLE_LIST *reverse_table_list(TABLE_LIST *table_list); bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) { bool error= 1; - bool binlog_error= 0; TABLE_LIST *ren_table= 0; int to_table; char *rename_log_table[2]= {NULL, NULL}; @@ -45,16 +44,16 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) if the user is trying to to do this in a transcation context */ - if (thd->locked_tables || thd->active_transaction()) + if (thd->locked_tables_mode || thd->active_transaction()) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); DBUG_RETURN(1); } - mysql_ha_rm_tables(thd, table_list, FALSE); + mysql_ha_rm_tables(thd, table_list); - if (wait_if_global_read_lock(thd,0,1)) + if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) DBUG_RETURN(1); if (logger.is_log_table_enabled(QUERY_LOG_GENERAL) || @@ -134,12 +133,14 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) } } - mysql_mutex_lock(&LOCK_open); - if (lock_table_names_exclusively(thd, table_list)) - { - mysql_mutex_unlock(&LOCK_open); + if (lock_table_names(thd, table_list)) goto err; - } + + mysql_mutex_lock(&LOCK_open); + + for (ren_table= table_list; ren_table; ren_table= ren_table->next_local) + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, ren_table->db, + ren_table->table_name); error=0; if ((ren_table=rename_tables(thd,table_list,0))) @@ -175,23 +176,21 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) */ mysql_mutex_unlock(&LOCK_open); + /* Lets hope this doesn't fail as the result will be messy */ if (!silent && !error) { - binlog_error= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - if (!binlog_error) - my_ok(thd); + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + my_ok(thd); } if (!error) query_cache_invalidate3(thd, table_list, 0); - mysql_mutex_lock(&LOCK_open); - unlock_table_names(thd, table_list, (TABLE_LIST*) 0); - mysql_mutex_unlock(&LOCK_open); + unlock_table_names(thd); err: - start_waiting_global_read_lock(thd); - DBUG_RETURN(error || binlog_error); + thd->global_read_lock.start_waiting_global_read_lock(thd); + DBUG_RETURN(error); } diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 960a0adab2e..2b6085f6f04 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -1213,8 +1213,8 @@ int reset_slave(THD *thd, Master_info* mi) MY_STAT stat_area; char fname[FN_REFLEN]; int thread_mask= 0, error= 0; - uint sql_errno=ER_UNKNOWN_ERROR; - const char* errmsg= "Unknown error occured while reseting slave"; + uint sql_errno=0; + const char* errmsg=0; DBUG_ENTER("reset_slave"); lock_slave_threads(mi); @@ -1951,8 +1951,7 @@ err: replication events along LOAD DATA processing. @param file pointer to io-cache - @retval 0 success - @retval 1 failure + @return 0 */ int log_loaded_block(IO_CACHE* file) { @@ -1979,8 +1978,7 @@ int log_loaded_block(IO_CACHE* file) Append_block_log_event a(lf_info->thd, lf_info->thd->db, buffer, min(block_len, max_event_size), lf_info->log_delayed); - if (mysql_bin_log.write(&a)) - DBUG_RETURN(1); + mysql_bin_log.write(&a); } else { @@ -1988,8 +1986,7 @@ int log_loaded_block(IO_CACHE* file) buffer, min(block_len, max_event_size), lf_info->log_delayed); - if (mysql_bin_log.write(&b)) - DBUG_RETURN(1); + mysql_bin_log.write(&b); lf_info->wrote_create_file= 1; DBUG_SYNC_POINT("debug_lock.created_file_event",10); } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 5f1c5ac2a34..7b1d29a3ce8 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1024,7 +1024,7 @@ JOIN::optimize() error= -1; DBUG_RETURN(1); } - if (const_tables && !thd->locked_tables && + if (const_tables && !thd->locked_tables_mode && !(select_options & SELECT_NO_UNLOCK)) mysql_unlock_some_tables(thd, all_tables, const_tables); if (!conds && outer_join) @@ -6945,7 +6945,7 @@ void JOIN::join_free() We are not using tables anymore Unlock all tables. We may be in an INSERT .... SELECT statement. */ - if (can_unlock && lock && thd->lock && + if (can_unlock && lock && thd->lock && ! thd->locked_tables_mode && !(select_options & SELECT_NO_UNLOCK) && !select_lex->subquery_in_having && (select_lex == (thd->lex->unit.fake_select_lex ? @@ -11137,6 +11137,13 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure) fields); rc= join->result->send_data(*columns_list); } + /* + An error can happen when evaluating the conds + (the join condition and piece of where clause + relevant to this join table). + */ + if (join->thd->is_error()) + error= NESTED_LOOP_ERROR; } else { diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc index 141e00cf50f..7b03891aff8 100644 --- a/sql/sql_servers.cc +++ b/sql/sql_servers.cc @@ -39,6 +39,7 @@ #include <stdarg.h> #include "sp_head.h" #include "sp.h" +#include "transaction.h" /* We only use 1 mutex to guard the data structures - THR_LOCK_servers. @@ -247,20 +248,10 @@ bool servers_reload(THD *thd) bool return_val= TRUE; DBUG_ENTER("servers_reload"); - if (thd->locked_tables) - { // Can't have locked tables here - thd->lock=thd->locked_tables; - thd->locked_tables=0; - close_thread_tables(thd); - } - DBUG_PRINT("info", ("locking servers_cache")); mysql_rwlock_wrlock(&THR_LOCK_servers); - bzero((char*) tables, sizeof(tables)); - tables[0].alias= tables[0].table_name= (char*) "servers"; - tables[0].db= (char*) "mysql"; - tables[0].lock_type= TL_READ; + tables[0].init_one_table("mysql", 5, "servers", 7, "servers", TL_READ); if (simple_open_n_lock_tables(thd, tables)) { @@ -278,7 +269,9 @@ bool servers_reload(THD *thd) } end: + trans_commit_implicit(thd); close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); DBUG_PRINT("info", ("unlocking servers_cache")); mysql_rwlock_unlock(&THR_LOCK_servers); DBUG_RETURN(return_val); @@ -388,9 +381,7 @@ insert_server(THD *thd, FOREIGN_SERVER *server) DBUG_ENTER("insert_server"); - bzero((char*) &tables, sizeof(tables)); - tables.db= (char*) "mysql"; - tables.alias= tables.table_name= (char*) "servers"; + tables.init_one_table("mysql", 5, "servers", 7, "servers", TL_WRITE); /* need to open before acquiring THR_LOCK_plugin or it will deadlock */ if (! (table= open_ltable(thd, &tables, TL_WRITE, 0))) @@ -606,9 +597,7 @@ int drop_server(THD *thd, LEX_SERVER_OPTIONS *server_options) DBUG_PRINT("info", ("server name server->server_name %s", server_options->server_name)); - bzero((char*) &tables, sizeof(tables)); - tables.db= (char*) "mysql"; - tables.alias= tables.table_name= (char*) "servers"; + tables.init_one_table("mysql", 5, "servers", 7, "servers", TL_WRITE); mysql_rwlock_wrlock(&THR_LOCK_servers); @@ -730,9 +719,8 @@ int update_server(THD *thd, FOREIGN_SERVER *existing, FOREIGN_SERVER *altered) TABLE_LIST tables; DBUG_ENTER("update_server"); - bzero((char*) &tables, sizeof(tables)); - tables.db= (char*)"mysql"; - tables.alias= tables.table_name= (char*)"servers"; + tables.init_one_table("mysql", 5, "servers", 7, "servers", + TL_WRITE); if (!(table= open_ltable(thd, &tables, TL_WRITE, 0))) { diff --git a/sql/sql_show.cc b/sql/sql_show.cc index d562739f913..cddbe2a8d58 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2878,7 +2878,11 @@ make_table_name_list(THD *thd, List<LEX_STRING> *table_names, LEX *lex, @param[in] thd thread handler @param[in] tables TABLE_LIST for I_S table @param[in] schema_table pointer to I_S structure - @param[in] open_tables_state_backup pointer to Open_tables_state object + @param[in] can_deadlock Indicates that deadlocks are possible + due to metadata locks, so to avoid + them we should not wait in case if + conflicting lock is present. + @param[in] open_tables_state_backup pointer to Open_tables_backup object which is used to save|restore original status of variables related to open tables state @@ -2891,7 +2895,8 @@ make_table_name_list(THD *thd, List<LEX_STRING> *table_names, LEX *lex, static int fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables, ST_SCHEMA_TABLE *schema_table, - Open_tables_state *open_tables_state_backup) + bool can_deadlock, + Open_tables_backup *open_tables_state_backup) { LEX *lex= thd->lex; bool res; @@ -2919,7 +2924,9 @@ fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables, */ lex->sql_command= SQLCOM_SHOW_FIELDS; res= open_normal_and_derived_tables(thd, show_table_list, - MYSQL_LOCK_IGNORE_FLUSH); + (MYSQL_LOCK_IGNORE_FLUSH | + (can_deadlock ? + MYSQL_OPEN_FAIL_ON_MDL_CONFLICT : 0))); lex->sql_command= save_sql_command; /* get_all_tables() returns 1 on failure and 0 on success thus @@ -2945,7 +2952,8 @@ fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables, table, res, db_name, table_name)); thd->temporary_tables= 0; - close_tables_for_reopen(thd, &show_table_list); + close_tables_for_reopen(thd, &show_table_list, + open_tables_state_backup->mdl_system_tables_svp); DBUG_RETURN(error); } @@ -3058,6 +3066,47 @@ uint get_table_open_method(TABLE_LIST *tables, /** + Try acquire high priority share metadata lock on a table (with + optional wait for conflicting locks to go away). + + @param thd Thread context. + @param mdl_request Pointer to memory to be used for MDL_request + object for a lock request. + @param table Table list element for the table + @param can_deadlock Indicates that deadlocks are possible due to + metadata locks, so to avoid them we should not + wait in case if conflicting lock is present. + + @note This is an auxiliary function to be used in cases when we want to + access table's description by looking up info in TABLE_SHARE without + going through full-blown table open. + @note This function assumes that there are no other metadata lock requests + in the current metadata locking context. + + @retval FALSE No error, if lock was obtained TABLE_LIST::mdl_request::ticket + is set to non-NULL value. + @retval TRUE Some error occured (probably thread was killed). +*/ + +static bool +try_acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table, + bool can_deadlock) +{ + bool error; + table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name, + MDL_SHARED_HIGH_PRIO); + while (!(error= + thd->mdl_context.try_acquire_lock(&table->mdl_request)) && + !table->mdl_request.ticket && !can_deadlock) + { + if ((error= thd->mdl_context.wait_for_lock(&table->mdl_request))) + break; + } + return error; +} + + +/** @brief Fill I_S table with data from FRM file only @param[in] thd thread handler @@ -3066,6 +3115,10 @@ uint get_table_open_method(TABLE_LIST *tables, @param[in] db_name database name @param[in] table_name table name @param[in] schema_table_idx I_S table index + @param[in] can_deadlock Indicates that deadlocks are possible + due to metadata locks, so to avoid + them we should not wait in case if + conflicting lock is present. @return Operation status @retval 0 Table is processed and we can continue @@ -3078,13 +3131,15 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, ST_SCHEMA_TABLE *schema_table, LEX_STRING *db_name, LEX_STRING *table_name, - enum enum_schema_tables schema_table_idx) + enum enum_schema_tables schema_table_idx, + bool can_deadlock) { TABLE_SHARE *share; TABLE tbl; TABLE_LIST table_list; uint res= 0; - int error; + int not_used; + my_hash_value_type hash_value; char key[MAX_DBKEY_LENGTH]; uint key_length; char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1]; @@ -3112,14 +3167,46 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, table_list.db= db_name->str; } + /* + TODO: investigate if in this particular situation we can get by + simply obtaining internal lock of data-dictionary (ATM it + is LOCK_open) instead of obtaning full-blown metadata lock. + */ + if (try_acquire_high_prio_shared_mdl_lock(thd, &table_list, can_deadlock)) + { + /* + Some error occured (most probably we have been killed while + waiting for conflicting locks to go away), let the caller to + handle the situation. + */ + return 1; + } + + if (! table_list.mdl_request.ticket) + { + /* + We are in situation when we have encountered conflicting metadata + lock and deadlocks can occur due to waiting for it to go away. + So instead of waiting skip this table with an appropriate warning. + */ + DBUG_ASSERT(can_deadlock); + + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARN_I_S_SKIPPED_TABLE, + ER(ER_WARN_I_S_SKIPPED_TABLE), + table_list.db, table_list.table_name); + return 0; + } + key_length= create_table_def_key(thd, key, &table_list, 0); + hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length); mysql_mutex_lock(&LOCK_open); share= get_table_share(thd, &table_list, key, - key_length, OPEN_VIEW, &error); + key_length, OPEN_VIEW, ¬_used, hash_value); if (!share) { res= 0; - goto err; + goto end_unlock; } if (share->is_view) @@ -3128,7 +3215,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, { /* skip view processing */ res= 0; - goto err1; + goto end_share; } else if (schema_table->i_s_requested_object & OPEN_VIEW_FULL) { @@ -3137,7 +3224,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, open_normal_and_derived_tables() */ res= 1; - goto err1; + goto end_share; } } @@ -3153,14 +3240,20 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table, res= schema_table->process_table(thd, &table_list, table, res, db_name, table_name); closefrm(&tbl, true); - goto err; + goto end_unlock; } -err1: - release_table_share(share, RELEASE_NORMAL); +end_share: + release_table_share(share); -err: +end_unlock: mysql_mutex_unlock(&LOCK_open); + /* + Don't release the MDL lock, it can be part of a transaction. + If it is not, it will be released by the call to + MDL_context::rollback_to_savepoint() in the caller. + */ + thd->clear_error(); return res; } @@ -3204,22 +3297,35 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) COND *partial_cond= 0; uint derived_tables= lex->derived_tables; int error= 1; - Open_tables_state open_tables_state_backup; + Open_tables_backup open_tables_state_backup; bool save_view_prepare_mode= lex->view_prepare_mode; Query_tables_list query_tables_list_backup; #ifndef NO_EMBEDDED_ACCESS_CHECKS Security_context *sctx= thd->security_ctx; #endif uint table_open_method; + bool can_deadlock; DBUG_ENTER("get_all_tables"); + /* + In cases when SELECT from I_S table being filled by this call is + part of statement which also uses other tables or is being executed + under LOCK TABLES or is part of transaction which also uses other + tables waiting for metadata locks which happens below might result + in deadlocks. + To avoid them we don't wait if conflicting metadata lock is + encountered and skip table with emitting an appropriate warning. + */ + can_deadlock= thd->mdl_context.has_locks(); + lex->view_prepare_mode= TRUE; lex->reset_n_backup_query_tables_list(&query_tables_list_backup); /* We should not introduce deadlocks even if we already have some tables open and locked, since we won't lock tables which we will - open and will ignore possible name-locks for these tables. + open and will ignore pending exclusive metadata locks for these + tables by using high-priority requests for shared metadata locks. */ thd->reset_n_backup_open_tables_state(&open_tables_state_backup); @@ -3231,6 +3337,7 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) if (lsel && lsel->table_list.first) { error= fill_schema_show_cols_or_idxs(thd, tables, schema_table, + can_deadlock, &open_tables_state_backup); goto err; } @@ -3346,7 +3453,8 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) !with_i_schema) { if (!fill_schema_table_from_frm(thd, table, schema_table, db_name, - table_name, schema_table_idx)) + table_name, schema_table_idx, + can_deadlock)) continue; } @@ -3371,7 +3479,8 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) show_table_list->i_s_requested_object= schema_table->i_s_requested_object; res= open_normal_and_derived_tables(thd, show_table_list, - MYSQL_LOCK_IGNORE_FLUSH); + (MYSQL_LOCK_IGNORE_FLUSH | + (can_deadlock ? MYSQL_OPEN_FAIL_ON_MDL_CONFLICT : 0))); lex->sql_command= save_sql_command; /* XXX: show_table_list has a flag i_is_requested, @@ -3407,7 +3516,8 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) res= schema_table->process_table(thd, show_table_list, table, res, &orig_db_name, &tmp_lex_string); - close_tables_for_reopen(thd, &show_table_list); + close_tables_for_reopen(thd, &show_table_list, + open_tables_state_backup.mdl_system_tables_svp); } DBUG_ASSERT(!lex->query_tables_own_last); if (res) @@ -4210,7 +4320,7 @@ int fill_schema_proc(THD *thd, TABLE_LIST *tables, COND *cond) TABLE *table= tables->table; bool full_access; char definer[USER_HOST_BUFF_SIZE]; - Open_tables_state open_tables_state_backup; + Open_tables_backup open_tables_state_backup; DBUG_ENTER("fill_schema_proc"); strxmov(definer, thd->security_ctx->priv_user, "@", @@ -7166,14 +7276,14 @@ static bool show_create_trigger_impl(THD *thd, - do not update Lex::query_tables in add_table_to_list(). */ -static TABLE_LIST *get_trigger_table_impl( - THD *thd, - const sp_name *trg_name) +static +TABLE_LIST *get_trigger_table_impl(THD *thd, const sp_name *trg_name) { char trn_path_buff[FN_REFLEN]; - LEX_STRING trn_path= { trn_path_buff, 0 }; + LEX_STRING db; LEX_STRING tbl_name; + TABLE_LIST *table; build_trn_path(thd, trg_name, &trn_path); @@ -7187,25 +7297,19 @@ static TABLE_LIST *get_trigger_table_impl( return NULL; /* We need to reset statement table list to be PS/SP friendly. */ - - TABLE_LIST *table; - - if (!(table= (TABLE_LIST *)thd->calloc(sizeof(TABLE_LIST)))) - { - my_error(ER_OUTOFMEMORY, MYF(0), sizeof(TABLE_LIST)); + if (!(table= (TABLE_LIST*) thd->alloc(sizeof(TABLE_LIST)))) return NULL; - } - table->db_length= trg_name->m_db.length; - table->db= thd->strmake(trg_name->m_db.str, trg_name->m_db.length); + db= trg_name->m_db; - table->table_name_length= tbl_name.length; - table->table_name= thd->strmake(tbl_name.str, tbl_name.length); + db.str= thd->strmake(db.str, db.length); + tbl_name.str= thd->strmake(tbl_name.str, tbl_name.length); - table->alias= thd->strmake(tbl_name.str, tbl_name.length); + if (db.str == NULL || tbl_name.str == NULL) + return NULL; - table->lock_type= TL_IGNORE; - table->cacheable_table= 0; + table->init_one_table(db.str, db.length, tbl_name.str, tbl_name.length, + tbl_name.str, TL_IGNORE); return table; } @@ -7221,7 +7325,8 @@ static TABLE_LIST *get_trigger_table_impl( @return TABLE_LIST object corresponding to the base table. */ -static TABLE_LIST *get_trigger_table(THD *thd, const sp_name *trg_name) +static +TABLE_LIST *get_trigger_table(THD *thd, const sp_name *trg_name) { /* Acquire LOCK_open (stop the server). */ @@ -7271,8 +7376,8 @@ bool show_create_trigger(THD *thd, const sp_name *trg_name) Open the table by name in order to load Table_triggers_list object. NOTE: there is race condition here -- the table can be dropped after - LOCK_open is released. It will be fixed later by introducing - acquire-shared-table-name-lock functionality. + LOCK_open is released. It will be fixed later by acquiring shared + metadata lock on trigger or table name. */ uint num_tables; /* NOTE: unused, only to pass to open_tables(). */ diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 190c1cb9c70..0ffadb5d66f 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -16,12 +16,15 @@ /* drop and alter of tables */ #include "mysql_priv.h" +#include "debug_sync.h" #include <hash.h> #include <myisam.h> #include <my_dir.h> #include "sp_head.h" +#include "sp.h" #include "sql_trigger.h" #include "sql_show.h" +#include "transaction.h" #include "keycaches.h" #ifdef __WIN__ @@ -1739,10 +1742,9 @@ end: file */ -int write_bin_log(THD *thd, bool clear_error, - char const *query, ulong query_length) +void write_bin_log(THD *thd, bool clear_error, + char const *query, ulong query_length) { - int error= 0; if (mysql_bin_log.is_open()) { int errcode= 0; @@ -1750,10 +1752,9 @@ int write_bin_log(THD *thd, bool clear_error, thd->clear_error(); else errcode= query_error_code(thd, TRUE); - error= thd->binlog_query(THD::STMT_QUERY_TYPE, - query, query_length, FALSE, FALSE, errcode); + thd->binlog_query(THD::STMT_QUERY_TYPE, + query, query_length, FALSE, FALSE, errcode); } - return error; } @@ -1784,31 +1785,26 @@ int write_bin_log(THD *thd, bool clear_error, bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists, my_bool drop_temporary) { - bool error= FALSE, need_start_waiting= FALSE; + bool error; Drop_table_error_handler err_handler(thd->get_internal_handler()); + DBUG_ENTER("mysql_rm_table"); /* mark for close and remove all cached entries */ if (!drop_temporary) { - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + if (!thd->locked_tables_mode && + thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) DBUG_RETURN(TRUE); } - /* - Acquire LOCK_open after wait_if_global_read_lock(). If we would hold - LOCK_open during wait_if_global_read_lock(), other threads could not - close their tables. This would make a pretty deadlock. - */ thd->push_internal_handler(&err_handler); error= mysql_rm_table_part2(thd, tables, if_exists, drop_temporary, 0, 0); thd->pop_internal_handler(); - - if (need_start_waiting) - start_waiting_global_read_lock(thd); + if (thd->global_read_lock.has_protection()) + thd->global_read_lock.start_waiting_global_read_lock(thd); if (error) DBUG_RETURN(TRUE); @@ -1873,16 +1869,14 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, built_query.append("DROP TABLE "); } - mysql_ha_rm_tables(thd, tables, FALSE); - - mysql_mutex_lock(&LOCK_open); + mysql_ha_rm_tables(thd, tables); /* If we have the table in the definition cache, we don't have to check the .frm file to find if the table is a normal table (not view) and what engine to use. */ - + mysql_mutex_lock(&LOCK_open); for (table= tables; table; table= table->next_local) { TABLE_SHARE *share; @@ -1895,16 +1889,60 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, check_if_log_table(table->db_length, table->db, table->table_name_length, table->table_name, 1)) { - my_error(ER_BAD_LOG_STATEMENT, MYF(0), "DROP"); mysql_mutex_unlock(&LOCK_open); + my_error(ER_BAD_LOG_STATEMENT, MYF(0), "DROP"); DBUG_RETURN(1); } } + mysql_mutex_unlock(&LOCK_open); - if (!drop_temporary && lock_table_names_exclusively(thd, tables)) + if (!drop_temporary) { - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(1); + if (!thd->locked_tables_mode) + { + if (lock_table_names(thd, tables)) + DBUG_RETURN(1); + mysql_mutex_lock(&LOCK_open); + for (table= tables; table; table= table->next_local) + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name); + mysql_mutex_unlock(&LOCK_open); + } + else + { + for (table= tables; table; table= table->next_local) + if (find_temporary_table(thd, table->db, table->table_name)) + { + /* + A temporary table. + + Don't try to find a corresponding MDL lock or assign it + to table->mdl_request.ticket. There can't be metadata + locks for temporary tables: they are local to the session. + + Later in this function we release the MDL lock only if + table->mdl_requeset.ticket is not NULL. Thus here we + ensure that we won't release the metadata lock on the base + table locked with LOCK TABLES as a side effect of temporary + table drop. + */ + DBUG_ASSERT(table->mdl_request.ticket == NULL); + } + else + { + /* + Not a temporary table. + + Since 'tables' list can't contain duplicates (this is ensured + by parser) it is safe to cache pointer to the TABLE instances + in its elements. + */ + table->table= find_table_for_mdl_upgrade(thd->open_tables, table->db, + table->table_name, FALSE); + if (!table->table) + DBUG_RETURN(1); + table->mdl_request.ticket= table->table->mdl_ticket; + } + } } for (table= tables; table; table= table->next_local) @@ -1946,12 +1984,16 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, case -1: DBUG_ASSERT(thd->in_sub_stmt); error= 1; - goto err_with_placeholders; + goto err; default: // temporary table not found error= 0; } + /* Probably a non-temporary table. */ + if (!drop_temporary) + non_temp_tables_count++; + /* If row-based replication is used and the table is not a temporary table, we add the table name to the drop statement @@ -1960,7 +2002,6 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, */ if (!drop_temporary && thd->current_stmt_binlog_row_based && !dont_log_query) { - non_temp_tables_count++; /* Don't write the database name if it is the current one (or if thd->db is NULL). @@ -1979,22 +2020,21 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, table_type= table->db_type; if (!drop_temporary) { - TABLE *locked_table; - abort_locked_tables(thd, db, table->table_name); - remove_table_from_cache(thd, db, table->table_name, - RTFC_WAIT_OTHER_THREAD_FLAG | - RTFC_CHECK_KILLED_FLAG); - /* - If the table was used in lock tables, remember it so that - unlock_table_names can free it - */ - if ((locked_table= drop_locked_tables(thd, db, table->table_name))) - table->table= locked_table; + if (thd->locked_tables_mode) + { + if (wait_while_table_is_used(thd, table->table, HA_EXTRA_FORCE_REOPEN)) + { + error= -1; + goto err; + } + close_all_tables_for_name(thd, table->table->s, TRUE); + table->table= 0; + } if (thd->killed) { error= -1; - goto err_with_placeholders; + goto err; } alias= (lower_case_table_names == 2) ? table->alias : table->table_name; /* remove .frm file and engine files */ @@ -2003,6 +2043,11 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, table->internal_tmp_table ? FN_IS_TMP : 0); } + /* + TODO: Investigate what should be done to remove this lock completely. + Is exclusive meta-data lock enough ? + */ + mysql_mutex_lock(&LOCK_open); if (drop_temporary || ((table_type == NULL && access(path, F_OK) && @@ -2055,6 +2100,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, error|= new_error; } } + mysql_mutex_unlock(&LOCK_open); if (error) { if (wrong_tables.length()) @@ -2070,11 +2116,6 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, table->table_name);); } - /* - It's safe to unlock LOCK_open: we have an exclusive lock - on the table name. - */ - mysql_mutex_unlock(&LOCK_open); thd->thread_specific_used|= tmp_table_deleted; error= 0; if (wrong_tables.length()) @@ -2102,7 +2143,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, tables). In this case, we can write the original query into the binary log. */ - error |= write_bin_log(thd, !error, thd->query(), thd->query_length()); + write_bin_log(thd, !error, thd->query(), thd->query_length()); } else if (thd->current_stmt_binlog_row_based && tmp_table_deleted) @@ -2124,7 +2165,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, */ built_query.chop(); // Chop of the last comma built_query.append(" /* generated by server */"); - error|= write_bin_log(thd, !error, built_query.ptr(), built_query.length()); + write_bin_log(thd, !error, built_query.ptr(), built_query.length()); } /* @@ -2143,7 +2184,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, */ built_tmp_query.chop(); // Chop of the last comma built_tmp_query.append(" /* generated by server */"); - error|= write_bin_log(thd, !error, built_tmp_query.ptr(), built_tmp_query.length()); + write_bin_log(thd, !error, built_tmp_query.ptr(), built_tmp_query.length()); } } @@ -2157,10 +2198,45 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, */ } } - mysql_mutex_lock(&LOCK_open); -err_with_placeholders: - unlock_table_names(thd, tables, (TABLE_LIST*) 0); - mysql_mutex_unlock(&LOCK_open); +err: + if (!drop_temporary) + { + /* + Under LOCK TABLES we should release meta-data locks on the tables + which were dropped. Otherwise we can rely on close_thread_tables() + doing this. Unfortunately in this case we are likely to get more + false positives in try_acquire_lock() function. So + it makes sense to remove exclusive meta-data locks in all cases. + + Leave LOCK TABLES mode if we managed to drop all tables which were + 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) + unlock_table_names(thd); + else + { + if (thd->lock && thd->lock->table_count == 0 && non_temp_tables_count > 0) + { + thd->locked_tables_list.unlock_locked_tables(thd); + goto end; + } + for (table= tables; table; table= table->next_local) + { + if (table->mdl_request.ticket) + { + /* + Under LOCK TABLES we may have several instances of table open + and locked and therefore have to remove several metadata lock + requests associated with them. + */ + thd->mdl_context.release_all_locks_for_name(table->mdl_request.ticket); + } + } + } + } + +end: DBUG_RETURN(error); } @@ -3584,9 +3660,9 @@ void sp_prepare_create_field(THD *thd, Create_field *sql_field) RETURN VALUES NONE */ -static inline int write_create_table_bin_log(THD *thd, - const HA_CREATE_INFO *create_info, - bool internal_tmp_table) +static inline void write_create_table_bin_log(THD *thd, + const HA_CREATE_INFO *create_info, + bool internal_tmp_table) { /* Don't write statement if: @@ -3599,8 +3675,7 @@ static inline int write_create_table_bin_log(THD *thd, (!thd->current_stmt_binlog_row_based || (thd->current_stmt_binlog_row_based && !(create_info->options & HA_LEX_CREATE_TMP_TABLE)))) - return write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - return 0; + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } @@ -3623,9 +3698,9 @@ static inline int write_create_table_bin_log(THD *thd, If one creates a temporary table, this is automatically opened Note that this function assumes that caller already have taken - name-lock on table being created or used some other way to ensure - that concurrent operations won't intervene. mysql_create_table() - is a wrapper that can be used for this. + exclusive metadata lock on table being created or used some other + way to ensure that concurrent operations won't intervene. + mysql_create_table() is a wrapper that can be used for this. no_log is needed for the case of CREATE ... SELECT, as the logging will be done later in sql_insert.cc @@ -3869,7 +3944,8 @@ bool mysql_create_table_no_lock(THD *thd, push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), alias); - error= write_create_table_bin_log(thd, create_info, internal_tmp_table); + error= 0; + write_create_table_bin_log(thd, create_info, internal_tmp_table); goto err; } my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alias); @@ -4018,7 +4094,7 @@ bool mysql_create_table_no_lock(THD *thd, thd->thread_specific_used= TRUE; } - error= write_create_table_bin_log(thd, create_info, internal_tmp_table); + error= FALSE; unlock_and_end: mysql_mutex_unlock(&LOCK_open); @@ -4033,7 +4109,7 @@ warn: ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), alias); create_info->table_existed= 1; // Mark that table existed - error= write_create_table_bin_log(thd, create_info, internal_tmp_table); + write_create_table_bin_log(thd, create_info, internal_tmp_table); goto unlock_and_end; } @@ -4042,20 +4118,18 @@ warn: Database and name-locking aware wrapper for mysql_create_table_no_lock(), */ -bool mysql_create_table(THD *thd, const char *db, const char *table_name, +bool mysql_create_table(THD *thd, TABLE_LIST *create_table, HA_CREATE_INFO *create_info, - Alter_info *alter_info, - bool internal_tmp_table, - uint select_field_count) + Alter_info *alter_info) { - TABLE *name_lock= 0; bool result; DBUG_ENTER("mysql_create_table"); /* Wait for any database locks */ mysql_mutex_lock(&LOCK_lock_db); while (!thd->killed && - my_hash_search(&lock_db_cache,(uchar*) db, strlen(db))) + my_hash_search(&lock_db_cache, (uchar*)create_table->db, + create_table->db_length)) { wait_for_condition(thd, &LOCK_lock_db, &COND_refresh); mysql_mutex_lock(&LOCK_lock_db); @@ -4069,45 +4143,47 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name, creating_table++; mysql_mutex_unlock(&LOCK_lock_db); - if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) + /* + Open or obtain an exclusive metadata lock on table being created. + */ + if (open_and_lock_tables_derived(thd, thd->lex->query_tables, FALSE, + 0)) { - if (lock_table_name_if_not_cached(thd, db, table_name, &name_lock)) - { - result= TRUE; - goto unlock; - } - if (!name_lock) - { - if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) - { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), - table_name); - create_info->table_existed= 1; - result= FALSE; - write_create_table_bin_log(thd, create_info, internal_tmp_table); - } - else - { - my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name); - result= TRUE; - } - goto unlock; - } + result= TRUE; + goto unlock; } - result= mysql_create_table_no_lock(thd, db, table_name, create_info, - alter_info, - internal_tmp_table, - select_field_count); + /* Got lock. */ + DEBUG_SYNC(thd, "locked_table_name"); -unlock: - if (name_lock) + result= mysql_create_table_no_lock(thd, create_table->db, + create_table->table_name, create_info, + alter_info, FALSE, 0); + + /* + Don't write statement if: + - Table creation has failed + - Table has already existed + - Row-based logging is used and we are creating a temporary table + Otherwise, the statement shall be binlogged. + */ + if (!result && + !create_info->table_existed && + (!thd->current_stmt_binlog_row_based || + (thd->current_stmt_binlog_row_based && + !(create_info->options & HA_LEX_CREATE_TMP_TABLE)))) + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + + if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { - mysql_mutex_lock(&LOCK_open); - unlink_open_table(thd, name_lock, FALSE); - mysql_mutex_unlock(&LOCK_open); + /* + 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: mysql_mutex_lock(&LOCK_lock_db); if (!--creating_table && creating_database) mysql_cond_signal(&COND_refresh); @@ -4245,82 +4321,6 @@ mysql_rename_table(handlerton *base, const char *old_db, } -/* - Force all other threads to stop using the table - - SYNOPSIS - wait_while_table_is_used() - thd Thread handler - table Table to remove from cache - function HA_EXTRA_PREPARE_FOR_DROP if table is to be deleted - HA_EXTRA_FORCE_REOPEN if table is not be used - HA_EXTRA_PREPARE_FOR_RENAME if table is to be renamed - NOTES - When returning, the table will be unusable for other threads until - the table is closed. - - PREREQUISITES - Lock on LOCK_open - Win32 clients must also have a WRITE LOCK on the table ! -*/ - -void wait_while_table_is_used(THD *thd, TABLE *table, - enum ha_extra_function function) -{ - DBUG_ENTER("wait_while_table_is_used"); - DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu", - table->s->table_name.str, (ulong) table->s, - table->db_stat, table->s->version)); - - mysql_mutex_assert_owner(&LOCK_open); - - (void) table->file->extra(function); - /* Mark all tables that are in use as 'old' */ - mysql_lock_abort(thd, table, TRUE); /* end threads waiting on lock */ - - /* Wait until all there are no other threads that has this table open */ - remove_table_from_cache(thd, table->s->db.str, - table->s->table_name.str, - RTFC_WAIT_OTHER_THREAD_FLAG); - DBUG_VOID_RETURN; -} - -/* - Close a cached table - - SYNOPSIS - close_cached_table() - thd Thread handler - table Table to remove from cache - - NOTES - Function ends by signaling threads waiting for the table to try to - reopen the table. - - PREREQUISITES - Lock on LOCK_open - Win32 clients must also have a WRITE LOCK on the table ! -*/ - -void close_cached_table(THD *thd, TABLE *table) -{ - DBUG_ENTER("close_cached_table"); - - wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); - /* Close lock if this is not got with LOCK TABLES */ - if (thd->lock) - { - mysql_unlock_tables(thd, thd->lock); - thd->lock=0; // Start locked threads - } - /* Close all copies of 'table'. This also frees all LOCK TABLES lock */ - unlink_open_table(thd, table, TRUE); - - /* When lock on LOCK_open is freed other threads can continue */ - broadcast_refresh(); - DBUG_VOID_RETURN; -} - static int send_check_errmsg(THD *thd, TABLE_LIST* table, const char* operator_name, const char* errmsg) @@ -4337,29 +4337,62 @@ static int send_check_errmsg(THD *thd, TABLE_LIST* table, return 1; } + static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, HA_CHECK_OPT *check_opt) { int error= 0; TABLE tmp_table, *table; TABLE_SHARE *share; + bool has_mdl_lock= FALSE; char from[FN_REFLEN],tmp[FN_REFLEN+32]; const char **ext; MY_STAT stat_info; + Open_table_context ot_ctx_unused(thd); DBUG_ENTER("prepare_for_repair"); + uint reopen_for_repair_flags= (MYSQL_LOCK_IGNORE_FLUSH | + MYSQL_OPEN_HAS_MDL_LOCK); if (!(check_opt->sql_flags & TT_USEFRM)) DBUG_RETURN(0); - if (!(table= table_list->table)) /* if open_ltable failed */ + if (!(table= table_list->table)) { char key[MAX_DBKEY_LENGTH]; uint key_length; + MDL_request mdl_global_request; + MDL_request_list mdl_requests; + /* + If the table didn't exist, we have a shared metadata lock + on it that is left from mysql_admin_table()'s attempt to + open it. Release the shared metadata lock before trying to + acquire the exclusive lock to satisfy MDL asserts and avoid + deadlocks. + */ + thd->mdl_context.release_transactional_locks(); + /* + Attempt to do full-blown table open in mysql_admin_table() has failed. + Let us try to open at least a .FRM for this table. + */ + my_hash_value_type hash_value; key_length= create_table_def_key(thd, key, table_list, 0); + table_list->mdl_request.init(MDL_key::TABLE, + table_list->db, table_list->table_name, + MDL_EXCLUSIVE); + + mdl_global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); + mdl_requests.push_front(&table_list->mdl_request); + mdl_requests.push_front(&mdl_global_request); + + if (thd->mdl_context.acquire_locks(&mdl_requests)) + DBUG_RETURN(0); + has_mdl_lock= TRUE; + + hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length); mysql_mutex_lock(&LOCK_open); if (!(share= (get_table_share(thd, table_list, key, key_length, 0, - &error)))) + &error, hash_value)))) { mysql_mutex_unlock(&LOCK_open); DBUG_RETURN(0); // Can't open frm file @@ -4367,16 +4400,16 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, if (open_table_from_share(thd, share, "", 0, 0, 0, &tmp_table, FALSE)) { - release_table_share(share, RELEASE_NORMAL); + release_table_share(share); mysql_mutex_unlock(&LOCK_open); DBUG_RETURN(0); // Out of memory } - table= &tmp_table; mysql_mutex_unlock(&LOCK_open); + table= &tmp_table; } /* A MERGE table must not come here. */ - DBUG_ASSERT(!table->child_l); + DBUG_ASSERT(table->file->ht->db_type != DB_TYPE_MRG_MYISAM); /* REPAIR TABLE ... USE_FRM for temporary tables makes little sense. @@ -4423,68 +4456,69 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, my_snprintf(tmp, sizeof(tmp), "%s-%lx_%lx", from, current_pid, thd->thread_id); - /* If we could open the table, close it */ if (table_list->table) { - mysql_mutex_lock(&LOCK_open); - close_cached_table(thd, table); - mysql_mutex_unlock(&LOCK_open); - } - if (lock_and_wait_for_table_name(thd,table_list)) - { - error= -1; - goto end; + /* + Table was successfully open in mysql_admin_table(). Now we need + to close it, but leave it protected by exclusive metadata lock. + */ + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + goto end; + close_all_tables_for_name(thd, table_list->table->s, FALSE); + table_list->table= 0; } - if (mysql_file_rename(key_file_misc, from, tmp, MYF(MY_WME))) + /* + After this point we have an exclusive metadata lock on our table + in both cases when table was successfully open in mysql_admin_table() + and when it was open in prepare_for_repair(). + */ + + if (my_rename(from, tmp, MYF(MY_WME))) { - mysql_mutex_lock(&LOCK_open); - unlock_table_name(thd, table_list); - mysql_mutex_unlock(&LOCK_open); error= send_check_errmsg(thd, table_list, "repair", "Failed renaming data file"); goto end; } if (mysql_truncate(thd, table_list, 1)) { - mysql_mutex_lock(&LOCK_open); - unlock_table_name(thd, table_list); - mysql_mutex_unlock(&LOCK_open); error= send_check_errmsg(thd, table_list, "repair", "Failed generating table from .frm file"); goto end; } if (mysql_file_rename(key_file_misc, tmp, from, MYF(MY_WME))) { - mysql_mutex_lock(&LOCK_open); - unlock_table_name(thd, table_list); - mysql_mutex_unlock(&LOCK_open); error= send_check_errmsg(thd, table_list, "repair", "Failed restoring .MYD file"); goto end; } + if (thd->locked_tables_list.reopen_tables(thd)) + goto end; + /* Now we should be able to open the partially repaired table to finish the repair in the handler later on. */ - mysql_mutex_lock(&LOCK_open); - if (reopen_name_locked_table(thd, table_list, TRUE)) + if (open_table(thd, table_list, thd->mem_root, + &ot_ctx_unused, reopen_for_repair_flags)) { - unlock_table_name(thd, table_list); - mysql_mutex_unlock(&LOCK_open); error= send_check_errmsg(thd, table_list, "repair", "Failed to open partially repaired table"); goto end; } - mysql_mutex_unlock(&LOCK_open); end: + thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); if (table == &tmp_table) { mysql_mutex_lock(&LOCK_open); closefrm(table, 1); // Free allocated memory mysql_mutex_unlock(&LOCK_open); } + /* In case of a temporary table there will be no metadata lock. */ + if (error && has_mdl_lock) + thd->mdl_context.release_transactional_locks(); + DBUG_RETURN(error); } @@ -4518,8 +4552,6 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, int result_code; DBUG_ENTER("mysql_admin_table"); - if (end_active_trans(thd)) - DBUG_RETURN(1); field_list.push_back(item = new Item_empty_string("Table", NAME_CHAR_LEN*2)); item->maybe_null = 1; field_list.push_back(item = new Item_empty_string("Op", 10)); @@ -4532,13 +4564,14 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) DBUG_RETURN(TRUE); - mysql_ha_rm_tables(thd, tables, FALSE); + mysql_ha_rm_tables(thd, tables); for (table= tables; table; table= table->next_local) { char table_name[NAME_LEN*2+2]; char* db = table->db; bool fatal_error=0; + bool open_error; DBUG_PRINT("admin", ("table: '%s'.'%s'", table->db, table->table_name)); DBUG_PRINT("admin", ("extra_open_options: %u", extra_open_options)); @@ -4566,11 +4599,22 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, if (view_operator_func == NULL) table->required_type=FRMTYPE_TABLE; - open_and_lock_tables(thd, table); + open_error= open_and_lock_tables_derived(thd, table, TRUE, + MYSQL_OPEN_TAKE_UPGRADABLE_MDL); thd->no_warnings_for_error= 0; table->next_global= save_next_global; table->next_local= save_next_local; thd->open_options&= ~extra_open_options; + /* + Under locked tables, we know that the table can be opened, + so any errors opening the table are logical errors. + In these cases it does not make sense to try to repair. + */ + if (open_error && thd->locked_tables_mode) + { + result_code= HA_ADMIN_FAILED; + goto send_result; + } #ifdef WITH_PARTITION_STORAGE_ENGINE if (table->table) { @@ -4622,8 +4666,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, DBUG_PRINT("admin", ("calling prepare_func")); switch ((*prepare_func)(thd, table, check_opt)) { case 1: // error, message written to net - ha_autocommit_or_rollback(thd, 1); - end_trans(thd, ROLLBACK); + trans_rollback_stmt(thd); + trans_rollback(thd); close_thread_tables(thd); DBUG_PRINT("admin", ("simple error, admin next table")); continue; @@ -4694,9 +4738,10 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, length= my_snprintf(buff, sizeof(buff), ER(ER_OPEN_AS_READONLY), table_name); protocol->store(buff, length, system_charset_info); - ha_autocommit_or_rollback(thd, 0); - end_trans(thd, COMMIT); + trans_commit_stmt(thd); + trans_commit(thd); close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); lex->reset_query_tables_list(FALSE); table->table=0; // For query cache if (protocol->write()) @@ -4709,19 +4754,13 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, /* Close all instances of the table to allow repair to rename files */ if (lock_type == TL_WRITE && table->table->s->version) { - DBUG_PRINT("admin", ("removing table from cache")); - mysql_mutex_lock(&LOCK_open); - const char *old_message=thd->enter_cond(&COND_refresh, &LOCK_open, - "Waiting to get writelock"); - mysql_lock_abort(thd,table->table, TRUE); - remove_table_from_cache(thd, table->table->s->db.str, - table->table->s->table_name.str, - RTFC_WAIT_OTHER_THREAD_FLAG | - RTFC_CHECK_KILLED_FLAG); - thd->exit_cond(old_message); - DBUG_EXECUTE_IF("wait_in_mysql_admin_table", wait_for_kill_signal(thd);); - if (thd->killed) - goto err; + if (wait_while_table_is_used(thd, table->table, + HA_EXTRA_PREPARE_FOR_RENAME)) + goto err; + DBUG_EXECUTE_IF("wait_in_mysql_admin_table", + wait_for_kill_signal(thd); + if (thd->killed) + goto err;); /* Flush entries in the query cache involving this table. */ query_cache_invalidate3(thd, table->table, 0); open_for_modify= 0; @@ -4750,8 +4789,10 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, HA_ADMIN_NEEDS_ALTER)) { DBUG_PRINT("admin", ("recreating table")); - ha_autocommit_or_rollback(thd, 1); + trans_rollback_stmt(thd); + trans_rollback(thd); close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); tmp_disable_binlog(thd); // binlogging is done by caller if wanted result_code= mysql_recreate_table(thd, table); reenable_binlog(thd); @@ -4864,14 +4905,17 @@ send_result_message: reopen the table and do ha_innobase::analyze() on it. We have to end the row, so analyze could return more rows. */ + trans_commit_stmt(thd); + trans_commit(thd); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + DEBUG_SYNC(thd, "ha_admin_try_alter"); protocol->store(STRING_WITH_LEN("note"), system_charset_info); protocol->store(STRING_WITH_LEN( "Table does not support optimize, doing recreate + analyze instead"), system_charset_info); if (protocol->write()) goto err; - ha_autocommit_or_rollback(thd, 0); - close_thread_tables(thd); DBUG_PRINT("info", ("HA_ADMIN_TRY_ALTER, trying analyze...")); TABLE_LIST *save_next_local= table->next_local, *save_next_global= table->next_global; @@ -4887,13 +4931,19 @@ send_result_message: */ if (thd->stmt_da->is_ok()) thd->stmt_da->reset_diagnostics_area(); - ha_autocommit_or_rollback(thd, 0); + trans_commit_stmt(thd); + trans_commit(thd); close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); if (!result_code) // recreation went ok { + /* Clear the ticket released in close_thread_tables(). */ + table->mdl_request.ticket= NULL; if ((table->table= open_ltable(thd, table, lock_type, 0)) && ((result_code= table->table->file->ha_analyze(thd, check_opt)) > 0)) result_code= 0; // analyze went ok + if (result_code) // analyze failed + table->table->file->print_error(result_code, MYF(0)); } /* Start a new row for the final status row */ protocol->prepare_for_resend(); @@ -4973,19 +5023,38 @@ send_result_message: table->table->file->info(HA_STATUS_CONST); else { - mysql_mutex_lock(&LOCK_open); - remove_table_from_cache(thd, table->table->s->db.str, - table->table->s->table_name.str, RTFC_NO_FLAG); - mysql_mutex_unlock(&LOCK_open); + TABLE_LIST *save_next_global= table->next_global; + table->next_global= 0; + close_cached_tables(thd, table, FALSE, FALSE); + table->next_global= save_next_global; } /* May be something modified consequently we have to invalidate cache */ query_cache_invalidate3(thd, table->table, 0); } } - ha_autocommit_or_rollback(thd, 0); - end_trans(thd, COMMIT); + /* Error path, a admin command failed. */ + trans_commit_stmt(thd); + trans_commit_implicit(thd); close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); table->table=0; // For query cache + + /* + If it is CHECK TABLE v1, v2, v3, and v1, v2, v3 are views, we will run + separate open_tables() for each CHECK TABLE argument. + Right now we do not have a separate method to reset the prelocking + state in the lex to the state after parsing, so each open will pollute + this state: add elements to lex->srotuines_list, TABLE_LISTs to + lex->query_tables. Below is a lame attempt to recover from this + pollution. + @todo: have a method to reset a prelocking context, or use separate + contexts for each open. + */ + for (Sroutine_hash_entry *rt= + (Sroutine_hash_entry*)thd->lex->sroutines_list.first; + rt; rt= rt->next) + rt->mdl_request.ticket= NULL; + if (protocol->write()) goto err; } @@ -4994,9 +5063,10 @@ send_result_message: DBUG_RETURN(FALSE); err: - ha_autocommit_or_rollback(thd, 1); - end_trans(thd, ROLLBACK); + trans_rollback_stmt(thd); + trans_rollback(thd); close_thread_tables(thd); // Shouldn't be needed + thd->mdl_context.release_transactional_locks(); if (table) table->table=0; DBUG_RETURN(TRUE); @@ -5087,55 +5157,6 @@ bool mysql_preload_keys(THD* thd, TABLE_LIST* tables) } - -/** - @brief Create frm file based on I_S table - - @param[in] thd thread handler - @param[in] schema_table I_S table - @param[in] dst_path path where frm should be created - @param[in] create_info Create info - - @return Operation status - @retval 0 success - @retval 1 error -*/ - - -bool mysql_create_like_schema_frm(THD* thd, TABLE_LIST* schema_table, - char *dst_path, HA_CREATE_INFO *create_info) -{ - HA_CREATE_INFO local_create_info; - Alter_info alter_info; - bool tmp_table= (create_info->options & HA_LEX_CREATE_TMP_TABLE); - uint keys= schema_table->table->s->keys; - uint db_options= 0; - DBUG_ENTER("mysql_create_like_schema_frm"); - - bzero((char*) &local_create_info, sizeof(local_create_info)); - local_create_info.db_type= schema_table->table->s->db_type(); - local_create_info.row_type= schema_table->table->s->row_type; - local_create_info.default_table_charset=default_charset_info; - alter_info.flags= (ALTER_CHANGE_COLUMN | ALTER_RECREATE); - schema_table->table->use_all_columns(); - if (mysql_prepare_alter_table(thd, schema_table->table, - &local_create_info, &alter_info)) - DBUG_RETURN(1); - if (mysql_prepare_create_table(thd, &local_create_info, &alter_info, - tmp_table, &db_options, - schema_table->table->file, - &schema_table->table->s->key_info, &keys, 0)) - DBUG_RETURN(1); - local_create_info.max_rows= 0; - if (mysql_create_frm(thd, dst_path, NullS, NullS, - &local_create_info, alter_info.create_list, - keys, schema_table->table->s->key_info, - schema_table->table->file)) - DBUG_RETURN(1); - DBUG_RETURN(0); -} - - /* Create a table identical to the specified table @@ -5154,12 +5175,8 @@ bool mysql_create_like_schema_frm(THD* thd, TABLE_LIST* schema_table, bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, HA_CREATE_INFO *create_info) { - TABLE *name_lock= 0; - char src_path[FN_REFLEN], dst_path[FN_REFLEN + 1]; - uint dst_path_length; - char *db= table->db; - char *table_name= table->table_name; - int err; + HA_CREATE_INFO local_create_info; + Alter_info local_alter_info; bool res= TRUE; uint not_used; #ifdef WITH_PARTITION_STORAGE_ENGINE @@ -5171,157 +5188,64 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, /* - By opening source table we guarantee that it exists and no concurrent - DDL operation will mess with it. Later we also take an exclusive - name-lock on target table name, which makes copying of .frm file, - call to ha_create_table() and binlogging atomic against concurrent DML - and DDL operations on target table. Thus by holding both these "locks" - we ensure that our statement is properly isolated from all concurrent - operations which matter. - */ - if (open_tables(thd, &src_table, ¬_used, 0)) - DBUG_RETURN(TRUE); - - /* - For bug#25875, Newly created table through CREATE TABLE .. LIKE - has no ndb_dd attributes; - Add something to get possible tablespace info from src table, - it can get valid tablespace name only for disk-base ndb table + We the open source table to get its description in HA_CREATE_INFO + and Alter_info objects. This also acquires a shared metadata lock + on this table which ensures that no concurrent DDL operation will + mess with it. + Also in case when we create non-temporary table open_tables() + call obtains an exclusive metadata lock on target table ensuring + that we can safely perform table creation. + Thus by holding both these locks we ensure that our statement is + properly isolated from all concurrent operations which matter. */ - if ((src_table->table->file->get_tablespace_name(thd, ts_name, FN_LEN))) - { - create_info->tablespace= ts_name; - create_info->storage_media= HA_SM_DISK; - } - - strxmov(src_path, src_table->table->s->path.str, reg_ext, NullS); + if (open_tables(thd, &thd->lex->query_tables, ¬_used, 0)) + goto err; + src_table->table->use_all_columns(); - DBUG_EXECUTE_IF("sleep_create_like_before_check_if_exists", my_sleep(6000000);); + /* Fill HA_CREATE_INFO and Alter_info with description of source table. */ + bzero((char*) &local_create_info, sizeof(local_create_info)); + local_create_info.db_type= src_table->table->s->db_type(); + local_create_info.row_type= src_table->table->s->row_type; + if (mysql_prepare_alter_table(thd, src_table->table, &local_create_info, + &local_alter_info)) + goto err; +#ifdef WITH_PARTITION_STORAGE_ENGINE + /* Partition info is not handled by mysql_prepare_alter_table() call. */ + if (src_table->table->part_info) + thd->work_part_info= src_table->table->part_info->get_clone(); +#endif /* - Check that destination tables does not exist. Note that its name - was already checked when it was added to the table list. - */ - if (create_info->options & HA_LEX_CREATE_TMP_TABLE) - { - if (find_temporary_table(thd, db, table_name)) - goto table_exists; - dst_path_length= build_tmptable_filename(thd, dst_path, sizeof(dst_path)); - create_info->table_options|= HA_CREATE_DELAY_KEY_WRITE; - } - else - { - if (lock_table_name_if_not_cached(thd, db, table_name, &name_lock)) - goto err; - if (!name_lock) - goto table_exists; - dst_path_length= build_table_filename(dst_path, sizeof(dst_path) - 1, - db, table_name, reg_ext, 0); - if (!access(dst_path, F_OK)) - goto table_exists; - } - - DBUG_EXECUTE_IF("sleep_create_like_before_copy", my_sleep(6000000);); + Adjust description of source table before using it for creation of + target table. - if (opt_sync_frm && !(create_info->options & HA_LEX_CREATE_TMP_TABLE)) - flags|= MY_SYNC; - - /* - Create a new table by copying from source table - and sync the new table if the flag MY_SYNC is set - - Altough exclusive name-lock on target table protects us from concurrent - DML and DDL operations on it we still want to wrap .FRM creation and call - to ha_create_table() in critical section protected by LOCK_open in order - to provide minimal atomicity against operations which disregard name-locks, - like I_S implementation, for example. This is a temporary and should not - be copied. Instead we should fix our code to always honor name-locks. - - Also some engines (e.g. NDB cluster) require that LOCK_open should be held - during the call to ha_create_table(). See bug #28614 for more info. + Similarly to SHOW CREATE TABLE we ignore MAX_ROWS attribute of + temporary table which represents I_S table. */ - mysql_mutex_lock(&LOCK_open); if (src_table->schema_table) - { - if (mysql_create_like_schema_frm(thd, src_table, dst_path, create_info)) - { - mysql_mutex_unlock(&LOCK_open); - goto err; - } - } - else if (my_copy(src_path, dst_path, flags)) - { - if (my_errno == ENOENT) - my_error(ER_BAD_DB_ERROR,MYF(0),db); - else - my_error(ER_CANT_CREATE_FILE,MYF(0),dst_path,my_errno); - mysql_mutex_unlock(&LOCK_open); + local_create_info.max_rows= 0; + /* Set IF NOT EXISTS option as in the CREATE TABLE LIKE statement. */ + local_create_info.options|= create_info->options&HA_LEX_CREATE_IF_NOT_EXISTS; + /* Replace type of source table with one specified in the statement. */ + local_create_info.options&= ~HA_LEX_CREATE_TMP_TABLE; + local_create_info.options|= create_info->options & HA_LEX_CREATE_TMP_TABLE; + /* Reset auto-increment counter for the new table. */ + local_create_info.auto_increment_value= 0; + + if ((res= mysql_create_table_no_lock(thd, table->db, table->table_name, + &local_create_info, &local_alter_info, + FALSE, 0)) || + local_create_info.table_existed) goto err; - } /* - As mysql_truncate don't work on a new table at this stage of - creation, instead create the table directly (for both normal - and temporary tables). - */ -#ifdef WITH_PARTITION_STORAGE_ENGINE - /* - For partitioned tables we need to copy the .par file as well since - it is used in open_table_def to even be able to create a new handler. - There is no way to find out here if the original table is a - partitioned table so we copy the file and ignore any errors. + Ensure that we have an exclusive lock on target table if we are creating + non-temporary table. */ - fn_format(tmp_path, dst_path, reg_ext, ".par", MYF(MY_REPLACE_EXT)); - strmov(dst_path, tmp_path); - fn_format(tmp_path, src_path, reg_ext, ".par", MYF(MY_REPLACE_EXT)); - strmov(src_path, tmp_path); - my_copy(src_path, dst_path, MYF(MY_DONT_OVERWRITE_FILE)); -#endif - - DBUG_EXECUTE_IF("sleep_create_like_before_ha_create", my_sleep(6000000);); - - dst_path[dst_path_length - reg_ext_length]= '\0'; // Remove .frm - if (thd->variables.keep_files_on_create) - create_info->options|= HA_CREATE_KEEP_FILES; - err= ha_create_table(thd, dst_path, db, table_name, create_info, 1); - mysql_mutex_unlock(&LOCK_open); - - if (create_info->options & HA_LEX_CREATE_TMP_TABLE) - { - if (err || !open_temporary_table(thd, dst_path, db, table_name, 1)) - { - (void) rm_temporary_table(create_info->db_type, - dst_path); /* purecov: inspected */ - goto err; /* purecov: inspected */ - } - thd->thread_specific_used= TRUE; - } - else if (err) - { - (void) quick_rm_table(create_info->db_type, db, - table_name, 0); /* purecov: inspected */ - goto err; /* purecov: inspected */ - } - -goto binlog; - -table_exists: - if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) - { - char warn_buff[MYSQL_ERRMSG_SIZE]; - my_snprintf(warn_buff, sizeof(warn_buff), - ER(ER_TABLE_EXISTS_ERROR), table_name); - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_TABLE_EXISTS_ERROR,warn_buff); - } - else - { - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name); - goto err; - } - -binlog: - DBUG_EXECUTE_IF("sleep_create_like_before_binlogging", my_sleep(6000000);); + DBUG_ASSERT((create_info->options & HA_LEX_CREATE_TMP_TABLE) || + thd->mdl_context.is_lock_owner(MDL_key::TABLE, table->db, + table->table_name, + MDL_EXCLUSIVE)); /* We have to write the query before we unlock the tables. @@ -5350,58 +5274,45 @@ binlog: char buf[2048]; String query(buf, sizeof(buf), system_charset_info); query.length(0); // Have to zero it since constructor doesn't - + Open_table_context ot_ctx_unused(thd); /* Here we open the destination table, on which we already have - name-lock. This is needed for store_create_info() to work. - The table will be closed by unlink_open_table() at the end - of this function. + exclusive metadata lock. This is needed for store_create_info() + to work. The table will be closed by close_thread_table() at + the end of this branch. */ - table->table= name_lock; - mysql_mutex_lock(&LOCK_open); - if (reopen_name_locked_table(thd, table, FALSE)) - { - mysql_mutex_unlock(&LOCK_open); + if (open_table(thd, table, thd->mem_root, &ot_ctx_unused, + MYSQL_OPEN_REOPEN)) goto err; - } - mysql_mutex_unlock(&LOCK_open); - /* - The condition avoids a crash as described in BUG#48506. Other - binlogging problems related to CREATE TABLE IF NOT EXISTS LIKE - when the existing object is a view will be solved by BUG 47442. - */ - if (!table->view) - { - int result __attribute__((unused))= - store_create_info(thd, table, &query, - create_info, FALSE /* show_database */); + int result __attribute__((unused))= + store_create_info(thd, table, &query, + create_info, FALSE /* show_database */); - DBUG_ASSERT(result == 0); // store_create_info() always return 0 - if (write_bin_log(thd, TRUE, query.ptr(), query.length())) - goto err; - } + DBUG_ASSERT(result == 0); // store_create_info() always return 0 + write_bin_log(thd, TRUE, query.ptr(), query.length()); + + DBUG_ASSERT(thd->open_tables == table->table); + mysql_mutex_lock(&LOCK_open); + /* + When opening the table, we ignored the locked tables + (MYSQL_OPEN_GET_NEW_TABLE). Now we can close the table without + risking to close some locked table. + */ + close_thread_table(thd, &thd->open_tables); + mysql_mutex_unlock(&LOCK_open); } else // Case 1 - if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) - goto err; + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } /* Case 3 and 4 does nothing under RBR */ } - else if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) - goto err; - - res= FALSE; + else + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); err: - if (name_lock) - { - mysql_mutex_lock(&LOCK_open); - unlink_open_table(thd, name_lock, FALSE); - mysql_mutex_unlock(&LOCK_open); - } DBUG_RETURN(res); } @@ -5474,17 +5385,17 @@ mysql_discard_or_import_tablespace(THD *thd, query_cache_invalidate3(thd, table_list, 0); /* The ALTER TABLE is always in its own transaction */ - error = ha_autocommit_or_rollback(thd, 0); - if (end_active_trans(thd)) + error= trans_commit_stmt(thd); + if (trans_commit_implicit(thd)) error=1; if (error) goto err; - error= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + write_bin_log(thd, FALSE, thd->query(), thd->query_length()); err: - ha_autocommit_or_rollback(thd, error); + trans_rollback_stmt(thd); thd->tablespace_op=FALSE; - + if (error == 0) { my_ok(thd); @@ -5492,7 +5403,7 @@ err: } table->file->print_error(error, MYF(0)); - + DBUG_RETURN(-1); } @@ -5885,7 +5796,6 @@ bool alter_table_manage_keys(TABLE *table, int indexes_were_disabled, DBUG_RETURN(error); } - /** maximum possible length for certain blob types. @@ -6378,7 +6288,10 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, Alter_info *alter_info, uint order_num, ORDER *order, bool ignore) { - TABLE *table, *new_table= 0, *name_lock= 0; + TABLE *table, *new_table= 0; + MDL_ticket *mdl_ticket; + MDL_request target_mdl_request; + bool has_target_mdl_lock= FALSE; int error= 0; char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN + 1]; char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias; @@ -6461,7 +6374,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, build_table_filename(reg_path, sizeof(reg_path) - 1, db, table_name, reg_ext, 0); build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0); - mysql_ha_rm_tables(thd, table_list, FALSE); + mysql_ha_rm_tables(thd, table_list); /* DISCARD/IMPORT TABLESPACE is always alone in an ALTER TABLE */ if (alter_info->tablespace_op != NO_TABLESPACE_OP) @@ -6508,22 +6421,23 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, if the user is trying to to do this in a transcation context */ - if (thd->locked_tables || thd->active_transaction()) + if (thd->locked_tables_mode || thd->active_transaction()) { my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); DBUG_RETURN(TRUE); } - if (wait_if_global_read_lock(thd,0,1)) + if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) DBUG_RETURN(TRUE); - mysql_mutex_lock(&LOCK_open); if (lock_table_names(thd, table_list)) { error= 1; goto view_err; } - + + mysql_mutex_lock(&LOCK_open); + if (!do_rename(thd, table_list, new_db, new_name, new_name, 1)) { if (mysql_bin_log.is_open()) @@ -6531,24 +6445,41 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, thd->clear_error(); Query_log_event qinfo(thd, thd->query(), thd->query_length(), 0, FALSE, 0); - if (error= mysql_bin_log.write(&qinfo)) - goto view_err_unlock; + mysql_bin_log.write(&qinfo); } my_ok(thd); } + mysql_mutex_unlock(&LOCK_open); -view_err_unlock: - unlock_table_names(thd, table_list, (TABLE_LIST*) 0); + unlock_table_names(thd); view_err: - mysql_mutex_unlock(&LOCK_open); - start_waiting_global_read_lock(thd); + thd->global_read_lock.start_waiting_global_read_lock(thd); DBUG_RETURN(error); } - if (!(table= open_n_lock_single_table(thd, table_list, TL_WRITE_ALLOW_READ))) + + /* + Code below can handle only base tables so ensure that we won't open a view. + Note that RENAME TABLE the only ALTER clause which is supported for views + has been already processed. + */ + table_list->required_type= FRMTYPE_TABLE; + + Alter_table_prelocking_strategy alter_prelocking_strategy(alter_info); + + error= open_and_lock_tables_derived(thd, table_list, FALSE, + MYSQL_OPEN_TAKE_UPGRADABLE_MDL, + &alter_prelocking_strategy); + + if (error) + { DBUG_RETURN(TRUE); + } + + table= table_list->table; table->use_all_columns(); + mdl_ticket= table->mdl_ticket; /* Prohibit changing of the UNION list of a non-temporary MERGE table @@ -6556,7 +6487,8 @@ view_err: set of tables from the old table or to open a new TABLE object for an extended list and verify that they belong to locked tables. */ - if (thd->locked_tables && + if ((thd->locked_tables_mode == LTM_LOCK_TABLES || + thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) && (create_info->used_fields & HA_CREATE_USED_UNION) && (table->s->tmp_table == NO_TMP_TABLE)) { @@ -6600,14 +6532,30 @@ view_err: } else { - if (lock_table_name_if_not_cached(thd, new_db, new_name, &name_lock)) + target_mdl_request.init(MDL_key::TABLE, new_db, new_name, + MDL_EXCLUSIVE); + /* + Global intention exclusive lock must have been already acquired when + table to be altered was open, so there is no need to do it here. + */ + DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::GLOBAL, + "", "", + MDL_INTENTION_EXCLUSIVE)); + + if (thd->mdl_context.try_acquire_lock(&target_mdl_request)) DBUG_RETURN(TRUE); - if (!name_lock) + if (target_mdl_request.ticket == NULL) { + /* Table exists and is locked by some thread. */ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias); DBUG_RETURN(TRUE); } - + DEBUG_SYNC(thd, "locked_table_name"); + has_target_mdl_lock= TRUE; + /* + Table maybe does not exist, but we got an exclusive lock + on the name, now we can safely try to find out for sure. + */ build_table_filename(new_name_buff, sizeof(new_name_buff) - 1, new_db, new_name_buff, reg_ext, 0); if (!access(new_name_buff, F_OK)) @@ -6693,26 +6641,15 @@ view_err: case LEAVE_AS_IS: break; case ENABLE: - /* - wait_while_table_is_used() ensures that table being altered is - opened only by this thread and that TABLE::TABLE_SHARE::version - of TABLE object corresponding to this table is 0. - The latter guarantees that no DML statement will open this table - until ALTER TABLE finishes (i.e. until close_thread_tables()) - while the fact that the table is still open gives us protection - from concurrent DDL statements. - */ - mysql_mutex_lock(&LOCK_open); - wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); - mysql_mutex_unlock(&LOCK_open); + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + 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: - mysql_mutex_lock(&LOCK_open); - wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); - mysql_mutex_unlock(&LOCK_open); + 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; @@ -6725,53 +6662,51 @@ view_err: { error= 0; push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), - table->alias); + ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), + table->alias); } - mysql_mutex_lock(&LOCK_open); - /* - Unlike to the above case close_cached_table() below will remove ALL - instances of TABLE from table cache (it will also remove table lock - held by this thread). So to make actual table renaming and writing - to binlog atomic we have to put them into the same critical section - protected by LOCK_open mutex. This also removes gap for races between - access() and mysql_rename_table() calls. - */ - if (!error && (new_name != table_name || new_db != db)) { thd_proc_info(thd, "rename"); /* Then do a 'simple' rename of the table. First we need to close all instances of 'source' table. + Note that if wait_while_table_is_used() returns error here (i.e. if + this thread was killed) then it must be that previous step of + simple rename did nothing and therefore we can safely return + without additional clean-up. */ - close_cached_table(thd, table); + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + goto err; + close_all_tables_for_name(thd, table->s, TRUE); /* Then, we want check once again that target table does not exist. Actually the order of these two steps does not matter since - earlier we took name-lock on the target table, so we do them - in this particular order only to be consistent with 5.0, in which - we don't take this name-lock and where this order really matters. + earlier we took exclusive metadata lock on the target table, so + we do them in this particular order only to be consistent with 5.0, + in which we don't take this lock and where this order really matters. TODO: Investigate if we need this access() check at all. */ if (!access(new_name_buff,F_OK)) { - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name); - error= -1; + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name); + error= -1; } else { - *fn_ext(new_name)=0; - if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias, 0)) - error= -1; + *fn_ext(new_name)=0; + mysql_mutex_lock(&LOCK_open); + if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias, 0)) + error= -1; else if (Table_triggers_list::change_table_name(thd, db, table_name, new_db, new_alias)) { (void) mysql_rename_table(old_db_type, new_db, new_alias, db, - table_name, 0); + table_name, 0); error= -1; } + mysql_mutex_unlock(&LOCK_open); } } @@ -6779,26 +6714,39 @@ view_err: { error= 0; push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), - table->alias); + ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA), + table->alias); } if (!error) { - error= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - if (!error) - my_ok(thd); + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + my_ok(thd); } else if (error > 0) { table->file->print_error(error, MYF(0)); error= -1; } - if (name_lock) - unlink_open_table(thd, name_lock, FALSE); - mysql_mutex_unlock(&LOCK_open); table_list->table= NULL; // For query cache query_cache_invalidate3(thd, table_list, 0); + + if ((thd->locked_tables_mode == LTM_LOCK_TABLES || + thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)) + { + /* + Under LOCK TABLES we should adjust meta-data locks before finishing + statement. Otherwise we can rely on close_thread_tables() releasing + them. + */ + if (new_name != table_name || new_db != db) + { + thd->mdl_context.release_lock(target_mdl_request.ticket); + thd->mdl_context.release_all_locks_for_name(mdl_ticket); + } + else + mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE); + } DBUG_RETURN(error); } @@ -6824,7 +6772,7 @@ view_err: if (mysql_prepare_alter_table(thd, table, create_info, alter_info)) goto err; - + need_copy_table= alter_info->change_level; set_table_default_charset(thd, create_info, db); @@ -6848,7 +6796,7 @@ view_err: &index_add_buffer, &index_add_count, &candidate_key_count)) goto err; - + if (need_copy_table == ALTER_TABLE_METADATA_ONLY) need_copy_table= need_copy_table_res; } @@ -7030,7 +6978,6 @@ view_err: #ifdef WITH_PARTITION_STORAGE_ENGINE if (fast_alter_partition) { - DBUG_ASSERT(!name_lock); DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info, create_info, table_list, db, table_name, @@ -7110,13 +7057,15 @@ view_err: { if (table->s->tmp_table) { + Open_table_context ot_ctx_unused(thd); TABLE_LIST tbl; bzero((void*) &tbl, sizeof(tbl)); tbl.db= new_db; tbl.table_name= tbl.alias= tmp_name; /* Table is in thd->temporary_tables */ - new_table= open_table(thd, &tbl, thd->mem_root, (bool*) 0, - MYSQL_LOCK_IGNORE_FLUSH); + (void) open_table(thd, &tbl, thd->mem_root, &ot_ctx_unused, + MYSQL_LOCK_IGNORE_FLUSH); + new_table= tbl.table; } else { @@ -7125,10 +7074,10 @@ view_err: build_table_filename(path, sizeof(path) - 1, new_db, tmp_name, "", FN_IS_TMP); /* Open our intermediate table */ - new_table=open_temporary_table(thd, path, new_db, tmp_name,0); + new_table= open_temporary_table(thd, path, new_db, tmp_name, 1); } if (!new_table) - goto err1; + goto err_new_table_cleanup; /* Note: In case of MERGE table, we do not attach children. We do not copy data for MERGE tables. Only the children have data. @@ -7149,6 +7098,10 @@ view_err: new_table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; new_table->next_number_field=new_table->found_next_number_field; thd_proc_info(thd, "copy to tmp table"); + DBUG_EXECUTE_IF("abort_copy_table", { + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); + goto err_new_table_cleanup; + }); error= copy_data_between_tables(table, new_table, alter_info->create_list, ignore, order_num, order, &copied, &deleted, @@ -7157,14 +7110,14 @@ view_err: } else { - mysql_mutex_lock(&LOCK_open); - wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN); - mysql_mutex_unlock(&LOCK_open); + if (!table->s->tmp_table && + wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + goto err_new_table_cleanup; thd_proc_info(thd, "manage keys"); alter_table_manage_keys(table, table->file->indexes_are_disabled(), alter_info->keys_onoff); - error= ha_autocommit_or_rollback(thd, 0); - if (end_active_trans(thd)) + error= trans_commit_stmt(thd); + if (trans_commit_implicit(thd)) error= 1; } thd->count_cuted_fields= CHECK_FIELD_IGNORE; @@ -7210,7 +7163,7 @@ view_err: table->key_info= key_info; table->file->print_error(error, MYF(0)); table->key_info= save_key_info; - goto err1; + goto err_new_table_cleanup; } } /*end of if (index_add_count)*/ @@ -7233,14 +7186,14 @@ view_err: index_drop_count))) { table->file->print_error(error, MYF(0)); - goto err1; + goto err_new_table_cleanup; } /* Tell the handler to finally drop the indexes. */ if ((error= table->file->final_drop_index(table))) { table->file->print_error(error, MYF(0)); - goto err1; + goto err_new_table_cleanup; } } /*end of if (index_drop_count)*/ @@ -7252,19 +7205,21 @@ view_err: /* Need to commit before a table is unlocked (NDB requirement). */ DBUG_PRINT("info", ("Committing before unlocking table")); - if (ha_autocommit_or_rollback(thd, 0) || end_active_trans(thd)) - goto err1; + if (trans_commit_stmt(thd) || trans_commit_implicit(thd)) + goto err_new_table_cleanup; committed= 1; } /*end of if (! new_table) for add/drop index*/ + if (error) + goto err_new_table_cleanup; + if (table->s->tmp_table != NO_TMP_TABLE) { - /* We changed a temporary table */ - if (error) - goto err1; /* Close lock if this is a transactional table */ - if (thd->lock) + if (thd->lock && + ! (thd->locked_tables_mode == LTM_LOCK_TABLES || + thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)) { mysql_unlock_tables(thd, thd->lock); thd->lock=0; @@ -7273,45 +7228,39 @@ view_err: close_temporary_table(thd, table, 1, 1); /* Should pass the 'new_name' as we store table name in the cache */ if (rename_temporary_table(thd, new_table, new_db, new_name)) - goto err1; + goto err_new_table_cleanup; /* We don't replicate alter table statement on temporary tables */ - if (!thd->current_stmt_binlog_row_based && - write_bin_log(thd, TRUE, thd->query(), thd->query_length())) - DBUG_RETURN(TRUE); + if (!thd->current_stmt_binlog_row_based) + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); goto end_temporary; } + /* + Close the intermediate table that will be the new table, but do + not delete it! Even altough MERGE tables do not have their children + attached here it is safe to call close_temporary_table(). + */ if (new_table) { - /* - Close the intermediate table that will be the new table. - Note that MERGE tables do not have their children attached here. - */ - intern_close_table(new_table); - my_free(new_table,MYF(0)); - } - mysql_mutex_lock(&LOCK_open); - if (error) - { - (void) quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP); - mysql_mutex_unlock(&LOCK_open); - goto err; + close_temporary_table(thd, new_table, 1, 0); + new_table= 0; } /* Data is copied. Now we: - 1) Wait until all other threads close old version of table. + 1) Wait until all other threads will stop using old version of table + by upgrading shared metadata lock to exclusive one. 2) Close instances of table open by this thread and replace them - with exclusive name-locks. + with placeholders to simplify reopen process. 3) Rename the old table to a temp name, rename the new one to the old name. 4) If we are under LOCK TABLES and don't do ALTER TABLE ... RENAME we reopen new version of table. 5) Write statement to the binary log. 6) If we are under LOCK TABLES and do ALTER TABLE ... RENAME we - remove name-locks from list of open tables and table cache. + remove placeholders and release metadata locks. 7) If we are not not under LOCK TABLES we rely on close_thread_tables() - call to remove name-locks from table cache and list of open table. + call to remove placeholders and releasing metadata locks. */ thd_proc_info(thd, "rename result table"); @@ -7320,10 +7269,15 @@ view_err: if (lower_case_table_names) my_casedn_str(files_charset_info, old_name); - wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME); - close_data_files_and_morph_locks(thd, db, table_name); + if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME)) + goto err_new_table_cleanup; + + + close_all_tables_for_name(thd, table->s, + new_name != table_name || new_db != db); error=0; + table_list->table= table= 0; /* Safety */ save_old_db_type= old_db_type; /* @@ -7345,6 +7299,7 @@ view_err: /* This type cannot happen in regular ALTER. */ new_db_type= old_db_type= NULL; } + mysql_mutex_lock(&LOCK_open); if (mysql_rename_table(old_db_type, db, table_name, db, old_name, FN_TO_IS_TMP)) { @@ -7368,10 +7323,15 @@ view_err: FN_FROM_IS_TMP); } + if (! error) + (void) quick_rm_table(old_db_type, db, old_name, FN_IS_TMP); + + mysql_mutex_unlock(&LOCK_open); + if (error) { /* This shouldn't happen. But let us play it safe. */ - goto err_with_placeholders; + goto err_with_mdl; } if (need_copy_table == ALTER_TABLE_METADATA_ONLY) @@ -7381,6 +7341,7 @@ view_err: To do this we need to obtain a handler object for it. NO need to tamper with MERGE tables. The real open is done later. */ + Open_table_context ot_ctx_unused(thd); TABLE *t_table; if (new_name != table_name || new_db != db) { @@ -7389,45 +7350,39 @@ view_err: table_list->table_name_length= strlen(new_name); table_list->db= new_db; table_list->db_length= strlen(new_db); - table_list->table= name_lock; - if (reopen_name_locked_table(thd, table_list, FALSE)) - goto err_with_placeholders; - t_table= table_list->table; + table_list->mdl_request.ticket= target_mdl_request.ticket; } else { - if (reopen_table(table)) - goto err_with_placeholders; - t_table= table; - } - /* Tell the handler that a new frm file is in place. */ - if (t_table->file->ha_create_handler_files(path, NULL, CHF_INDEX_FLAG, - create_info)) - goto err_with_placeholders; - if (thd->locked_tables && new_name == table_name && new_db == db) - { /* - We are going to reopen table down on the road, so we have to restore - state of the TABLE object which we used for obtaining of handler - object to make it suitable for reopening. + Under LOCK TABLES, we have a different mdl_lock_ticket + points to a different instance than the one set initially + to request the lock. */ - DBUG_ASSERT(t_table == table); - table->open_placeholder= 1; - close_handle_and_leave_table_as_lock(table); + table_list->mdl_request.ticket= mdl_ticket; } - } + if (open_table(thd, table_list, thd->mem_root, + &ot_ctx_unused, MYSQL_OPEN_REOPEN)) + { + goto err_with_mdl; + } + t_table= table_list->table; - (void) quick_rm_table(old_db_type, db, old_name, FN_IS_TMP); + /* Tell the handler that a new frm file is in place. */ + error= t_table->file->ha_create_handler_files(path, NULL, CHF_INDEX_FLAG, + create_info); + + DBUG_ASSERT(thd->open_tables == t_table); + mysql_mutex_lock(&LOCK_open); + close_thread_table(thd, &thd->open_tables); + mysql_mutex_unlock(&LOCK_open); + table_list->table= 0; - if (thd->locked_tables && new_name == table_name && new_db == db) - { - thd->in_lock_tables= 1; - error= reopen_tables(thd, 1, 1); - thd->in_lock_tables= 0; if (error) - goto err_with_placeholders; + goto err_with_mdl; } - mysql_mutex_unlock(&LOCK_open); + if (thd->locked_tables_list.reopen_tables(thd)) + goto err_with_mdl; thd_proc_info(thd, "end"); @@ -7440,8 +7395,7 @@ view_err: DBUG_ASSERT(!(mysql_bin_log.is_open() && thd->current_stmt_binlog_row_based && (create_info->options & HA_LEX_CREATE_TMP_TABLE))); - if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) - DBUG_RETURN(TRUE); + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); if (ha_check_storage_engine_flag(old_db_type, HTON_FLUSH_AFTER_RENAME)) { @@ -7467,18 +7421,16 @@ view_err: table_list->table=0; // For query cache query_cache_invalidate3(thd, table_list, 0); - if (thd->locked_tables && (new_name != table_name || new_db != db)) + if (thd->locked_tables_mode == LTM_LOCK_TABLES || + thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) { - /* - If are we under LOCK TABLES and did ALTER TABLE with RENAME we need - to remove placeholders for the old table and for the target table - from the list of open tables and table cache. If we are not under - LOCK TABLES we can rely on close_thread_tables() doing this job. - */ - mysql_mutex_lock(&LOCK_open); - unlink_open_table(thd, table, FALSE); - unlink_open_table(thd, name_lock, FALSE); - mysql_mutex_unlock(&LOCK_open); + if ((new_name != table_name || new_db != db)) + { + thd->mdl_context.release_lock(target_mdl_request.ticket); + thd->mdl_context.release_all_locks_for_name(mdl_ticket); + } + else + mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE); } end_temporary: @@ -7486,10 +7438,9 @@ end_temporary: (ulong) (copied + deleted), (ulong) deleted, (ulong) thd->warning_info->statement_warn_count()); my_ok(thd, copied + deleted, 0L, tmp_name); - thd->some_tables_deleted=0; DBUG_RETURN(FALSE); -err1: +err_new_table_cleanup: if (new_table) { /* close_temporary_table() frees the new_table pointer. */ @@ -7533,24 +7484,23 @@ err: alter_info->datetime_field->field_name); thd->abort_on_warning= save_abort_on_warning; } - if (name_lock) - { - mysql_mutex_lock(&LOCK_open); - unlink_open_table(thd, name_lock, FALSE); - mysql_mutex_unlock(&LOCK_open); - } + if (has_target_mdl_lock) + thd->mdl_context.release_lock(target_mdl_request.ticket); + DBUG_RETURN(TRUE); -err_with_placeholders: +err_with_mdl: /* - An error happened while we were holding exclusive name-lock on table - being altered. To be safe under LOCK TABLES we should remove placeholders - from list of open tables list and table cache. + An error happened while we were holding exclusive name metadata lock + on table being altered. To be safe under LOCK TABLES we should + remove all references to the altered table from the list of locked + tables and release the exclusive metadata lock. */ - unlink_open_table(thd, table, FALSE); - if (name_lock) - unlink_open_table(thd, name_lock, FALSE); - mysql_mutex_unlock(&LOCK_open); + thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); + if (has_target_mdl_lock) + thd->mdl_context.release_lock(target_mdl_request.ticket); + + thd->mdl_context.release_all_locks_for_name(mdl_ticket); DBUG_RETURN(TRUE); } /* mysql_alter_table */ @@ -7743,7 +7693,7 @@ copy_data_between_tables(TABLE *from,TABLE *to, if (to->file->ha_end_bulk_insert() && error <= 0) { to->file->print_error(my_errno,MYF(0)); - error=1; + error= 1; } to->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); @@ -7757,9 +7707,9 @@ copy_data_between_tables(TABLE *from,TABLE *to, Ensure that the new table is saved properly to disk so that we can do a rename */ - if (ha_autocommit_or_rollback(thd, 0)) + if (trans_commit_stmt(thd)) error=1; - if (end_active_trans(thd)) + if (trans_commit_implicit(thd)) error=1; err: @@ -7771,6 +7721,8 @@ copy_data_between_tables(TABLE *from,TABLE *to, to->file->ha_release_auto_increment(); if (to->file->ha_external_lock(thd,F_UNLCK)) error=1; + if (error < 0 && to->file->extra(HA_EXTRA_PREPARE_FOR_RENAME)) + error= 1; DBUG_RETURN(error > 0 ? -1 : 0); } @@ -7798,6 +7750,8 @@ bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list) uninitialized data. open_tables() could fail. */ table_list->table= NULL; + /* Same applies to MDL ticket. */ + table_list->mdl_request.ticket= NULL; bzero((char*) &create_info, sizeof(create_info)); create_info.row_type=ROW_TYPE_NOT_USED; @@ -7836,7 +7790,7 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, strxmov(table_name, table->db ,".", table->table_name, NullS); - t= table->table= open_n_lock_single_table(thd, table, TL_READ); + t= table->table= open_n_lock_single_table(thd, table, TL_READ, 0); thd->clear_error(); // these errors shouldn't get client protocol->prepare_for_resend(); diff --git a/sql/sql_tablespace.cc b/sql/sql_tablespace.cc index 4db3083eb04..ef81c7d847e 100644 --- a/sql/sql_tablespace.cc +++ b/sql/sql_tablespace.cc @@ -66,6 +66,6 @@ int mysql_alter_tablespace(THD *thd, st_alter_tablespace *ts_info) ha_resolve_storage_engine_name(hton), "TABLESPACE or LOGFILE GROUP"); } - error= write_bin_log(thd, FALSE, thd->query(), thd->query_length()); - DBUG_RETURN(error); + write_bin_log(thd, FALSE, thd->query(), thd->query_length()); + DBUG_RETURN(FALSE); } diff --git a/sql/sql_test.cc b/sql/sql_test.cc index 8dc4b4ffeb6..37e6d704e73 100644 --- a/sql/sql_test.cc +++ b/sql/sql_test.cc @@ -75,7 +75,8 @@ print_where(COND *cond,const char *info, enum_query_type query_type) void print_cached_tables(void) { uint idx,count,unused; - TABLE *start_link,*lnk; + TABLE_SHARE *share; + TABLE *start_link, *lnk, *entry; compile_time_assert(TL_WRITE_ONLY+1 == array_elements(lock_descriptions)); @@ -83,16 +84,26 @@ void print_cached_tables(void) mysql_mutex_lock(&LOCK_open); puts("DB Table Version Thread Open Lock"); - for (idx=unused=0 ; idx < open_cache.records ; idx++) + for (idx=unused=0 ; idx < table_def_cache.records ; idx++) { - TABLE *entry=(TABLE*) my_hash_element(&open_cache,idx); - printf("%-14.14s %-32s%6ld%8ld%6d %s\n", - entry->s->db.str, entry->s->table_name.str, entry->s->version, - entry->in_use ? entry->in_use->thread_id : 0L, - entry->db_stat ? 1 : 0, - entry->in_use ? lock_descriptions[(int)entry->reginfo.lock_type] : "Not in use"); - if (!entry->in_use) + share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx); + + I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables); + while ((entry= it++)) + { + printf("%-14.14s %-32s%6ld%8ld%6d %s\n", + entry->s->db.str, entry->s->table_name.str, entry->s->version, + entry->in_use->thread_id, entry->db_stat ? 1 : 0, + lock_descriptions[(int)entry->reginfo.lock_type]); + } + it.init(share->free_tables); + while ((entry= it++)) + { unused++; + printf("%-14.14s %-32s%6ld%8ld%6d %s\n", + entry->s->db.str, entry->s->table_name.str, entry->s->version, + 0L, entry->db_stat ? 1 : 0, "Not in use"); + } } count=0; if ((start_link=lnk=unused_tables)) @@ -104,17 +115,18 @@ void print_cached_tables(void) printf("unused_links isn't linked properly\n"); return; } - } while (count++ < open_cache.records && (lnk=lnk->next) != start_link); + } while (count++ < table_cache_count && (lnk=lnk->next) != start_link); if (lnk != start_link) { printf("Unused_links aren't connected\n"); } } if (count != unused) - printf("Unused_links (%d) doesn't match open_cache: %d\n", count,unused); + printf("Unused_links (%d) doesn't match table_def_cache: %d\n", count, + unused); printf("\nCurrent refresh version: %ld\n",refresh_version); - if (my_hash_check(&open_cache)) - printf("Error: File hash table is corrupted\n"); + if (my_hash_check(&table_def_cache)) + printf("Error: Table definition hash table is corrupted\n"); fflush(stdout); mysql_mutex_unlock(&LOCK_open); /* purecov: end */ @@ -380,7 +392,7 @@ static void display_table_locks(void) LIST *list; DYNAMIC_ARRAY saved_table_locks; - (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO),open_cache.records + 20,50); + (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO), table_cache_count + 20,50); mysql_mutex_lock(&THR_LOCK_lock); for (list= thr_lock_thread_list; list; list= list_rest(list)) { diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 6f0602cedfd..71216d74e6e 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -19,6 +19,7 @@ #include "sp_head.h" #include "sql_trigger.h" #include "parse_file.h" +#include "sp.h" /*************************************************************************/ @@ -327,7 +328,8 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) TABLE *table; bool result= TRUE; String stmt_query; - bool need_start_waiting= FALSE; + bool lock_upgrade_done= FALSE; + MDL_ticket *mdl_ticket= NULL; DBUG_ENTER("mysql_create_or_drop_trigger"); @@ -383,12 +385,10 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) LOCK_open is not enough because global read lock is held without holding LOCK_open). */ - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + if (!thd->locked_tables_mode && + thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) DBUG_RETURN(TRUE); - mysql_mutex_lock(&LOCK_open); - if (!create) { bool if_exists= thd->lex->drop_if_exists; @@ -442,31 +442,43 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) /* We also don't allow creation of triggers on views. */ tables->required_type= FRMTYPE_TABLE; + /* + Also prevent DROP TRIGGER from opening temporary table which might + shadow base table on which trigger to be dropped is defined. + */ + tables->open_type= OT_BASE_ONLY; /* Keep consistent with respect to other DDL statements */ - mysql_ha_rm_tables(thd, tables, TRUE); + mysql_ha_rm_tables(thd, tables); - if (thd->locked_tables) + if (thd->locked_tables_mode) { - /* Table must be write locked */ - if (name_lock_locked_table(thd, tables)) + /* Under LOCK TABLES we must only accept write locked tables. */ + if (!(tables->table= find_table_for_mdl_upgrade(thd->open_tables, + tables->db, + tables->table_name, + FALSE))) goto end; } else { - /* Grab the name lock and insert the placeholder*/ - if (lock_table_names(thd, tables)) - goto end; - - /* Convert the placeholder to a real table */ - if (reopen_name_locked_table(thd, tables, TRUE)) - { - unlock_table_name(thd, tables); + tables->table= open_n_lock_single_table(thd, tables, + TL_WRITE_ALLOW_READ, + MYSQL_OPEN_TAKE_UPGRADABLE_MDL); + if (! tables->table) goto end; - } + tables->table->use_all_columns(); } table= tables->table; + /* Later on we will need it to downgrade the lock */ + mdl_ticket= table->mdl_ticket; + + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + goto end; + + lock_upgrade_done= TRUE; + if (!table->triggers) { if (!create) @@ -479,41 +491,39 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) goto end; } + mysql_mutex_lock(&LOCK_open); result= (create ? table->triggers->create_trigger(thd, tables, &stmt_query): table->triggers->drop_trigger(thd, tables, &stmt_query)); + mysql_mutex_unlock(&LOCK_open); - /* Under LOCK TABLES we must reopen the table to activate the trigger. */ - if (!result && thd->locked_tables) - { - /* Make table suitable for reopening */ - close_data_files_and_morph_locks(thd, tables->db, tables->table_name); - thd->in_lock_tables= 1; - if (reopen_tables(thd, 1, 1)) - { - /* To be safe remove this table from the set of LOCKED TABLES */ - unlink_open_table(thd, tables->table, FALSE); + if (result) + goto end; - /* - Ignore reopen_tables errors for now. It's better not leave master/slave - in a inconsistent state. - */ - thd->clear_error(); - } - thd->in_lock_tables= 0; - } + close_all_tables_for_name(thd, table->s, FALSE); + /* + Reopen the table if we were under LOCK TABLES. + Ignore the return value for now. It's better to + keep master/slave in consistent state. + */ + thd->locked_tables_list.reopen_tables(thd); end: - if (!result) { - result= write_bin_log(thd, TRUE, stmt_query.ptr(), stmt_query.length()); + write_bin_log(thd, TRUE, stmt_query.ptr(), stmt_query.length()); } - mysql_mutex_unlock(&LOCK_open); + /* + 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 (thd->locked_tables_mode && tables && lock_upgrade_done) + mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE); - if (need_start_waiting) - start_waiting_global_read_lock(thd); + if (thd->global_read_lock.has_protection()) + thd->global_read_lock.start_waiting_global_read_lock(thd); if (!result) my_ok(thd); @@ -1853,7 +1863,7 @@ Table_triggers_list::change_table_name_in_trignames(const char *old_db_name, i.e. it either will complete successfully, or will fail leaving files in their initial state. Also this method assumes that subject table is not renamed to itself. - This method needs to be called under an exclusive table name lock. + This method needs to be called under an exclusive table metadata lock. @retval FALSE Success @retval TRUE Error @@ -1875,15 +1885,12 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db, /* This method interfaces the mysql server code protected by - either LOCK_open mutex or with an exclusive table name lock. - In the future, only an exclusive table name lock will be enough. + either LOCK_open mutex or with an exclusive metadata lock. + In the future, only an exclusive metadata lock will be enough. */ #ifndef DBUG_OFF - uchar key[MAX_DBKEY_LENGTH]; - uint key_length= (uint) (strmov(strmov((char*)&key[0], db)+1, - old_table)-(char*)&key[0])+1; - - if (!is_table_name_exclusively_locked_by_this_thread(thd, key, key_length)) + if (thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, old_table, + MDL_EXCLUSIVE)) mysql_mutex_assert_owner(&LOCK_open); #endif @@ -2019,6 +2026,61 @@ bool Table_triggers_list::process_triggers(THD *thd, /** + Add triggers for table to the set of routines used by statement. + Add tables used by them to statement table list. Do the same for + routines used by triggers. + + @param thd Thread context. + @param prelocking_ctx Prelocking context of the statement. + @param table_list Table list element for table with trigger. + + @retval FALSE Success. + @retval TRUE Failure. +*/ + +bool +Table_triggers_list:: +add_tables_and_routines_for_triggers(THD *thd, + Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list) +{ + DBUG_ASSERT(static_cast<int>(table_list->lock_type) >= + static_cast<int>(TL_WRITE_ALLOW_WRITE)); + + for (int i= 0; i < (int)TRG_EVENT_MAX; i++) + { + if (table_list->trg_event_map & + static_cast<uint8>(1 << static_cast<int>(i))) + { + for (int j= 0; j < (int)TRG_ACTION_MAX; j++) + { + /* We can have only one trigger per action type currently */ + sp_head *trigger= table_list->table->triggers->bodies[i][j]; + + if (trigger) + { + MDL_key key(MDL_key::TRIGGER, trigger->m_db.str, trigger->m_name.str); + + if (sp_add_used_routine(prelocking_ctx, thd->stmt_arena, + &key, table_list->belong_to_view)) + { + trigger->add_used_tables_to_table_list(thd, + &prelocking_ctx->query_tables_last, + table_list->belong_to_view); + sp_update_stmt_used_routines(thd, prelocking_ctx, + &trigger->m_sroutines, + table_list->belong_to_view); + trigger->propagate_attributes(prelocking_ctx); + } + } + } + } + } + return FALSE; +} + + +/** Mark fields of subject table which we read/set in its triggers as such. diff --git a/sql/sql_trigger.h b/sql/sql_trigger.h index b411acf2ac5..85b2dbe5f21 100644 --- a/sql/sql_trigger.h +++ b/sql/sql_trigger.h @@ -144,8 +144,10 @@ public: void mark_fields_used(trg_event_type event); friend class Item_trigger_field; - friend int sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex, - TABLE_LIST *table); + + bool add_tables_and_routines_for_triggers(THD *thd, + Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list); private: bool prepare_record1_accessors(TABLE *table); diff --git a/sql/sql_udf.cc b/sql/sql_udf.cc index 94e9fde6a33..443d11b693e 100644 --- a/sql/sql_udf.cc +++ b/sql/sql_udf.cc @@ -161,10 +161,7 @@ void udf_init() new_thd->store_globals(); new_thd->set_db(db, sizeof(db)-1); - bzero((uchar*) &tables,sizeof(tables)); - tables.alias= tables.table_name= (char*) "func"; - tables.lock_type = TL_READ; - tables.db= db; + tables.init_one_table(db, sizeof(db)-1, "func", 4, "func", TL_READ); if (simple_open_n_lock_tables(new_thd, &tables)) { @@ -505,9 +502,7 @@ int mysql_create_function(THD *thd,udf_func *udf) /* create entry in mysql.func table */ - bzero((char*) &tables,sizeof(tables)); - tables.db= (char*) "mysql"; - tables.table_name= tables.alias= (char*) "func"; + tables.init_one_table("mysql", 5, "func", 4, "func", TL_WRITE); /* Allow creation of functions even if we can't open func table */ if (!(table = open_ltable(thd, &tables, TL_WRITE, 0))) goto err; @@ -529,8 +524,8 @@ int mysql_create_function(THD *thd,udf_func *udf) mysql_rwlock_unlock(&THR_LOCK_udf); /* Binlog the create function. */ - if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) - DBUG_RETURN(1); + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + DBUG_RETURN(0); err: @@ -583,9 +578,8 @@ int mysql_drop_function(THD *thd,const LEX_STRING *udf_name) if (udf->dlhandle && !find_udf_dl(udf->dl)) dlclose(udf->dlhandle); - bzero((char*) &tables,sizeof(tables)); - tables.db=(char*) "mysql"; - tables.table_name= tables.alias= (char*) "func"; + tables.init_one_table("mysql", 5, "func", 4, "func", TL_WRITE); + if (!(table = open_ltable(thd, &tables, TL_WRITE, 0))) goto err; table->use_all_columns(); @@ -599,15 +593,16 @@ int mysql_drop_function(THD *thd,const LEX_STRING *udf_name) if ((error = table->file->ha_delete_row(table->record[0]))) table->file->print_error(error, MYF(0)); } - close_thread_tables(thd); - mysql_rwlock_unlock(&THR_LOCK_udf); - /* Binlog the drop function. */ - if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) - DBUG_RETURN(1); + /* + Binlog the drop function. Keep the table open and locked + while binlogging, to avoid binlog inconsistency. + */ + write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + DBUG_RETURN(0); - err: +err: mysql_rwlock_unlock(&THR_LOCK_udf); DBUG_RETURN(1); } diff --git a/sql/sql_update.cc b/sql/sql_update.cc index dec83f199ab..600c4a2f2dc 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -206,6 +206,7 @@ int mysql_update(THD *thd, ulonglong id; List<Item> all_fields; THD::killed_state killed_status= THD::NOT_KILLED; + MDL_ticket *start_of_statement_svp= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("mysql_update"); for ( ; ; ) @@ -222,11 +223,11 @@ int mysql_update(THD *thd, /* convert to multiupdate */ DBUG_RETURN(2); } - if (!lock_tables(thd, table_list, table_count, &need_reopen)) + if (!lock_tables(thd, table_list, table_count, 0, &need_reopen)) break; if (!need_reopen) DBUG_RETURN(1); - close_tables_for_reopen(thd, &table_list); + close_tables_for_reopen(thd, &table_list, start_of_statement_svp); } if (mysql_handle_derived(thd->lex, &mysql_derived_prepare) || @@ -978,9 +979,10 @@ int mysql_multi_update_prepare(THD *thd) count in open_tables() */ uint table_count= lex->table_count; - const bool using_lock_tables= thd->locked_tables != 0; + const bool using_lock_tables= thd->locked_tables_mode != LTM_NONE; bool original_multiupdate= (thd->lex->sql_command == SQLCOM_UPDATE_MULTI); bool need_reopen= FALSE; + MDL_ticket *start_of_statement_svp= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("mysql_multi_update_prepare"); /* following need for prepared statements, to run next time multi-update */ @@ -1100,7 +1102,7 @@ reopen_tables: /* now lock and fill tables */ if (!thd->stmt_arena->is_stmt_prepare() && - lock_tables(thd, table_list, table_count, &need_reopen)) + lock_tables(thd, table_list, table_count, 0, &need_reopen)) { if (!need_reopen) DBUG_RETURN(TRUE); @@ -1118,10 +1120,6 @@ reopen_tables: while ((item= it++)) item->cleanup(); - /* We have to cleanup translation tables of views. */ - for (TABLE_LIST *tbl= table_list; tbl; tbl= tbl->next_global) - tbl->cleanup_items(); - /* To not to hog memory (as a result of the unit->reinit_exec_mechanism() call below): @@ -1150,7 +1148,7 @@ reopen_tables: */ cleanup_items(thd->free_list); - close_tables_for_reopen(thd, &table_list); + close_tables_for_reopen(thd, &table_list, start_of_statement_svp); goto reopen_tables; } @@ -1876,10 +1874,9 @@ void multi_update::abort() into repl event. */ int errcode= query_error_code(thd, thd->killed == THD::NOT_KILLED); - /* the error of binary logging is ignored */ - (void)thd->binlog_query(THD::ROW_QUERY_TYPE, - thd->query(), thd->query_length(), - transactional_tables, FALSE, errcode); + thd->binlog_query(THD::ROW_QUERY_TYPE, + thd->query(), thd->query_length(), + transactional_tables, FALSE, errcode); } thd->transaction.all.modified_non_trans_table= TRUE; } diff --git a/sql/sql_view.cc b/sql/sql_view.cc index e088eb1ff59..84418d6b629 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -169,40 +169,17 @@ err: static bool fill_defined_view_parts (THD *thd, TABLE_LIST *view) { + char key[MAX_DBKEY_LENGTH]; + uint key_length; LEX *lex= thd->lex; - bool not_used; TABLE_LIST decoy; memcpy (&decoy, view, sizeof (TABLE_LIST)); + key_length= create_table_def_key(thd, key, view, 0); - /* - Let's reset decoy.view before calling open_table(): when we start - supporting ALTER VIEW in PS/SP that may save us from a crash. - */ - - decoy.view= NULL; - - /* - open_table() will return NULL if 'decoy' is idenitifying a view *and* - there is no TABLE object for that view in the table cache. However, - decoy.view will be set to 1. - - If there is a TABLE-instance for the oject identified by 'decoy', - open_table() will return that instance no matter if it is a table or - a view. - - Thus, there is no need to check for the return value of open_table(), - since the return value itself does not mean anything. - */ - - open_table(thd, &decoy, thd->mem_root, ¬_used, OPEN_VIEW_NO_PARSE); - - if (!decoy.view) - { - /* It's a table. */ - my_error(ER_WRONG_OBJECT, MYF(0), view->db, view->table_name, "VIEW"); + if (tdc_open_view(thd, &decoy, decoy.alias, key, key_length, + thd->mem_root, OPEN_VIEW_NO_PARSE)) return TRUE; - } if (!lex->definer) { @@ -397,6 +374,37 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, DBUG_ASSERT(!lex->proc_list.first && !lex->result && !lex->param_list.elements); + /* + We can't allow taking exclusive meta-data locks of unlocked view under + LOCK TABLES since this might lead to deadlock. Since at the moment we + can't really lock view with LOCK TABLES we simply prohibit creation/ + alteration of views under LOCK TABLES. + */ + + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + res= TRUE; + goto err; + } + + if ((res= create_view_precheck(thd, tables, view, mode))) + goto err; + + lex->link_first_table_back(view, link_to_local); + view->open_strategy= TABLE_LIST::OPEN_STUB; + view->lock_strategy= TABLE_LIST::EXCLUSIVE_MDL; + view->open_type= OT_BASE_ONLY; + + if (open_and_lock_tables(thd, lex->query_tables)) + { + view= lex->unlink_first_table(&link_to_local); + res= TRUE; + goto err; + } + + view= lex->unlink_first_table(&link_to_local); + if (mode != VIEW_CREATE_NEW) { if (mode == VIEW_ALTER && @@ -461,16 +469,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, } } #endif - - if ((res= create_view_precheck(thd, tables, view, mode))) - goto err; - - if (open_and_lock_tables(thd, tables)) - { - res= TRUE; - goto err; - } - /* check that tables are not temporary and this VIEW do not used in query (it is possible with ALTERing VIEW). @@ -612,11 +610,13 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, } #endif - if (wait_if_global_read_lock(thd, 0, 0)) + + if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) { res= TRUE; goto err; } + mysql_mutex_lock(&LOCK_open); res= mysql_register_view(thd, view, mode); @@ -659,15 +659,14 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, buff.append(views->source.str, views->source.length); int errcode= query_error_code(thd, TRUE); - if (thd->binlog_query(THD::STMT_QUERY_TYPE, - buff.ptr(), buff.length(), FALSE, FALSE, errcode)) - res= TRUE; + thd->binlog_query(THD::STMT_QUERY_TYPE, + buff.ptr(), buff.length(), FALSE, FALSE, errcode); } mysql_mutex_unlock(&LOCK_open); if (mode != VIEW_CREATE_NEW) query_cache_invalidate3(thd, view, 0); - start_waiting_global_read_lock(thd); + thd->global_read_lock.start_waiting_global_read_lock(thd); if (res) goto err; @@ -1136,6 +1135,20 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, table->view_db.length= table->db_length; table->view_name.str= table->table_name; table->view_name.length= table->table_name_length; + /* + We don't invalidate a prepared statement when a view changes, + or when someone creates a temporary table. + Instead, the view is inlined into the body of the statement + upon the first execution. Below, make sure that on + re-execution of a prepared statement we don't prefer + a temporary table to the view, if the view name was shadowed + with a temporary table with the same name. + This assignment ensures that on re-execution open_table() will + not try to call find_temporary_table() for this TABLE_LIST, + but will invoke open_table_from_share(), which will + eventually call this function. + */ + table->open_type= OT_BASE_ONLY; /*TODO: md5 test here and warning if it is differ */ @@ -1259,7 +1272,7 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, tbl; tbl= (view_tables_tail= tbl)->next_global) { - tbl->skip_temporary= 1; + tbl->open_type= OT_BASE_ONLY; tbl->belong_to_view= top_view; tbl->referencing_view= table; tbl->prelocking_placeholder= table->prelocking_placeholder; @@ -1578,6 +1591,21 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) bool something_wrong= FALSE; DBUG_ENTER("mysql_drop_view"); + /* + We can't allow dropping of unlocked view under LOCK TABLES since this + might lead to deadlock. But since we can't really lock view with LOCK + TABLES we have to simply prohibit dropping of views. + */ + + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + DBUG_RETURN(TRUE); + } + + if (lock_table_names(thd, views)) + DBUG_RETURN(TRUE); + mysql_mutex_lock(&LOCK_open); for (view= views; view; view= view->next_local) { @@ -1626,11 +1654,9 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) if ((share= get_cached_table_share(view->db, view->table_name))) { DBUG_ASSERT(share->ref_count == 0); - mysql_mutex_lock(&share->mutex); share->ref_count++; share->version= 0; - mysql_mutex_unlock(&share->mutex); - release_table_share(share, RELEASE_WAIT_FOR_DROP); + release_table_share(share); } query_cache_invalidate3(thd, view, 0); sp_cache_invalidate(); @@ -1652,8 +1678,7 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) /* if something goes wrong, bin-log with possible error code, otherwise bin-log with error code cleared. */ - if (write_bin_log(thd, !something_wrong, thd->query(), thd->query_length())) - something_wrong= 1; + write_bin_log(thd, !something_wrong, thd->query(), thd->query_length()); } mysql_mutex_unlock(&LOCK_open); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 96cf9167014..2773a4ff0ed 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -688,7 +688,8 @@ static bool add_create_index_prepare (LEX *lex, Table_ident *table) { lex->sql_command= SQLCOM_CREATE_INDEX; if (!lex->current_select->add_table_to_list(lex->thd, table, NULL, - TL_OPTION_UPDATING)) + TL_OPTION_UPDATING, + TL_WRITE_ALLOW_READ)) return TRUE; lex->alter_info.reset(); lex->alter_info.flags= ALTER_ADD_INDEX; @@ -1168,7 +1169,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token PREV_SYM %token PRIMARY_SYM /* SQL-2003-R */ %token PRIVILEGES /* SQL-2003-N */ -%token PROCEDURE /* SQL-2003-R */ +%token PROCEDURE_SYM /* SQL-2003-R */ %token PROCESS %token PROCESSLIST_SYM %token PROFILE_SYM @@ -2019,6 +2020,7 @@ create: lex->create_info.default_table_charset= NULL; lex->name.str= 0; lex->name.length= 0; + lex->create_last_non_select_table= lex->last_table(); } create2 { @@ -2033,6 +2035,7 @@ create: ha_resolve_storage_engine_name(lex->create_info.db_type), $5->table.str); } + create_table_set_open_action_and_adjust_tables(lex); } | CREATE opt_unique INDEX_SYM ident key_alg ON table_ident { @@ -4186,7 +4189,7 @@ size_number: create2: '(' create2a {} | opt_create_table_options - opt_partitioning + opt_create_partitioning create3 {} | LIKE table_ident { @@ -4219,10 +4222,10 @@ create2: ; create2a: - field_list ')' opt_create_table_options - opt_partitioning + create_field_list ')' opt_create_table_options + opt_create_partitioning create3 {} - | opt_partitioning + | opt_create_partitioning create_select ')' { Select->set_braces(1);} union_opt {} @@ -4238,6 +4241,19 @@ create3: union_opt {} ; +opt_create_partitioning: + opt_partitioning + { + /* + Remove all tables used in PARTITION clause from the global table + list. Partitioning with subqueries is not allowed anyway. + */ + TABLE_LIST *last_non_sel_table= Lex->create_last_non_select_table; + last_non_sel_table->next_global= 0; + Lex->query_tables_last= &last_non_sel_table->next_global; + } + ; + /* This part of the parser is about handling of the partition information. @@ -5068,19 +5084,30 @@ create_table_option: Lex->create_info.row_type= $3; Lex->create_info.used_fields|= HA_CREATE_USED_ROW_FORMAT; } - | UNION_SYM opt_equal '(' opt_table_list ')' + | UNION_SYM opt_equal + { + Lex->select_lex.table_list.save_and_clear(&Lex->save_list); + } + '(' opt_table_list ')' { - /* Move the union list to the merge_list */ + /* + Move the union list to the merge_list and exclude its tables + from the global list. + */ LEX *lex=Lex; - TABLE_LIST *table_list= lex->select_lex.get_table_list(); lex->create_info.merge_list= lex->select_lex.table_list; - lex->create_info.merge_list.elements--; - lex->create_info.merge_list.first= - (uchar*) (table_list->next_local); - lex->select_lex.table_list.elements=1; - lex->select_lex.table_list.next= - (uchar**) &(table_list->next_local); - table_list->next_local= 0; + lex->select_lex.table_list= lex->save_list; + /* + When excluding union list from the global list we assume that + elements of the former immediately follow elements which represent + table being created/altered and parent tables. + */ + TABLE_LIST *last_non_sel_table= lex->create_last_non_select_table; + DBUG_ASSERT(last_non_sel_table->next_global == + (TABLE_LIST *)lex->create_info.merge_list.first); + last_non_sel_table->next_global= 0; + Lex->query_tables_last= &last_non_sel_table->next_global; + lex->create_info.used_fields|= HA_CREATE_USED_UNION; } | default_charset @@ -5218,6 +5245,14 @@ udf_type: | INT_SYM {$$ = (int) INT_RESULT; } ; + +create_field_list: + field_list + { + Lex->create_last_non_select_table= Lex->last_table(); + } + ; + field_list: field_list_item | field_list ',' field_list_item @@ -6079,7 +6114,8 @@ alter: lex->sql_command= SQLCOM_ALTER_TABLE; lex->duplicates= DUP_ERROR; if (!lex->select_lex.add_table_to_list(thd, $4, NULL, - TL_OPTION_UPDATING)) + TL_OPTION_UPDATING, + TL_WRITE_ALLOW_READ)) MYSQL_YYABORT; lex->col_list.empty(); lex->select_lex.init_order(); @@ -6092,6 +6128,7 @@ alter: lex->alter_info.reset(); lex->no_write_to_binlog= 0; lex->create_info.storage_media= HA_SM_DEFAULT; + lex->create_last_non_select_table= lex->last_table(); } alter_commands {} @@ -6120,7 +6157,7 @@ alter: lex->sql_command= SQLCOM_ALTER_DB_UPGRADE; lex->name= $3; } - | ALTER PROCEDURE sp_name + | ALTER PROCEDURE_SYM sp_name { LEX *lex= Lex; @@ -6475,12 +6512,16 @@ add_column: ; alter_list_item: - add_column column_def opt_place { } + add_column column_def opt_place + { + Lex->create_last_non_select_table= Lex->last_table(); + } | ADD key_def { + Lex->create_last_non_select_table= Lex->last_table(); Lex->alter_info.flags|= ALTER_ADD_INDEX; } - | add_column '(' field_list ')' + | add_column '(' create_field_list ')' { Lex->alter_info.flags|= ALTER_ADD_COLUMN | ALTER_ADD_INDEX; } @@ -6491,6 +6532,9 @@ alter_list_item: lex->alter_info.flags|= ALTER_CHANGE_COLUMN; } field_spec opt_place + { + Lex->create_last_non_select_table= Lex->last_table(); + } | MODIFY_SYM opt_column field_ident { LEX *lex=Lex; @@ -6513,6 +6557,9 @@ alter_list_item: MYSQL_YYABORT; } opt_place + { + Lex->create_last_non_select_table= Lex->last_table(); + } | DROP opt_column field_ident opt_restrict { LEX *lex=Lex; @@ -9844,7 +9891,7 @@ dec_num: procedure_clause: /* empty */ - | PROCEDURE ident /* Procedure name */ + | PROCEDURE_SYM ident /* Procedure name */ { LEX *lex=Lex; @@ -10044,7 +10091,8 @@ drop: lex->alter_info.flags= ALTER_DROP_INDEX; lex->alter_info.drop_list.push_back(ad); if (!lex->current_select->add_table_to_list(lex->thd, $5, NULL, - TL_OPTION_UPDATING)) + TL_OPTION_UPDATING, + TL_WRITE_ALLOW_READ)) MYSQL_YYABORT; } | DROP DATABASE if_exists ident @@ -10098,7 +10146,7 @@ drop: spname->init_qname(thd); lex->spname= spname; } - | DROP PROCEDURE if_exists sp_name + | DROP PROCEDURE_SYM if_exists sp_name { LEX *lex=Lex; if (lex->sphead) @@ -10849,7 +10897,7 @@ show_param: { Lex->sql_command = SQLCOM_SHOW_SLAVE_STAT; } - | CREATE PROCEDURE sp_name + | CREATE PROCEDURE_SYM sp_name { LEX *lex= Lex; @@ -10869,7 +10917,7 @@ show_param: lex->sql_command= SQLCOM_SHOW_CREATE_TRIGGER; lex->spname= $3; } - | PROCEDURE STATUS_SYM wild_and_where + | PROCEDURE_SYM STATUS_SYM wild_and_where { LEX *lex= Lex; lex->sql_command= SQLCOM_SHOW_STATUS_PROC; @@ -10883,7 +10931,7 @@ show_param: if (prepare_schema_table(YYTHD, lex, 0, SCH_PROCEDURES)) MYSQL_YYABORT; } - | PROCEDURE CODE_SYM sp_name + | PROCEDURE_SYM CODE_SYM sp_name { Lex->sql_command= SQLCOM_SHOW_PROC_CODE; Lex->spname= $3; @@ -13019,7 +13067,7 @@ revoke_command: lex->sql_command= SQLCOM_REVOKE; lex->type= TYPE_ENUM_FUNCTION; } - | grant_privileges ON PROCEDURE grant_ident FROM grant_list + | grant_privileges ON PROCEDURE_SYM grant_ident FROM grant_list { LEX *lex= Lex; if (lex->columns.elements) @@ -13061,7 +13109,7 @@ grant_command: lex->sql_command= SQLCOM_GRANT; lex->type= TYPE_ENUM_FUNCTION; } - | grant_privileges ON PROCEDURE grant_ident TO_SYM grant_list + | grant_privileges ON PROCEDURE_SYM grant_ident TO_SYM grant_list require_clause grant_options { LEX *lex= Lex; @@ -14098,7 +14146,7 @@ sf_tail: ; sp_tail: - PROCEDURE remember_name sp_name + PROCEDURE_SYM remember_name sp_name { LEX *lex= Lex; sp_head *sp; diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 1bac48d053f..b37468e8292 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -33,6 +33,7 @@ #include <thr_alarm.h> #include "slave.h" #include "rpl_mi.h" +#include "transaction.h" #ifdef WITH_PERFSCHEMA_STORAGE_ENGINE #include "../storage/perfschema/pfs_server.h" @@ -1310,7 +1311,7 @@ static my_bool read_only; static bool check_read_only(sys_var *self, THD *thd, set_var *var) { /* Prevent self dead-lock */ - if (thd->locked_tables || thd->active_transaction()) + if (thd->locked_tables_mode || thd->active_transaction()) { my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); return true; @@ -1332,7 +1333,7 @@ static bool fix_read_only(sys_var *self, THD *thd, enum_var_type type) if (check_read_only(self, thd, 0)) // just in case goto end; - if (thd->global_read_lock) + if (thd->global_read_lock.is_acquired()) { /* This connection already holds the global read lock. @@ -1358,7 +1359,7 @@ static bool fix_read_only(sys_var *self, THD *thd, enum_var_type type) read_only= opt_readonly; mysql_mutex_unlock(&LOCK_global_system_variables); - if (lock_global_read_lock(thd)) + if (thd->global_read_lock.lock_global_read_lock(thd)) goto end_with_mutex_unlock; /* @@ -1370,10 +1371,10 @@ static bool fix_read_only(sys_var *self, THD *thd, enum_var_type type) can cause to wait on a read lock, it's required for the client application to unlock everything, and acceptable for the server to wait on all locks. */ - if ((result= close_cached_tables(thd, NULL, FALSE, TRUE, TRUE))) + if ((result= close_cached_tables(thd, NULL, FALSE, TRUE))) goto end_with_read_lock; - if ((result= make_global_read_lock_block_commit(thd))) + if ((result= thd->global_read_lock.make_global_read_lock_block_commit(thd))) goto end_with_read_lock; /* Change the opt_readonly system variable, safe because the lock is held */ @@ -1382,7 +1383,7 @@ static bool fix_read_only(sys_var *self, THD *thd, enum_var_type type) end_with_read_lock: /* Release the lock */ - unlock_global_read_lock(thd); + thd->global_read_lock.unlock_global_read_lock(thd); end_with_mutex_unlock: mysql_mutex_lock(&LOCK_global_system_variables); end: @@ -2039,11 +2040,13 @@ static bool fix_autocommit(sys_var *self, THD *thd, enum_var_type type) thd->variables.option_bits & OPTION_NOT_AUTOCOMMIT) { // activating autocommit - if (ha_commit(thd)) + if (trans_commit(thd)) { thd->variables.option_bits&= ~OPTION_AUTOCOMMIT; return true; } + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG | OPTION_NOT_AUTOCOMMIT); diff --git a/sql/table.cc b/sql/table.cc index c0c365f4901..db7137f41c2 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -317,9 +317,12 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key, share->table_map_id= ~0UL; share->cached_row_logging_check= -1; + share->used_tables.empty(); + share->free_tables.empty(); + memcpy((char*) &share->mem_root, (char*) &mem_root, sizeof(mem_root)); - mysql_mutex_init(key_TABLE_SHARE_mutex, &share->mutex, MY_MUTEX_INIT_FAST); - mysql_cond_init(key_TABLE_SHARE_cond, &share->cond, NULL); + mysql_mutex_init(key_TABLE_SHARE_LOCK_ha_data, + &share->LOCK_ha_data, MY_MUTEX_INIT_FAST); } DBUG_RETURN(share); } @@ -383,6 +386,9 @@ void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key, */ share->table_map_id= (ulong) thd->query_id; + share->used_tables.empty(); + share->free_tables.empty(); + DBUG_VOID_RETURN; } @@ -407,25 +413,11 @@ void free_table_share(TABLE_SHARE *share) DBUG_PRINT("enter", ("table: %s.%s", share->db.str, share->table_name.str)); DBUG_ASSERT(share->ref_count == 0); - /* - If someone is waiting for this to be deleted, inform it about this. - Don't do a delete until we know that no one is refering to this anymore. - */ + /* The mutex is initialized only for shares that are part of the TDC */ if (share->tmp_table == NO_TMP_TABLE) - { - /* share->mutex is locked in release_table_share() */ - while (share->waiting_on_cond) - { - mysql_cond_broadcast(&share->cond); - mysql_cond_wait(&share->cond, &share->mutex); - } - /* No thread refers to this anymore */ - mysql_mutex_unlock(&share->mutex); - mysql_mutex_destroy(&share->mutex); - mysql_cond_destroy(&share->cond); - } + mysql_mutex_destroy(&share->LOCK_ha_data); my_hash_free(&share->name_hash); - + plugin_unlock(NULL, share->db_plugin); share->db_plugin= NULL; @@ -1998,7 +1990,7 @@ int closefrm(register TABLE *table, bool free_share) if (free_share) { if (table->s->tmp_table == NO_TMP_TABLE) - release_table_share(table->s, RELEASE_NORMAL); + release_table_share(table->s); else free_table_share(table->s); } @@ -4578,24 +4570,6 @@ void TABLE::mark_columns_needed_for_insert() } -/** - @brief Check if this is part of a MERGE table with attached children. - - @return status - @retval TRUE children are attached - @retval FALSE no MERGE part or children not attached - - @detail - A MERGE table consists of a parent TABLE and zero or more child - TABLEs. Each of these TABLEs is called a part of a MERGE table. -*/ - -bool TABLE::is_children_attached(void) -{ - return((child_l && children_attached) || - (parent && parent->children_attached)); -} - /* Cleanup this table for re-execution. @@ -4624,6 +4598,15 @@ void TABLE_LIST::reinit_before_use(THD *thd) } while (parent_embedding && parent_embedding->nested_join->join_list.head() == embedded); + + mdl_request.ticket= NULL; + /* + Since we manipulate with the metadata lock type in open_table(), + we need to reset it to the parser default, to restore things back + to first-execution state. + */ + mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? + MDL_SHARED_WRITE : MDL_SHARED_READ); } /* @@ -4846,6 +4829,22 @@ size_t max_row_length(TABLE *table, const uchar *data) return length; } + +/** + Helper function which allows to allocate metadata lock request + objects for all elements of table list. +*/ + +void init_mdl_requests(TABLE_LIST *table_list) +{ + for ( ; table_list ; table_list= table_list->next_global) + table_list->mdl_request.init(MDL_key::TABLE, + table_list->db, table_list->table_name, + table_list->lock_type >= TL_WRITE_ALLOW_WRITE ? + MDL_SHARED_WRITE : MDL_SHARED_READ); +} + + /***************************************************************************** ** Instansiate templates *****************************************************************************/ diff --git a/sql/table.h b/sql/table.h index b2b84ed283d..3d11118a3ea 100644 --- a/sql/table.h +++ b/sql/table.h @@ -16,6 +16,9 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "sql_plist.h" +#include "mdl.h" + /* Structs that defines the TABLE */ class Item; /* Needed by ORDER */ @@ -333,6 +336,10 @@ TABLE_CATEGORY get_table_category(const LEX_STRING *db, const LEX_STRING *name); +struct TABLE_share; + +extern ulong refresh_version; + typedef struct st_table_field_type { LEX_STRING name; @@ -380,10 +387,16 @@ struct TABLE_SHARE TYPELIB keynames; /* Pointers to keynames */ TYPELIB fieldnames; /* Pointer to fieldnames */ TYPELIB *intervals; /* pointer to interval info */ - mysql_mutex_t mutex; /* For locking the share */ - mysql_cond_t cond; /* To signal that share is ready */ + mysql_mutex_t LOCK_ha_data; /* To protect access to ha_data */ TABLE_SHARE *next, **prev; /* Link to unused shares */ + /* + Doubly-linked (back-linked) lists of used and unused TABLE objects + for this share. + */ + I_P_List <TABLE, TABLE_share> used_tables; + I_P_List <TABLE, TABLE_share> free_tables; + /* The following is copied to each TABLE on OPEN */ Field **field; Field **found_next_number_field; @@ -470,8 +483,6 @@ struct TABLE_SHARE bool db_low_byte_first; /* Portable row format */ bool crashed; bool is_view; - bool name_lock, replace_with_name_lock; - bool waiting_on_cond; /* Protection against free */ ulong table_map_id; /* for row-based replication */ ulonglong table_map_version; @@ -581,6 +592,14 @@ struct TABLE_SHARE return table_map_id; } + + /* + Must all TABLEs be reopened? + */ + inline bool needs_reopen() + { + return version != refresh_version; + } /** Convert unrelated members of TABLE_SHARE to one enum representing its type. @@ -683,8 +702,6 @@ struct TABLE_SHARE }; -extern ulong refresh_version; - /* Information for one open table */ enum index_hint_type { @@ -701,10 +718,17 @@ struct TABLE handler *file; TABLE *next, *prev; - /* For the below MERGE related members see top comment in ha_myisammrg.cc */ - TABLE *parent; /* Set in MERGE child. Ptr to parent */ - TABLE_LIST *child_l; /* Set in MERGE parent. List of children */ - TABLE_LIST **child_last_l; /* Set in MERGE parent. End of list */ +private: + /** + Links for the lists of used/unused TABLE objects for this share. + Declared as private to avoid direct manipulation with those objects. + One should use methods of I_P_List template instead. + */ + TABLE *share_next, **share_prev; + + friend struct TABLE_share; + +public: THD *in_use; /* Which thread uses this */ Field **field; /* Pointer to fields */ @@ -744,6 +768,8 @@ struct TABLE /* Table's triggers, 0 if there are no of them */ Table_triggers_list *triggers; TABLE_LIST *pos_in_table_list;/* Element referring to this table */ + /* Position in thd->locked_table_list under LOCK TABLES */ + TABLE_LIST *pos_in_locked_tables; ORDER *group; const char *alias; /* alias or table name */ uchar *null_flags; @@ -857,24 +883,6 @@ struct TABLE */ my_bool key_read; my_bool no_keyread; - /* - Placeholder for an open table which prevents other connections - from taking name-locks on this table. Typically used with - TABLE_SHARE::version member to take an exclusive name-lock on - this table name -- a name lock that not only prevents other - threads from opening the table, but also blocks other name - locks. This is achieved by: - - setting open_placeholder to 1 - this will block other name - locks, as wait_for_locked_table_name will be forced to wait, - see table_is_used for details. - - setting version to 0 - this will force other threads to close - the instance of this table and wait (this is the same approach - as used for usual name locks). - An exclusively name-locked table currently can have no handler - object associated with it (db_stat is always 0), but please do - not rely on that. - */ - my_bool open_placeholder; my_bool locked_by_logger; my_bool no_replicate; my_bool locked_by_name; @@ -891,8 +899,7 @@ struct TABLE my_bool insert_or_update; /* Can be used by the handler */ my_bool alias_name_used; /* true if table_name is alias */ my_bool get_fields_in_item_tree; /* Signal to fix_field */ - /* If MERGE children attached to parent. See top comment in ha_myisammrg.cc */ - my_bool children_attached; + my_bool m_needs_reopen; REGINFO reginfo; /* field connections */ MEM_ROOT mem_root; @@ -902,6 +909,7 @@ struct TABLE partition_info *part_info; /* Partition related information */ bool no_partitions_used; /* If true, all partitions have been pruned away */ #endif + MDL_ticket *mdl_ticket; bool fill_item_list(List<Item> *item_list) const; void reset_item_list(List<Item> *item_list) const; @@ -937,16 +945,32 @@ struct TABLE read_set= &def_read_set; write_set= &def_write_set; } - /* Is table open or should be treated as such by name-locking? */ - inline bool is_name_opened() { return db_stat || open_placeholder; } /* - Is this instance of the table should be reopen or represents a name-lock? + Is this instance of the table should be reopen? */ - inline bool needs_reopen_or_name_lock() - { return s->version != refresh_version; } - bool is_children_attached(void); + inline bool needs_reopen() + { return !db_stat || m_needs_reopen; } }; + +/** + Helper class which specifies which members of TABLE are used for + participation in the list of used/unused TABLE objects for the share. +*/ + +struct TABLE_share +{ + static inline TABLE **next_ptr(TABLE *l) + { + return &l->share_next; + } + static inline TABLE ***prev_ptr(TABLE *l) + { + return &l->share_prev; + } +}; + + enum enum_schema_table_state { NOT_PROCESSED= 0, @@ -1100,6 +1124,16 @@ public: }; +/** + Type of table which can be open for an element of table list. +*/ + +enum enum_open_type +{ + OT_TEMPORARY_OR_BASE= 0, OT_TEMPORARY_ONLY, OT_BASE_ONLY +}; + + /* Table reference in the FROM clause. @@ -1143,13 +1177,22 @@ struct TABLE_LIST simple_open_and_lock_tables */ inline void init_one_table(const char *db_name_arg, + size_t db_length_arg, const char *table_name_arg, + size_t table_name_length_arg, + const char *alias_arg, enum thr_lock_type lock_type_arg) { bzero((char*) this, sizeof(*this)); db= (char*) db_name_arg; - table_name= alias= (char*) table_name_arg; + db_length= db_length_arg; + table_name= (char*) table_name_arg; + table_name_length= table_name_length_arg; + alias= (char*) alias_arg; lock_type= lock_type_arg; + mdl_request.init(MDL_key::TABLE, db, table_name, + (lock_type >= TL_WRITE_ALLOW_WRITE) ? + MDL_SHARED_WRITE : MDL_SHARED_READ); } /* @@ -1354,7 +1397,11 @@ struct TABLE_LIST bool cacheable_table; /* stop PS caching */ /* used in multi-upd/views privilege check */ bool table_in_first_from_clause; - bool skip_temporary; /* this table shouldn't be temporary */ + /** + Specifies which kind of table should be open for this element + of table list. + */ + enum enum_open_type open_type; /* TRUE if this merged view contain auto_increment field */ bool contain_auto_increment; bool multitable_view; /* TRUE iff this is multitable view */ @@ -1372,12 +1419,38 @@ struct TABLE_LIST used for implicit LOCK TABLES only and won't be used in real statement. */ bool prelocking_placeholder; - /* - This TABLE_LIST object corresponds to the table to be created - so it is possible that it does not exist (used in CREATE TABLE - ... SELECT implementation). + /** + Indicates that if TABLE_LIST object corresponds to the table/view + which requires special handling. + */ + enum + { + /* Normal open. */ + OPEN_NORMAL= 0, + /* Associate a table share only if the the table exists. */ + OPEN_IF_EXISTS, + /* Don't associate a table share. */ + OPEN_STUB + } open_strategy; + /** + Indicates the locking strategy for the object being opened: + whether the associated metadata lock is shared or exclusive. */ - bool create; + enum + { + /* Take a shared metadata lock before the object is opened. */ + SHARED_MDL= 0, + /* + Take a exclusive metadata lock before the object is opened. + If opening is successful, downgrade to a shared lock. + */ + EXCLUSIVE_DOWNGRADABLE_MDL, + /* Take a exclusive metadata lock before the object is opened. */ + EXCLUSIVE_MDL + } lock_strategy; + /* For transactional locking. */ + int lock_timeout; /* NOWAIT or WAIT [X] */ + bool lock_transactional; /* If transactional lock requested. */ bool internal_tmp_table; /** TRUE if an alias for this table was specified in the SQL. */ bool is_alias; @@ -1426,6 +1499,9 @@ struct TABLE_LIST bool has_table_lookup_value; uint table_open_method; enum enum_schema_table_state schema_table_state; + + MDL_request mdl_request; + void calc_md5(char *buffer); void set_underlying_merge(); int view_check_option(THD *thd, bool ignore_failure); @@ -1433,8 +1509,7 @@ struct TABLE_LIST void cleanup_items(); bool placeholder() { - return derived || view || schema_table || (create && !table->db_stat) || - !table; + return derived || view || schema_table || !table; } void print(THD *thd, String *str, enum_query_type query_type); bool check_single_table(TABLE_LIST **table, table_map map, @@ -1482,20 +1557,6 @@ struct TABLE_LIST */ bool process_index_hints(TABLE *table); - /* Access MERGE child def version. See top comment in ha_myisammrg.cc */ - inline ulong get_child_def_version() - { - return child_def_version; - } - inline void set_child_def_version(ulong version) - { - child_def_version= version; - } - inline void init_child_def_version() - { - child_def_version= ~0UL; - } - /** Compare the version of metadata from the previous execution (if any) with values obtained from the current table @@ -1518,9 +1579,14 @@ struct TABLE_LIST */ inline void set_table_ref_id(TABLE_SHARE *s) + { set_table_ref_id(s->get_table_ref_type(), s->get_table_ref_version()); } + + inline + void set_table_ref_id(enum_table_ref_type table_ref_type_arg, + ulong table_ref_version_arg) { - m_table_ref_type= s->get_table_ref_type(); - m_table_ref_version= s->get_table_ref_version(); + m_table_ref_type= table_ref_type_arg; + m_table_ref_version= table_ref_version_arg; } /** @@ -1546,13 +1612,6 @@ struct TABLE_LIST private: bool prep_check_option(THD *thd, uint8 check_opt_type); bool prep_where(THD *thd, Item **conds, bool no_where_clause); - /* - Cleanup for re-execution in a prepared statement or a stored - procedure. - */ - - /* Remembered MERGE child def version. See top comment in ha_myisammrg.cc */ - ulong child_def_version; /** See comments for set_metadata_id() */ enum enum_table_ref_type m_table_ref_type; /** See comments for set_metadata_id() */ @@ -1782,4 +1841,7 @@ static inline void dbug_tmp_restore_column_maps(MY_BITMAP *read_set, size_t max_row_length(TABLE *table, const uchar *data); + +void init_mdl_requests(TABLE_LIST *table_list); + #endif /* TABLE_INCLUDED */ diff --git a/sql/transaction.cc b/sql/transaction.cc new file mode 100644 index 00000000000..8d9b4943404 --- /dev/null +++ b/sql/transaction.cc @@ -0,0 +1,671 @@ +/* Copyright (C) 2008 Sun/MySQL + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#include "mysql_priv.h" +#include "transaction.h" +#include "rpl_handler.h" + +/* Conditions under which the transaction state must not change. */ +static bool trans_check(THD *thd) +{ + enum xa_states xa_state= thd->transaction.xid_state.xa_state; + DBUG_ENTER("trans_check"); + + if (unlikely(thd->in_sub_stmt)) + my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); + if (xa_state != XA_NOTR) + my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]); + else + DBUG_RETURN(FALSE); + + DBUG_RETURN(TRUE); +} + + +/** + Mark a XA transaction as rollback-only if the RM unilaterally + rolled back the transaction branch. + + @note If a rollback was requested by the RM, this function sets + the appropriate rollback error code and transits the state + to XA_ROLLBACK_ONLY. + + @return TRUE if transaction was rolled back or if the transaction + state is XA_ROLLBACK_ONLY. FALSE otherwise. +*/ +static bool xa_trans_rolled_back(XID_STATE *xid_state) +{ + if (xid_state->rm_error) + { + switch (xid_state->rm_error) { + case ER_LOCK_WAIT_TIMEOUT: + my_error(ER_XA_RBTIMEOUT, MYF(0)); + break; + case ER_LOCK_DEADLOCK: + my_error(ER_XA_RBDEADLOCK, MYF(0)); + break; + default: + my_error(ER_XA_RBROLLBACK, MYF(0)); + } + xid_state->xa_state= XA_ROLLBACK_ONLY; + } + + return (xid_state->xa_state == XA_ROLLBACK_ONLY); +} + + +/** + Begin a new transaction. + + @note Beginning a transaction implicitly commits any current + transaction and releases existing locks. + + @param thd Current thread + @param flags Transaction flags + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_begin(THD *thd, uint flags) +{ + int res= FALSE; + DBUG_ENTER("trans_begin"); + + if (trans_check(thd)) + DBUG_RETURN(TRUE); + + thd->locked_tables_list.unlock_locked_tables(thd); + + DBUG_ASSERT(!thd->locked_tables_mode); + + if (trans_commit_implicit(thd)) + DBUG_RETURN(TRUE); + + /* + Release transactional metadata locks only after the + transaction has been committed. + */ + thd->mdl_context.release_transactional_locks(); + + thd->variables.option_bits|= OPTION_BEGIN; + thd->server_status|= SERVER_STATUS_IN_TRANS; + + if (flags & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT) + res= ha_start_consistent_snapshot(thd); + + DBUG_RETURN(test(res)); +} + + +/** + Commit the current transaction, making its changes permanent. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_commit(THD *thd) +{ + int res; + DBUG_ENTER("trans_commit"); + + if (trans_check(thd)) + DBUG_RETURN(TRUE); + + thd->server_status&= ~SERVER_STATUS_IN_TRANS; + res= ha_commit_trans(thd, TRUE); + if (res) + /* + if res is non-zero, then ha_commit_trans has rolled back the + transaction, so the hooks for rollback will be called. + */ + RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + else + RUN_HOOK(transaction, after_commit, (thd, FALSE)); + thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + thd->transaction.all.modified_non_trans_table= FALSE; + thd->lex->start_transaction_opt= 0; + + DBUG_RETURN(test(res)); +} + + +/** + Implicitly commit the current transaction. + + @note A implicit commit does not releases existing table locks. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_commit_implicit(THD *thd) +{ + bool res= FALSE; + DBUG_ENTER("trans_commit_implicit"); + + if (trans_check(thd)) + DBUG_RETURN(TRUE); + + if (thd->in_multi_stmt_transaction() || + (thd->variables.option_bits & OPTION_TABLE_LOCK)) + { + /* Safety if one did "drop table" on locked tables */ + if (!thd->locked_tables_mode) + thd->variables.option_bits&= ~OPTION_TABLE_LOCK; + thd->server_status&= ~SERVER_STATUS_IN_TRANS; + res= test(ha_commit_trans(thd, TRUE)); + } + + thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + thd->transaction.all.modified_non_trans_table= FALSE; + + DBUG_RETURN(res); +} + + +/** + Rollback the current transaction, canceling its changes. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_rollback(THD *thd) +{ + int res; + DBUG_ENTER("trans_rollback"); + + if (trans_check(thd)) + DBUG_RETURN(TRUE); + + thd->server_status&= ~SERVER_STATUS_IN_TRANS; + res= ha_rollback_trans(thd, TRUE); + RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + thd->transaction.all.modified_non_trans_table= FALSE; + thd->lex->start_transaction_opt= 0; + + DBUG_RETURN(test(res)); +} + + +/** + Commit the single statement transaction. + + @note Note that if the autocommit is on, then the following call + inside InnoDB will commit or rollback the whole transaction + (= the statement). The autocommit mechanism built into InnoDB + is based on counting locks, but if the user has used LOCK + TABLES then that mechanism does not know to do the commit. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_commit_stmt(THD *thd) +{ + DBUG_ENTER("trans_commit_stmt"); + int res= FALSE; + if (thd->transaction.stmt.ha_list) + res= ha_commit_trans(thd, FALSE); + + if (res) + /* + if res is non-zero, then ha_commit_trans has rolled back the + transaction, so the hooks for rollback will be called. + */ + RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + else + RUN_HOOK(transaction, after_commit, (thd, FALSE)); + DBUG_RETURN(test(res)); +} + + +/** + Rollback the single statement transaction. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ +bool trans_rollback_stmt(THD *thd) +{ + DBUG_ENTER("trans_rollback_stmt"); + + if (thd->transaction.stmt.ha_list) + { + ha_rollback_trans(thd, FALSE); + if (thd->transaction_rollback_request && !thd->in_sub_stmt) + ha_rollback_trans(thd, TRUE); + } + + RUN_HOOK(transaction, after_rollback, (thd, FALSE)); + + DBUG_RETURN(FALSE); +} + +/* Find a named savepoint in the current transaction. */ +static SAVEPOINT ** +find_savepoint(THD *thd, LEX_STRING name) +{ + SAVEPOINT **sv= &thd->transaction.savepoints; + + while (*sv) + { + if (my_strnncoll(system_charset_info, (uchar *) name.str, name.length, + (uchar *) (*sv)->name, (*sv)->length) == 0) + break; + sv= &(*sv)->prev; + } + + return sv; +} + + +/** + Set a named transaction savepoint. + + @param thd Current thread + @param name Savepoint name + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_savepoint(THD *thd, LEX_STRING name) +{ + SAVEPOINT **sv, *newsv; + DBUG_ENTER("trans_savepoint"); + + if (!(thd->in_multi_stmt_transaction() || thd->in_sub_stmt) || + !opt_using_transactions) + DBUG_RETURN(FALSE); + + sv= find_savepoint(thd, name); + + if (*sv) /* old savepoint of the same name exists */ + { + newsv= *sv; + ha_release_savepoint(thd, *sv); + *sv= (*sv)->prev; + } + else if ((newsv= (SAVEPOINT *) alloc_root(&thd->transaction.mem_root, + savepoint_alloc_size)) == NULL) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + DBUG_RETURN(TRUE); + } + + newsv->name= strmake_root(&thd->transaction.mem_root, name.str, name.length); + newsv->length= name.length; + + /* + if we'll get an error here, don't add new savepoint to the list. + we'll lose a little bit of memory in transaction mem_root, but it'll + be free'd when transaction ends anyway + */ + if (ha_savepoint(thd, newsv)) + DBUG_RETURN(TRUE); + + newsv->prev= thd->transaction.savepoints; + thd->transaction.savepoints= newsv; + + /* + Remember the last acquired lock before the savepoint was set. + This is used as a marker to only release locks acquired after + the setting of this savepoint. + Note: this works just fine if we're under LOCK TABLES, + since mdl_savepoint() is guaranteed to be beyond + the last locked table. This allows to release some + locks acquired during LOCK TABLES. + */ + newsv->mdl_savepoint = thd->mdl_context.mdl_savepoint(); + + DBUG_RETURN(FALSE); +} + + +/** + Rollback a transaction to the named savepoint. + + @note Modifications that the current transaction made to + rows after the savepoint was set are undone in the + rollback. + + @note Savepoints that were set at a later time than the + named savepoint are deleted. + + @param thd Current thread + @param name Savepoint name + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_rollback_to_savepoint(THD *thd, LEX_STRING name) +{ + int res= FALSE; + SAVEPOINT *sv= *find_savepoint(thd, name); + DBUG_ENTER("trans_rollback_to_savepoint"); + + if (sv == NULL) + { + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", name.str); + DBUG_RETURN(TRUE); + } + + if (ha_rollback_to_savepoint(thd, sv)) + res= TRUE; + else if (((thd->variables.option_bits & OPTION_KEEP_LOG) || + thd->transaction.all.modified_non_trans_table) && + !thd->slave_thread) + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_WARNING_NOT_COMPLETE_ROLLBACK, + ER(ER_WARNING_NOT_COMPLETE_ROLLBACK)); + + thd->transaction.savepoints= sv; + + /* + Release metadata locks that were acquired during this savepoint unit. + */ + if (!res) + thd->mdl_context.rollback_to_savepoint(sv->mdl_savepoint); + + DBUG_RETURN(test(res)); +} + + +/** + Remove the named savepoint from the set of savepoints of + the current transaction. + + @note No commit or rollback occurs. It is an error if the + savepoint does not exist. + + @param thd Current thread + @param name Savepoint name + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_release_savepoint(THD *thd, LEX_STRING name) +{ + int res= FALSE; + SAVEPOINT *sv= *find_savepoint(thd, name); + DBUG_ENTER("trans_release_savepoint"); + + if (sv == NULL) + { + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", name.str); + DBUG_RETURN(TRUE); + } + + if (ha_release_savepoint(thd, sv)) + res= TRUE; + + thd->transaction.savepoints= sv->prev; + + DBUG_RETURN(test(res)); +} + + +/** + Starts an XA transaction with the given xid value. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_xa_start(THD *thd) +{ + enum xa_states xa_state= thd->transaction.xid_state.xa_state; + DBUG_ENTER("trans_xa_start"); + + if (xa_state == XA_IDLE && thd->lex->xa_opt == XA_RESUME) + { + bool not_equal= !thd->transaction.xid_state.xid.eq(thd->lex->xid); + if (not_equal) + my_error(ER_XAER_NOTA, MYF(0)); + else + thd->transaction.xid_state.xa_state= XA_ACTIVE; + DBUG_RETURN(not_equal); + } + + /* TODO: JOIN is not supported yet. */ + if (thd->lex->xa_opt != XA_NONE) + my_error(ER_XAER_INVAL, MYF(0)); + else if (xa_state != XA_NOTR) + my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]); + else if (thd->locked_tables_mode || thd->active_transaction()) + my_error(ER_XAER_OUTSIDE, MYF(0)); + else if (xid_cache_search(thd->lex->xid)) + my_error(ER_XAER_DUPID, MYF(0)); + else if (!trans_begin(thd)) + { + DBUG_ASSERT(thd->transaction.xid_state.xid.is_null()); + thd->transaction.xid_state.xa_state= XA_ACTIVE; + thd->transaction.xid_state.rm_error= 0; + thd->transaction.xid_state.xid.set(thd->lex->xid); + xid_cache_insert(&thd->transaction.xid_state); + DBUG_RETURN(FALSE); + } + + DBUG_RETURN(TRUE); +} + + +/** + Put a XA transaction in the IDLE state. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_xa_end(THD *thd) +{ + DBUG_ENTER("trans_xa_end"); + + /* TODO: SUSPEND and FOR MIGRATE are not supported yet. */ + if (thd->lex->xa_opt != XA_NONE) + my_error(ER_XAER_INVAL, MYF(0)); + else if (thd->transaction.xid_state.xa_state != XA_ACTIVE) + my_error(ER_XAER_RMFAIL, MYF(0), + xa_state_names[thd->transaction.xid_state.xa_state]); + else if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) + my_error(ER_XAER_NOTA, MYF(0)); + else if (!xa_trans_rolled_back(&thd->transaction.xid_state)) + thd->transaction.xid_state.xa_state= XA_IDLE; + + DBUG_RETURN(thd->transaction.xid_state.xa_state != XA_IDLE); +} + + +/** + Put a XA transaction in the PREPARED state. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_xa_prepare(THD *thd) +{ + DBUG_ENTER("trans_xa_prepare"); + + if (thd->transaction.xid_state.xa_state != XA_IDLE) + my_error(ER_XAER_RMFAIL, MYF(0), + xa_state_names[thd->transaction.xid_state.xa_state]); + else if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) + my_error(ER_XAER_NOTA, MYF(0)); + else if (ha_prepare(thd)) + { + xid_cache_delete(&thd->transaction.xid_state); + thd->transaction.xid_state.xa_state= XA_NOTR; + my_error(ER_XA_RBROLLBACK, MYF(0)); + } + else + thd->transaction.xid_state.xa_state= XA_PREPARED; + + DBUG_RETURN(thd->transaction.xid_state.xa_state != XA_PREPARED); +} + + +/** + Commit and terminate the a XA transaction. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_xa_commit(THD *thd) +{ + bool res= TRUE; + enum xa_states xa_state= thd->transaction.xid_state.xa_state; + DBUG_ENTER("trans_xa_commit"); + + if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) + { + XID_STATE *xs= xid_cache_search(thd->lex->xid); + res= !xs || xs->in_thd; + if (res) + my_error(ER_XAER_NOTA, MYF(0)); + else + { + res= xa_trans_rolled_back(xs); + ha_commit_or_rollback_by_xid(thd->lex->xid, !res); + xid_cache_delete(xs); + } + DBUG_RETURN(res); + } + + if (xa_trans_rolled_back(&thd->transaction.xid_state)) + { + if ((res= test(ha_rollback_trans(thd, TRUE)))) + my_error(ER_XAER_RMERR, MYF(0)); + } + else if (xa_state == XA_IDLE && thd->lex->xa_opt == XA_ONE_PHASE) + { + int r= ha_commit_trans(thd, TRUE); + if ((res= test(r))) + my_error(r == 1 ? ER_XA_RBROLLBACK : ER_XAER_RMERR, MYF(0)); + } + else if (xa_state == XA_PREPARED && thd->lex->xa_opt == XA_NONE) + { + if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, FALSE)) + { + ha_rollback_trans(thd, TRUE); + my_error(ER_XAER_RMERR, MYF(0)); + } + else + { + res= test(ha_commit_one_phase(thd, 1)); + if (res) + my_error(ER_XAER_RMERR, MYF(0)); + thd->global_read_lock.start_waiting_global_read_lock(thd); + } + } + else + { + my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]); + DBUG_RETURN(TRUE); + } + + thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + thd->transaction.all.modified_non_trans_table= FALSE; + thd->server_status&= ~SERVER_STATUS_IN_TRANS; + xid_cache_delete(&thd->transaction.xid_state); + thd->transaction.xid_state.xa_state= XA_NOTR; + + DBUG_RETURN(res); +} + + +/** + Roll back and terminate a XA transaction. + + @param thd Current thread + + @retval FALSE Success + @retval TRUE Failure +*/ + +bool trans_xa_rollback(THD *thd) +{ + bool res= TRUE; + enum xa_states xa_state= thd->transaction.xid_state.xa_state; + DBUG_ENTER("trans_xa_rollback"); + + if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) + { + XID_STATE *xs= xid_cache_search(thd->lex->xid); + if (!xs || xs->in_thd) + my_error(ER_XAER_NOTA, MYF(0)); + else + { + xa_trans_rolled_back(xs); + ha_commit_or_rollback_by_xid(thd->lex->xid, 0); + xid_cache_delete(xs); + } + DBUG_RETURN(thd->stmt_da->is_error()); + } + + if (xa_state != XA_IDLE && xa_state != XA_PREPARED && xa_state != XA_ROLLBACK_ONLY) + { + my_error(ER_XAER_RMFAIL, MYF(0), xa_state_names[xa_state]); + DBUG_RETURN(TRUE); + } + + /* + Resource Manager error is meaningless at this point, as we perform + explicit rollback request by user. We must reset rm_error before + calling ha_rollback(), so thd->transaction.xid structure gets reset + by ha_rollback()/THD::transaction::cleanup(). + */ + thd->transaction.xid_state.rm_error= 0; + if ((res= test(ha_rollback_trans(thd, TRUE)))) + my_error(ER_XAER_RMERR, MYF(0)); + + thd->variables.option_bits&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); + thd->transaction.all.modified_non_trans_table= FALSE; + thd->server_status&= ~SERVER_STATUS_IN_TRANS; + xid_cache_delete(&thd->transaction.xid_state); + thd->transaction.xid_state.xa_state= XA_NOTR; + + DBUG_RETURN(res); +} diff --git a/sql/transaction.h b/sql/transaction.h new file mode 100644 index 00000000000..135e6cae73f --- /dev/null +++ b/sql/transaction.h @@ -0,0 +1,46 @@ +/* Copyright (C) 2008 Sun/MySQL + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifndef TRANSACTION_H +#define TRANSACTION_H + +#ifdef USE_PRAGMA_INTERFACE +#pragma interface /* gcc class implementation */ +#endif + +#include <my_global.h> +#include <m_string.h> + +class THD; + +bool trans_begin(THD *thd, uint flags= 0); +bool trans_commit(THD *thd); +bool trans_commit_implicit(THD *thd); +bool trans_rollback(THD *thd); + +bool trans_commit_stmt(THD *thd); +bool trans_rollback_stmt(THD *thd); + +bool trans_savepoint(THD *thd, LEX_STRING name); +bool trans_rollback_to_savepoint(THD *thd, LEX_STRING name); +bool trans_release_savepoint(THD *thd, LEX_STRING name); + +bool trans_xa_start(THD *thd); +bool trans_xa_end(THD *thd); +bool trans_xa_prepare(THD *thd); +bool trans_xa_commit(THD *thd); +bool trans_xa_rollback(THD *thd); + +#endif /* TRANSACTION_H */ diff --git a/sql/tztime.cc b/sql/tztime.cc index 442669d9c3d..01450c9bae3 100644 --- a/sql/tztime.cc +++ b/sql/tztime.cc @@ -1585,7 +1585,6 @@ my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap) { THD *thd; TABLE_LIST tz_tables[1+MY_TZ_TABLES_COUNT]; - Open_tables_state open_tables_state_backup; TABLE *table; Tz_names_entry *tmp_tzname; my_bool return_val= 1; @@ -1662,12 +1661,14 @@ my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap) tz_init_table_list(tz_tables+1); tz_tables[0].next_global= tz_tables[0].next_local= &tz_tables[1]; tz_tables[1].prev_global= &tz_tables[0].next_global; + init_mdl_requests(tz_tables); /* We need to open only mysql.time_zone_leap_second, but we try to open all time zone tables to see if they exist. */ - if (open_system_tables_for_read(thd, tz_tables, &open_tables_state_backup)) + if (open_and_lock_tables_derived(thd, tz_tables, FALSE, + MYSQL_LOCK_IGNORE_FLUSH)) { sql_print_warning("Can't open and lock time zone table: %s " "trying to live without them", thd->stmt_da->message()); @@ -1676,6 +1677,9 @@ my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap) goto end_with_setting_default_tz; } + for (TABLE_LIST *tl= tz_tables; tl; tl= tl->next_global) + tl->table->use_all_columns(); + /* Now we are going to load leap seconds descriptions that are shared between all time zones that use them. We are using index for getting @@ -1764,7 +1768,8 @@ end_with_close: if (time_zone_tables_exist) { thd->version--; /* Force close to free memory */ - close_system_tables(thd, &open_tables_state_backup); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); } end_with_cleanup: @@ -2322,9 +2327,10 @@ my_tz_find(THD *thd, const String *name) else if (time_zone_tables_exist) { TABLE_LIST tz_tables[MY_TZ_TABLES_COUNT]; - Open_tables_state open_tables_state_backup; + Open_tables_backup open_tables_state_backup; tz_init_table_list(tz_tables); + init_mdl_requests(tz_tables); if (!open_system_tables_for_read(thd, tz_tables, &open_tables_state_backup)) { diff --git a/storage/myisam/ha_myisam.cc b/storage/myisam/ha_myisam.cc index 1117e8fff15..77b94947ba6 100644 --- a/storage/myisam/ha_myisam.cc +++ b/storage/myisam/ha_myisam.cc @@ -1006,7 +1006,7 @@ int ha_myisam::repair(THD *thd, MI_CHECK ¶m, bool do_optimize) ha_release_temporary_latches(thd); // Don't lock tables if we have used LOCK TABLE - if (!thd->locked_tables && + if (! thd->locked_tables_mode && mi_lock_database(file, table->s->tmp_table ? F_EXTRA_LCK : F_WRLCK)) { mi_check_print_error(¶m,ER(ER_CANT_LOCK),my_errno); @@ -1116,7 +1116,7 @@ int ha_myisam::repair(THD *thd, MI_CHECK ¶m, bool do_optimize) update_state_info(¶m, file, 0); } thd_proc_info(thd, old_proc_info); - if (!thd->locked_tables) + if (! thd->locked_tables_mode) mi_lock_database(file,F_UNLCK); DBUG_RETURN(error ? HA_ADMIN_FAILED : !optimize_done ? HA_ADMIN_ALREADY_DONE : HA_ADMIN_OK); @@ -1709,7 +1709,7 @@ int ha_myisam::info(uint flag) /* Update share */ if (share->tmp_table == NO_TMP_TABLE) - mysql_mutex_lock(&share->mutex); + mysql_mutex_lock(&share->LOCK_ha_data); share->keys_in_use.set_prefix(share->keys); share->keys_in_use.intersect_extended(misam_info.key_map); share->keys_for_keyread.intersect(share->keys_in_use); @@ -1719,7 +1719,7 @@ int ha_myisam::info(uint flag) (char*) misam_info.rec_per_key, sizeof(table->key_info[0].rec_per_key[0])*share->key_parts); if (share->tmp_table == NO_TMP_TABLE) - mysql_mutex_unlock(&share->mutex); + mysql_mutex_unlock(&share->LOCK_ha_data); /* Set data_file_name and index_file_name to point at the symlink value diff --git a/storage/myisammrg/ha_myisammrg.cc b/storage/myisammrg/ha_myisammrg.cc index 49ea9626b21..a34a52d8f9c 100644 --- a/storage/myisammrg/ha_myisammrg.cc +++ b/storage/myisammrg/ha_myisammrg.cc @@ -33,40 +33,29 @@ and hence through open_tables(). When the parent appears in the list of tables to open, the initial open of the handler does nothing but read the meta file and collect a list of TABLE_LIST objects for the - children. This list is attached to the parent TABLE object as - TABLE::child_l. The end of the children list is saved in - TABLE::child_last_l. - - Back in open_tables(), add_merge_table_list() is called. It updates - each list member with the lock type and a back pointer to the parent - TABLE_LIST object TABLE_LIST::parent_l. The list is then inserted in - the list of tables to open, right behind the parent. Consequently, - open_tables() opens the children, one after the other. The TABLE - references of the TABLE_LIST objects are implicitly set to the open - tables. The children are opened as independent MyISAM tables, right as - if they are used by the SQL statement. - - TABLE_LIST::parent_l is required to find the parent 1. when the last - child has been opened and children are to be attached, and 2. when an - error happens during child open and the child list must be removed - from the queuery list. In these cases the current child does not have - TABLE::parent set or does not have a TABLE at all respectively. - - When the last child is open, attach_merge_children() is called. It - removes the list of children from the open list. Then the children are - "attached" to the parent. All required references between parent and + children. This list is attached to the handler object as + ha_myisammrg::children_l. The end of the children list is saved in + ha_myisammrg::children_last_l. + + Back in open_tables(), handler::extra(HA_EXTRA_ADD_CHILDREN_LIST) is + called. It updates each list member with the lock type and a back + pointer to the parent TABLE_LIST object TABLE_LIST::parent_l. The list + is then inserted in the list of tables to open, right behind the + parent. Consequently, open_tables() opens the children, one after the + other. The TABLE references of the TABLE_LIST objects are implicitly + set to the open tables by open_table(). The children are opened as + independent MyISAM tables, right as if they are used by the SQL + statement. + + When all tables from the statement query list are open, + handler::extra(HA_EXTRA_ATTACH_CHILDREN) is called. It "attaches" the + children to the parent. All required references between parent and children are set up. The MERGE storage engine sets up an array with references to the low-level MyISAM table objects (MI_INFO). It remembers the state of the table in MYRG_INFO::children_attached. - Every child TABLE::parent references the parent TABLE object. That way - TABLE objects belonging to a MERGE table can be identified. - TABLE::parent is required because the parent and child TABLE objects - can live longer than the parent TABLE_LIST object. So the path - child->pos_in_table_list->parent_l->table can be broken. - If necessary, the compatibility of parent and children is checked. This check is necessary when any of the objects are reopend. This is detected by comparing the current table def version against the @@ -80,14 +69,20 @@ myisammrg_attach_children_callback() sets it ot TRUE if a table def version mismatches the remembered child def version. - Finally the parent TABLE::children_attached is set. + The children chain remains in the statement query list until the table + is closed or the children are detached. This is done so that the + children are locked by lock_tables(). + + At statement end the children are detached. At the next statement + begin the open-add-attach sequence repeats. There is no exception for + LOCK TABLES. The fresh establishment of the parent-child relationship + before every statement catches numerous cases of ALTER/FLUSH/DROP/etc + of parent or children during LOCK TABLES. --- On parent open the storage engine structures are allocated and initialized. They stay with the open table until its final close. - - */ #ifdef USE_PRAGMA_IMPLEMENTATION @@ -118,7 +113,10 @@ static handler *myisammrg_create_handler(handlerton *hton, ha_myisammrg::ha_myisammrg(handlerton *hton, TABLE_SHARE *table_arg) :handler(hton, table_arg), file(0), is_cloned(0) -{} +{ + init_sql_alloc(&children_mem_root, + FN_REFLEN + ALLOC_ROOT_MIN_BLOCK_SIZE, 0); +} /** @@ -126,7 +124,9 @@ ha_myisammrg::ha_myisammrg(handlerton *hton, TABLE_SHARE *table_arg) */ ha_myisammrg::~ha_myisammrg(void) -{} +{ + free_root(&children_mem_root, MYF(0)); +} static const char *ha_myisammrg_exts[] = { @@ -178,49 +178,53 @@ const char *ha_myisammrg::index_type(uint key_number) /** - @brief Callback function for open of a MERGE parent table. - - @detail This function adds a TABLE_LIST object for a MERGE child table - to a list of tables of the parent TABLE object. It is called for - each child table. - - The list of child TABLE_LIST objects is kept in the TABLE object of - the parent for the whole life time of the MERGE table. It is - inserted in the statement list behind the MERGE parent TABLE_LIST - object when the MERGE table is opened. It is removed from the - statement list after the last child is opened. - - All memeory used for the child TABLE_LIST objects and the strings - referred by it are taken from the parent TABLE::mem_root. Thus they - are all freed implicitly at the final close of the table. - - TABLE::child_l -> TABLE_LIST::next_global -> TABLE_LIST::next_global - # # ^ # ^ - # # | # | - # # +--------- TABLE_LIST::prev_global - # # | - # |<--- TABLE_LIST::prev_global | - # | - TABLE::child_last_l -----------------------------------------+ + Callback function for open of a MERGE parent table. @param[in] callback_param data pointer as given to myrg_parent_open() + this is used to pass the handler handle @param[in] filename file name of MyISAM table without extension. @return status @retval 0 OK @retval != 0 Error + + @detail + + This function adds a TABLE_LIST object for a MERGE child table to a + list of tables in the parent handler object. It is called for each + child table. + + The list of child TABLE_LIST objects is kept in the handler object + of the parent for the whole life time of the MERGE table. It is + inserted in the statement query list behind the MERGE parent + TABLE_LIST object when the MERGE table is opened. It is removed from + the statement query list at end of statement or at children detach. + + All memory used for the child TABLE_LIST objects and the strings + referred by it are taken from the parent + ha_myisammrg::children_mem_root. Thus they are all freed implicitly at + the final close of the table. + + children_l -> TABLE_LIST::next_global -> TABLE_LIST::next_global + # # ^ # ^ + # # | # | + # # +--------- TABLE_LIST::prev_global + # # | + # |<--- TABLE_LIST::prev_global | + # | + children_last_l -----------------------------------------+ */ static int myisammrg_parent_open_callback(void *callback_param, const char *filename) { - ha_myisammrg *ha_myrg; - TABLE *parent; - TABLE_LIST *child_l; - const char *db; - const char *table_name; - size_t dirlen; + ha_myisammrg *ha_myrg= (ha_myisammrg*) callback_param; + Mrg_child_def *mrg_child_def; + char *db; + char *table_name; + uint dirlen; + uint table_name_length; char dir_path[FN_REFLEN]; DBUG_ENTER("myisammrg_parent_open_callback"); @@ -234,7 +238,7 @@ static int myisammrg_parent_open_callback(void *callback_param, DBUG_RETURN(1); /* purecov: end */ } - table_name= filename + dirlen; + table_name= (char*) filename + dirlen; dirlen--; /* Strip off trailing '/'. */ memcpy(dir_path, filename, dirlen); dir_path[dirlen]= '\0'; @@ -242,117 +246,313 @@ static int myisammrg_parent_open_callback(void *callback_param, dirlen-= db - dir_path; /* This is now the length of 'db'. */ DBUG_PRINT("myrg", ("open: '%s'.'%s'", db, table_name)); - ha_myrg= (ha_myisammrg*) callback_param; - parent= ha_myrg->table_ptr(); + /* Set database (schema) name. */ + db= strmake_root(&ha_myrg->children_mem_root, db, dirlen); + /* Set table name. */ + table_name_length= strlen(table_name); + table_name= strmake_root(&ha_myrg->children_mem_root, table_name, + table_name_length); + + if (! db || ! table_name) + DBUG_RETURN(1); - /* Get a TABLE_LIST object. */ - if (!(child_l= (TABLE_LIST*) alloc_root(&parent->mem_root, - sizeof(TABLE_LIST)))) + /* Convert to lowercase if required. */ + if (lower_case_table_names && table_name_length) + { + /* purecov: begin tested */ + table_name_length= my_casedn_str(files_charset_info, table_name); + /* purecov: end */ + } + + mrg_child_def= new (&ha_myrg->children_mem_root) + Mrg_child_def(db, dirlen, table_name, table_name_length); + + if (! mrg_child_def || + ha_myrg->child_def_list.push_back(mrg_child_def, + &ha_myrg->children_mem_root)) { - /* purecov: begin inspected */ - DBUG_PRINT("error", ("my_malloc error: %d", my_errno)); DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + + +/** + Open a MERGE parent table, but not its children. + + @param[in] name MERGE table path name + @param[in] mode read/write mode, unused + @param[in] test_if_locked_arg open flags + + @return status + @retval 0 OK + @retval -1 Error, my_errno gives reason + + @detail + This function initializes the MERGE storage engine structures + and adds a child list of TABLE_LIST to the parent handler. +*/ + +int ha_myisammrg::open(const char *name, int mode __attribute__((unused)), + uint test_if_locked_arg) +{ + DBUG_ENTER("ha_myisammrg::open"); + DBUG_PRINT("myrg", ("name: '%s' table: 0x%lx", name, (long) table)); + DBUG_PRINT("myrg", ("test_if_locked: %u", test_if_locked_arg)); + + /* Must not be used when table is open. */ + DBUG_ASSERT(!this->file); + + /* Save for later use. */ + test_if_locked= test_if_locked_arg; + + /* In case this handler was open and closed before, free old data. */ + free_root(&this->children_mem_root, MYF(MY_MARK_BLOCKS_FREE)); + + /* + Initialize variables that are used, modified, and/or set by + myisammrg_parent_open_callback(). + 'children_l' is the head of the children chain. + 'children_last_l' points to the end of the children chain. + 'my_errno' is set by myisammrg_parent_open_callback() in + case of an error. + */ + children_l= NULL; + children_last_l= NULL; + child_def_list.empty(); + my_errno= 0; + + /* retrieve children table list. */ + if (is_cloned) + { + /* + Open and attaches the MyISAM tables,that are under the MERGE table + parent, on the MyISAM storage engine interface directly within the + MERGE engine. The new MyISAM table instances, as well as the MERGE + clone itself, are not visible in the table cache. This is not a + problem because all locking is handled by the original MERGE table + from which this is cloned of. + */ + if (!(file= myrg_open(name, table->db_stat, HA_OPEN_IGNORE_IF_LOCKED))) + { + DBUG_PRINT("error", ("my_errno %d", my_errno)); + DBUG_RETURN(my_errno ? my_errno : -1); + } + + file->children_attached= TRUE; + + info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST); + } + else if (!(file= myrg_parent_open(name, myisammrg_parent_open_callback, this))) + { + /* purecov: begin inspected */ + DBUG_PRINT("error", ("my_errno %d", my_errno)); + DBUG_RETURN(my_errno ? my_errno : -1); /* purecov: end */ } - bzero((char*) child_l, sizeof(TABLE_LIST)); + DBUG_PRINT("myrg", ("MYRG_INFO: 0x%lx child tables: %u", + (long) file, file->tables)); + DBUG_RETURN(0); +} - /* Set database (schema) name. */ - child_l->db_length= dirlen; - child_l->db= strmake_root(&parent->mem_root, db, dirlen); - /* Set table name. */ - child_l->table_name_length= strlen(table_name); - child_l->table_name= strmake_root(&parent->mem_root, table_name, - child_l->table_name_length); - /* Convert to lowercase if required. */ - if (lower_case_table_names && child_l->table_name_length) - child_l->table_name_length= my_casedn_str(files_charset_info, - child_l->table_name); - /* Set alias. */ - child_l->alias= child_l->table_name; - /* Initialize table map to 'undefined'. */ - child_l->init_child_def_version(); +/** + Add list of MERGE children to a TABLE_LIST chain. + + @return status + @retval 0 OK + @retval != 0 Error + + @detail + When a MERGE parent table has just been opened, insert the + TABLE_LIST chain from the MERGE handler into the table list used for + opening tables for this statement. This lets the children be opened + too. +*/ - /* Link TABLE_LIST object into the parent list. */ - if (!parent->child_last_l) +int ha_myisammrg::add_children_list(void) +{ + TABLE_LIST *parent_l= this->table->pos_in_table_list; + THD *thd= table->in_use; + List_iterator_fast<Mrg_child_def> it(child_def_list); + Mrg_child_def *mrg_child_def; + DBUG_ENTER("ha_myisammrg::add_children_list"); + DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", this->table->s->db.str, + this->table->s->table_name.str, (long) this->table)); + + /* Must call this with open table. */ + DBUG_ASSERT(this->file); + + /* Ignore this for empty MERGE tables (UNION=()). */ + if (!this->file->tables) { - /* Initialize parent->child_last_l when handling first child. */ - parent->child_last_l= &parent->child_l; + DBUG_PRINT("myrg", ("empty merge table union")); + goto end; } - *parent->child_last_l= child_l; - child_l->prev_global= parent->child_last_l; - parent->child_last_l= &child_l->next_global; + /* Must not call this with attached children. */ + DBUG_ASSERT(!this->file->children_attached); + + /* Must not call this with children list in place. */ + DBUG_ASSERT(this->children_l == NULL); + + /* + Prevent inclusion of another MERGE table, which could make infinite + recursion. + */ + if (parent_l->parent_l) + { + my_error(ER_ADMIN_WRONG_MRG_TABLE, MYF(0), parent_l->alias); + DBUG_RETURN(1); + } + + while ((mrg_child_def= it++)) + { + TABLE_LIST *child_l; + char *db; + char *table_name; + + child_l= (TABLE_LIST*) thd->alloc(sizeof(TABLE_LIST)); + db= (char*) thd->memdup(mrg_child_def->db.str, mrg_child_def->db.length+1); + table_name= (char*) thd->memdup(mrg_child_def->name.str, + mrg_child_def->name.length+1); + + if (child_l == NULL || db == NULL || table_name == NULL) + DBUG_RETURN(1); + + child_l->init_one_table(db, mrg_child_def->db.length, + table_name, mrg_child_def->name.length, + table_name, parent_l->lock_type); + /* Set parent reference. Used to detect MERGE in children list. */ + child_l->parent_l= parent_l; + /* Copy select_lex. Used in unique_table() at least. */ + child_l->select_lex= parent_l->select_lex; + /* + Set the expected table version, to not cause spurious re-prepare. + @todo: revise after the fix for Bug#36171 + */ + child_l->set_table_ref_id(mrg_child_def->get_child_table_ref_type(), + mrg_child_def->get_child_def_version()); + /* Link TABLE_LIST object into the children list. */ + if (this->children_last_l) + child_l->prev_global= this->children_last_l; + else + { + /* Initialize children_last_l when handling first child. */ + this->children_last_l= &this->children_l; + } + *this->children_last_l= child_l; + this->children_last_l= &child_l->next_global; + } + + /* Insert children into the table list. */ + if (parent_l->next_global) + parent_l->next_global->prev_global= this->children_last_l; + *this->children_last_l= parent_l->next_global; + parent_l->next_global= this->children_l; + this->children_l->prev_global= &parent_l->next_global; + /* + We have to update LEX::query_tables_last if children are added to + the tail of the table list in order to be able correctly add more + elements to it (e.g. as part of prelocking process). + */ + if (thd->lex->query_tables_last == &parent_l->next_global) + thd->lex->query_tables_last= this->children_last_l; + +end: DBUG_RETURN(0); } /** - @brief Callback function for attaching a MERGE child table. + A context of myrg_attach_children() callback. +*/ - @detail This function retrieves the MyISAM table handle from the - next child table. It is called for each child table. +class Mrg_attach_children_callback_param +{ +public: + /** + 'need_compat_check' is set by myisammrg_attach_children_callback() + if a child fails the table def version check. + */ + bool need_compat_check; + /** TABLE_LIST identifying this merge parent. */ + TABLE_LIST *parent_l; + /** Iterator position, the current child to attach. */ + TABLE_LIST *next_child_attach; + List_iterator_fast<Mrg_child_def> def_it; + Mrg_child_def *mrg_child_def; +public: + Mrg_attach_children_callback_param(TABLE_LIST *parent_l_arg, + TABLE_LIST *first_child, + List<Mrg_child_def> &child_def_list) + :need_compat_check(FALSE), + parent_l(parent_l_arg), + next_child_attach(first_child), + def_it(child_def_list), + mrg_child_def(def_it++) + {} + void next() + { + next_child_attach= next_child_attach->next_global; + if (next_child_attach && next_child_attach->parent_l != parent_l) + next_child_attach= NULL; + if (mrg_child_def) + mrg_child_def= def_it++; + } +}; - @param[in] callback_param data pointer as given to - myrg_attach_children() + +/** + Callback function for attaching a MERGE child table. + + @param[in] callback_param data pointer as given to myrg_attach_children() + this is used to pass the handler handle @return pointer to open MyISAM table structure @retval !=NULL OK, returning pointer @retval NULL, my_errno == 0 Ok, no more child tables @retval NULL, my_errno != 0 error + + @detail + This function retrieves the MyISAM table handle from the + next child table. It is called for each child table. */ static MI_INFO *myisammrg_attach_children_callback(void *callback_param) { - ha_myisammrg *ha_myrg; - TABLE *parent; + Mrg_attach_children_callback_param *param= + (Mrg_attach_children_callback_param*) callback_param; + TABLE *parent= param->parent_l->table; TABLE *child; - TABLE_LIST *child_l; - MI_INFO *myisam; + TABLE_LIST *child_l= param->next_child_attach; + Mrg_child_def *mrg_child_def= param->mrg_child_def; + MI_INFO *myisam= NULL; DBUG_ENTER("myisammrg_attach_children_callback"); - my_errno= 0; - ha_myrg= (ha_myisammrg*) callback_param; - parent= ha_myrg->table_ptr(); - - /* Get child list item. */ - child_l= ha_myrg->next_child_attach; if (!child_l) { DBUG_PRINT("myrg", ("No more children to attach")); - DBUG_RETURN(NULL); + my_errno= 0; /* Ok, no more child tables. */ + goto end; } child= child_l->table; - DBUG_PRINT("myrg", ("child table: '%s'.'%s' 0x%lx", child->s->db.str, - child->s->table_name.str, (long) child)); - /* - Prepare for next child. Used as child_l in next call to this function. - We cannot rely on a NULL-terminated chain. - */ - if (&child_l->next_global == parent->child_last_l) - { - DBUG_PRINT("myrg", ("attaching last child")); - ha_myrg->next_child_attach= NULL; - } - else - ha_myrg->next_child_attach= child_l->next_global; - - /* Set parent reference. */ - child->parent= parent; + /* Prepare for next child. */ + param->next(); /* Do a quick compatibility check. The table def version is set when the table share is created. The child def version is copied - from the table def version after a sucessful compatibility check. + from the table def version after a successful compatibility check. We need to repeat the compatibility check only if a child is opened from a different share than last time it was used with this MERGE table. */ DBUG_PRINT("myrg", ("table_def_version last: %lu current: %lu", - (ulong) child_l->get_child_def_version(), + (ulong) mrg_child_def->get_child_def_version(), (ulong) child->s->get_table_def_version())); - if (child_l->get_child_def_version() != child->s->get_table_def_version()) - ha_myrg->need_compat_check= TRUE; + if (mrg_child_def->get_child_def_version() != child->s->get_table_def_version()) + param->need_compat_check= TRUE; /* If parent is temporary, children must be temporary too and vice @@ -368,7 +568,7 @@ static MI_INFO *myisammrg_attach_children_callback(void *callback_param) DBUG_PRINT("error", ("temporary table mismatch parent: %d child: %d", parent->s->tmp_table, child->s->tmp_table)); my_errno= HA_ERR_WRONG_MRG_TABLE_DEF; - goto err; + goto end; } /* Extract the MyISAM table structure pointer from the handler object. */ @@ -383,69 +583,12 @@ static MI_INFO *myisammrg_attach_children_callback(void *callback_param) DBUG_PRINT("myrg", ("MyISAM handle: 0x%lx my_errno: %d", (long) myisam, my_errno)); - err: - DBUG_RETURN(my_errno ? NULL : myisam); + end: + DBUG_RETURN(myisam); } /** - @brief Open a MERGE parent table, not its children. - - @detail This function initializes the MERGE storage engine structures - and adds a child list of TABLE_LIST to the parent TABLE. - - @param[in] name MERGE table path name - @param[in] mode read/write mode, unused - @param[in] test_if_locked open flags - - @return status - @retval 0 OK - @retval -1 Error, my_errno gives reason -*/ - -int ha_myisammrg::open(const char *name, int mode __attribute__((unused)), - uint test_if_locked) -{ - DBUG_ENTER("ha_myisammrg::open"); - DBUG_PRINT("myrg", ("name: '%s' table: 0x%lx", name, (long) table)); - DBUG_PRINT("myrg", ("test_if_locked: %u", test_if_locked)); - - /* Save for later use. */ - this->test_if_locked= test_if_locked; - - /* retrieve children table list. */ - my_errno= 0; - if (is_cloned) - { - /* - Open and attaches the MyISAM tables,that are under the MERGE table - parent, on the MyISAM storage engine interface directly within the - MERGE engine. The new MyISAM table instances, as well as the MERGE - clone itself, are not visible in the table cache. This is not a - problem because all locking is handled by the original MERGE table - from which this is cloned of. - */ - if (!(file= myrg_open(table->s->normalized_path.str, table->db_stat, - HA_OPEN_IGNORE_IF_LOCKED))) - { - DBUG_PRINT("error", ("my_errno %d", my_errno)); - DBUG_RETURN(my_errno ? my_errno : -1); - } - - file->children_attached= TRUE; - - info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST); - } - else if (!(file= myrg_parent_open(name, myisammrg_parent_open_callback, this))) - { - DBUG_PRINT("error", ("my_errno %d", my_errno)); - DBUG_RETURN(my_errno ? my_errno : -1); - } - DBUG_PRINT("myrg", ("MYRG_INFO: 0x%lx", (long) file)); - DBUG_RETURN(0); -} - -/** Returns a cloned instance of the current handler. @return A cloned handler instance. @@ -496,22 +639,24 @@ handler *ha_myisammrg::clone(MEM_ROOT *mem_root) /** - @brief Attach children to a MERGE table. + Attach children to a MERGE table. - @detail Let the storage engine attach its children through a callback + @return status + @retval 0 OK + @retval != 0 Error, my_errno gives reason + + @detail + Let the storage engine attach its children through a callback function. Check table definitions for consistency. - @note Special thd->open_options may be in effect. We can make use of + @note + Special thd->open_options may be in effect. We can make use of them in attach. I.e. we use HA_OPEN_FOR_REPAIR to report the names of mismatching child tables. We cannot transport these options in ha_myisammrg::test_if_locked because they may change after the parent is opened. The parent is kept open in the table cache over multiple statements and can be used by other threads. Open options can change over time. - - @return status - @retval 0 OK - @retval != 0 Error, my_errno gives reason */ int ha_myisammrg::attach_children(void) @@ -521,36 +666,47 @@ int ha_myisammrg::attach_children(void) MI_KEYDEF *keyinfo; uint recs; uint keys= table->s->keys; + TABLE_LIST *parent_l= table->pos_in_table_list; int error; + Mrg_attach_children_callback_param param(parent_l, this->children_l, child_def_list); DBUG_ENTER("ha_myisammrg::attach_children"); DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", table->s->db.str, table->s->table_name.str, (long) table)); DBUG_PRINT("myrg", ("test_if_locked: %u", this->test_if_locked)); + + /* Must call this with open table. */ + DBUG_ASSERT(this->file); + + /* + A MERGE table with no children (empty union) is always seen as + attached internally. + */ + if (!this->file->tables) + { + DBUG_PRINT("myrg", ("empty merge table union")); + goto end; + } + DBUG_PRINT("myrg", ("child tables: %u", this->file->tables)); + + /* Must not call this with attached children. */ DBUG_ASSERT(!this->file->children_attached); + /* Must call this with children list in place. */ + DBUG_ASSERT(this->table->pos_in_table_list->next_global == this->children_l); + /* - Initialize variables that are used, modified, and/or set by - myisammrg_attach_children_callback(). - 'next_child_attach' traverses the chain of TABLE_LIST objects - that has been compiled during myrg_parent_open(). Every call - to myisammrg_attach_children_callback() moves the pointer to - the next object. - 'need_compat_check' is set by myisammrg_attach_children_callback() - if a child fails the table def version check. 'my_errno' is set by myisammrg_attach_children_callback() in case of an error. */ - next_child_attach= table->child_l; - need_compat_check= FALSE; my_errno= 0; if (myrg_attach_children(this->file, this->test_if_locked | current_thd->open_options, - myisammrg_attach_children_callback, this, - (my_bool *) &need_compat_check)) + myisammrg_attach_children_callback, ¶m, + (my_bool *) ¶m.need_compat_check)) { - DBUG_PRINT("error", ("my_errno %d", my_errno)); - DBUG_RETURN(my_errno ? my_errno : -1); + error= my_errno; + goto err; } DBUG_PRINT("myrg", ("calling myrg_extrafunc")); myrg_extrafunc(file, query_cache_invalidate_by_MyISAM_filename_ref); @@ -567,8 +723,8 @@ int ha_myisammrg::attach_children(void) always happen at the first attach because the reference child def version is initialized to 'undefined' at open. */ - DBUG_PRINT("myrg", ("need_compat_check: %d", need_compat_check)); - if (need_compat_check) + DBUG_PRINT("myrg", ("need_compat_check: %d", param.need_compat_check)); + if (param.need_compat_check) { TABLE_LIST *child_l; @@ -577,7 +733,11 @@ int ha_myisammrg::attach_children(void) DBUG_PRINT("error",("reclength: %lu mean_rec_length: %lu", table->s->reclength, stats.mean_rec_length)); if (test_if_locked & HA_OPEN_FOR_REPAIR) + { + /* purecov: begin inspected */ myrg_print_wrong_table(file->open_tables->table->filename); + /* purecov: end */ + } error= HA_ERR_WRONG_MRG_TABLE_DEF; goto err; } @@ -608,20 +768,25 @@ int ha_myisammrg::attach_children(void) my_free((uchar*) recinfo, MYF(0)); goto err; } + /* purecov: begin inspected */ myrg_print_wrong_table(u_table->table->filename); + /* purecov: end */ } } my_free((uchar*) recinfo, MYF(0)); if (error == HA_ERR_WRONG_MRG_TABLE_DEF) - goto err; + goto err; /* purecov: inspected */ - /* All checks passed so far. Now update child def version. */ - for (child_l= table->child_l; ; child_l= child_l->next_global) + List_iterator_fast<Mrg_child_def> def_it(child_def_list); + DBUG_ASSERT(this->children_l); + for (child_l= this->children_l; ; child_l= child_l->next_global) { - child_l->set_child_def_version( + Mrg_child_def *mrg_child_def= def_it++; + mrg_child_def->set_child_def_version( + child_l->table->s->get_table_ref_type(), child_l->table->s->get_table_def_version()); - if (&child_l->next_global == table->child_last_l) + if (&child_l->next_global == this->children_last_l) break; } } @@ -634,50 +799,132 @@ int ha_myisammrg::attach_children(void) goto err; } #endif + + end: DBUG_RETURN(0); err: - myrg_detach_children(file); + DBUG_PRINT("error", ("attaching MERGE children failed: %d", error)); + print_error(error, MYF(0)); + detach_children(); DBUG_RETURN(my_errno= error); } /** - @brief Detach all children from a MERGE table. - - @note Detach must not touch the children in any way. - They may have been closed at ths point already. - All references to the children should be removed. + Detach all children from a MERGE table and from the query list of tables. @return status @retval 0 OK @retval != 0 Error, my_errno gives reason + + @note + Detach must not touch the child TABLE objects in any way. + They may have been closed at ths point already. + All references to the children should be removed. */ int ha_myisammrg::detach_children(void) { + TABLE_LIST *child_l; DBUG_ENTER("ha_myisammrg::detach_children"); - DBUG_ASSERT(this->file && this->file->children_attached); + + /* Must call this with open table. */ + DBUG_ASSERT(this->file); + + /* A MERGE table with no children (empty union) cannot be detached. */ + if (!this->file->tables) + { + DBUG_PRINT("myrg", ("empty merge table union")); + goto end; + } + + if (this->children_l) + { + THD *thd= table->in_use; + + /* Clear TABLE references. */ + for (child_l= this->children_l; ; child_l= child_l->next_global) + { + /* + Do not DBUG_ASSERT(child_l->table); open_tables might be + incomplete. + + Clear the table reference. + */ + child_l->table= NULL; + /* Similarly, clear the ticket reference. */ + child_l->mdl_request.ticket= NULL; + + /* Break when this was the last child. */ + if (&child_l->next_global == this->children_last_l) + break; + } + /* + Remove children from the table list. This won't fail if called + twice. The list is terminated after removal. + + If the parent is LEX::query_tables_own_last and pre-locked tables + follow (tables used by stored functions or triggers), the children + are inserted behind the parent and before the pre-locked tables. But + we do not adjust LEX::query_tables_own_last. The pre-locked tables + could have chopped off the list by clearing + *LEX::query_tables_own_last. This did also chop off the children. If + we would copy the reference from *this->children_last_l in this + case, we would put the chopped off pre-locked tables back to the + list. So we refrain from copying it back, if the destination has + been set to NULL meanwhile. + */ + if (this->children_l->prev_global && *this->children_l->prev_global) + *this->children_l->prev_global= *this->children_last_l; + if (*this->children_last_l) + (*this->children_last_l)->prev_global= this->children_l->prev_global; + + /* + If table elements being removed are at the end of table list we + need to adjust LEX::query_tables_last member to point to the + new last element of the list. + */ + if (thd->lex->query_tables_last == this->children_last_l) + thd->lex->query_tables_last= this->children_l->prev_global; + + /* Terminate child list. So it cannot be tried to remove again. */ + *this->children_last_l= NULL; + this->children_l->prev_global= NULL; + + /* Forget about the children, we don't own their memory. */ + this->children_l= NULL; + this->children_last_l= NULL; + } + + if (!this->file->children_attached) + { + DBUG_PRINT("myrg", ("merge children are already detached")); + goto end; + } if (myrg_detach_children(this->file)) { /* purecov: begin inspected */ - DBUG_PRINT("error", ("my_errno %d", my_errno)); + print_error(my_errno, MYF(0)); DBUG_RETURN(my_errno ? my_errno : -1); /* purecov: end */ } + + end: DBUG_RETURN(0); } /** - @brief Close a MERGE parent table, not its children. - - @note The children are expected to be closed separately by the caller. + Close a MERGE parent table, but not its children. @return status @retval 0 OK @retval != 0 Error, my_errno gives reason + + @note + The children are expected to be closed separately by the caller. */ int ha_myisammrg::close(void) @@ -685,11 +932,12 @@ int ha_myisammrg::close(void) int rc; DBUG_ENTER("ha_myisammrg::close"); /* - Children must not be attached here. Unless the MERGE table has no - children or the handler instance has been cloned. In these cases - children_attached is always true. + There are cases where children are not explicitly detached before + close. detach_children() protects itself against double detach. */ - DBUG_ASSERT(!this->file->children_attached || !this->file->tables || this->is_cloned); + if (!is_cloned) + detach_children(); + rc= myrg_close(file); file= 0; DBUG_RETURN(rc); @@ -967,13 +1215,23 @@ int ha_myisammrg::info(uint flag) int ha_myisammrg::extra(enum ha_extra_function operation) { - if (operation == HA_EXTRA_ATTACH_CHILDREN) + if (operation == HA_EXTRA_ADD_CHILDREN_LIST) + { + int rc= add_children_list(); + return(rc); + } + else if (operation == HA_EXTRA_ATTACH_CHILDREN) { int rc= attach_children(); if (!rc) (void) extra(HA_EXTRA_NO_READCHECK); // Not needed in SQL return(rc); } + else if (operation == HA_EXTRA_IS_ATTACHED_CHILDREN) + { + /* For the upper layer pretend empty MERGE union is never attached. */ + return(file && file->tables && file->children_attached); + } else if (operation == HA_EXTRA_DETACH_CHILDREN) { /* @@ -994,6 +1252,7 @@ int ha_myisammrg::extra(enum ha_extra_function operation) int ha_myisammrg::reset(void) { + /* This is normally called with detached children. */ return myrg_reset(file); } @@ -1009,24 +1268,24 @@ int ha_myisammrg::extra_opt(enum ha_extra_function operation, ulong cache_size) int ha_myisammrg::external_lock(THD *thd, int lock_type) { - MYRG_TABLE *tmp; - DBUG_ASSERT(this->file->children_attached); - for (tmp= file->open_tables; tmp != file->end_table; tmp++) - tmp->table->in_use.data= thd; - return myrg_lock_database(file,lock_type); + /* + This can be called with no children attached. E.g. FLUSH TABLES + unlocks and re-locks tables under LOCK TABLES, but it does not open + them first. So they are detached all the time. But locking of the + children should work anyway because thd->open_tables is not changed + during FLUSH TABLES. + + If this handler instance has been cloned, we still must call + myrg_lock_database(). + */ + if (is_cloned) + return myrg_lock_database(file, lock_type); + return 0; } uint ha_myisammrg::lock_count(void) const { - /* - Return the real lock count even if the children are not attached. - This method is used for allocating memory. If we would return 0 - to another thread (e.g. doing FLUSH TABLE), and attach the children - before the other thread calls store_lock(), then we would return - more locks in store_lock() than we claimed by lock_count(). The - other tread would overrun its memory. - */ - return file->tables; + return 0; } @@ -1034,37 +1293,6 @@ THR_LOCK_DATA **ha_myisammrg::store_lock(THD *thd, THR_LOCK_DATA **to, enum thr_lock_type lock_type) { - MYRG_TABLE *open_table; - - /* - This method can be called while another thread is attaching the - children. If the processor reorders instructions or write to memory, - 'children_attached' could be set before 'open_tables' has all the - pointers to the children. Use of a mutex here and in - myrg_attach_children() forces consistent data. - */ - mysql_mutex_lock(&this->file->mutex); - - /* - When MERGE table is open, but not yet attached, other threads - could flush it, which means call mysql_lock_abort_for_thread() - on this threads TABLE. 'children_attached' is FALSE in this - situaton. Since the table is not locked, return no lock data. - */ - if (!this->file->children_attached) - goto end; /* purecov: tested */ - - for (open_table=file->open_tables ; - open_table != file->end_table ; - open_table++) - { - *(to++)= &open_table->table->lock; - if (lock_type != TL_IGNORE && open_table->table->lock.type == TL_UNLOCK) - open_table->table->lock.type=lock_type; - } - - end: - mysql_mutex_unlock(&this->file->mutex); return to; } @@ -1153,7 +1381,7 @@ int ha_myisammrg::create(const char *name, register TABLE *form, /* Allocate a table_names array in thread mem_root. */ if (!(table_names= (const char**) thd->alloc((create_info->merge_list.elements+1) * sizeof(char*)))) - DBUG_RETURN(HA_ERR_OUT_OF_MEM); + DBUG_RETURN(HA_ERR_OUT_OF_MEM); /* purecov: inspected */ /* Create child path names. */ for (pos= table_names; tables; tables= tables->next_local) @@ -1263,7 +1491,7 @@ bool ha_myisammrg::check_if_incompatible_data(HA_CREATE_INFO *info, int ha_myisammrg::check(THD* thd, HA_CHECK_OPT* check_opt) { - return HA_ADMIN_OK; + return this->file->children_attached ? HA_ADMIN_OK : HA_ADMIN_CORRUPT; } diff --git a/storage/myisammrg/ha_myisammrg.h b/storage/myisammrg/ha_myisammrg.h index 790aa15e90a..4ff24c69071 100644 --- a/storage/myisammrg/ha_myisammrg.h +++ b/storage/myisammrg/ha_myisammrg.h @@ -22,15 +22,62 @@ #include <myisammrg.h> +/** + Represents one name of a MERGE child. + + @todo: Add MYRG_SHARE and store chlidren names in the + share. +*/ + +class Mrg_child_def: public Sql_alloc +{ + /* Remembered MERGE child def version. See top comment in ha_myisammrg.cc */ + enum_table_ref_type m_child_table_ref_type; + ulong m_child_def_version; +public: + LEX_STRING db; + LEX_STRING name; + + /* Access MERGE child def version. See top comment in ha_myisammrg.cc */ + inline enum_table_ref_type get_child_table_ref_type() + { + return m_child_table_ref_type; + } + inline ulong get_child_def_version() + { + return m_child_def_version; + } + inline void set_child_def_version(enum_table_ref_type child_table_ref_type, + ulong version) + { + m_child_table_ref_type= child_table_ref_type; + m_child_def_version= version; + } + + Mrg_child_def(char *db_arg, size_t db_len_arg, + char *table_name_arg, size_t table_name_len_arg) + { + db.str= db_arg; + db.length= db_len_arg; + name.str= table_name_arg; + name.length= table_name_len_arg; + m_child_def_version= ~0UL; + m_child_table_ref_type= TABLE_REF_NULL; + } +}; + + class ha_myisammrg: public handler { MYRG_INFO *file; my_bool is_cloned; /* This instance has been cloned */ - public: - TABLE_LIST *next_child_attach; /* next child to attach */ +public: + MEM_ROOT children_mem_root; /* mem root for children list */ + List<Mrg_child_def> child_def_list; + TABLE_LIST *children_l; /* children list */ + TABLE_LIST **children_last_l; /* children list end */ uint test_if_locked; /* flags from ::open() */ - bool need_compat_check; /* if need compatibility check */ ha_myisammrg(handlerton *hton, TABLE_SHARE *table_arg); ~ha_myisammrg(); @@ -60,6 +107,7 @@ class ha_myisammrg: public handler { return ulonglong2double(stats.data_file_length) / IO_SIZE + file->tables; } int open(const char *name, int mode, uint test_if_locked); + int add_children_list(void); int attach_children(void); int detach_children(void); virtual handler *clone(MEM_ROOT *mem_root); diff --git a/storage/myisammrg/myrg_extra.c b/storage/myisammrg/myrg_extra.c index 3d14f6a56e6..0b9c138a188 100644 --- a/storage/myisammrg/myrg_extra.c +++ b/storage/myisammrg/myrg_extra.c @@ -75,12 +75,17 @@ int myrg_reset(MYRG_INFO *info) MYRG_TABLE *file; DBUG_ENTER("myrg_reset"); - if (!info->children_attached) - DBUG_RETURN(1); info->cache_in_use=0; info->current_table=0; info->last_used_table= info->open_tables; - + + /* + This is normally called with detached children. + Return OK as this is the normal case. + */ + if (!info->children_attached) + DBUG_RETURN(0); + for (file=info->open_tables ; file != info->end_table ; file++) { int error; diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 1b7fc76c300..01b994b11ee 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -18436,6 +18436,59 @@ static void test_bug36004() DBUG_VOID_RETURN; } +/** + Test that COM_REFRESH issues a implicit commit. +*/ + +static void test_wl4284_1() +{ + int rc; + MYSQL_ROW row; + MYSQL_RES *result; + + DBUG_ENTER("test_wl4284_1"); + myheader("test_wl4284_1"); + + /* set AUTOCOMMIT to OFF */ + rc= mysql_autocommit(mysql, FALSE); + myquery(rc); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS trans"); + myquery(rc); + + rc= mysql_query(mysql, "CREATE TABLE trans (a INT) ENGINE= InnoDB"); + myquery(rc); + + rc= mysql_query(mysql, "INSERT INTO trans VALUES(1)"); + myquery(rc); + + rc= mysql_refresh(mysql, REFRESH_GRANT | REFRESH_TABLES); + myquery(rc); + + rc= mysql_rollback(mysql); + myquery(rc); + + rc= mysql_query(mysql, "SELECT * FROM trans"); + myquery(rc); + + result= mysql_use_result(mysql); + mytest(result); + + row= mysql_fetch_row(result); + mytest(row); + + mysql_free_result(result); + + /* set AUTOCOMMIT to ON */ + rc= mysql_autocommit(mysql, TRUE); + myquery(rc); + + rc= mysql_query(mysql, "DROP TABLE trans"); + myquery(rc); + + DBUG_VOID_RETURN; +} + static void test_bug38486(void) { @@ -18584,6 +18637,8 @@ static void test_bug40365(void) DIE_UNLESS(tm[i].day == 0); } mysql_stmt_close(stmt); + rc= mysql_commit(mysql); + myquery(rc); DBUG_VOID_RETURN; } @@ -18881,6 +18936,115 @@ static void test_bug44495() } /* + Bug#49972: Crash in prepared statements. + + The following case lead to a server crash: + - Use binary protocol; + - Prepare a statement with OUT-parameter; + - Execute the statement; + - Cause re-prepare of the statement (change dependencies); + - Execute the statement again -- crash here. +*/ + +static void test_bug49972() +{ + int rc; + MYSQL_STMT *stmt; + + MYSQL_BIND in_param_bind; + MYSQL_BIND out_param_bind; + int int_data; + my_bool is_null; + + DBUG_ENTER("test_bug49972"); + myheader("test_49972"); + + rc= mysql_query(mysql, "DROP FUNCTION IF EXISTS f1"); + myquery(rc); + + rc= mysql_query(mysql, "DROP PROCEDURE IF EXISTS p1"); + myquery(rc); + + rc= mysql_query(mysql, "CREATE FUNCTION f1() RETURNS INT RETURN 1"); + myquery(rc); + + rc= mysql_query(mysql, "CREATE PROCEDURE p1(IN a INT, OUT b INT) SET b = a"); + myquery(rc); + + stmt= mysql_simple_prepare(mysql, "CALL p1((SELECT f1()), ?)"); + check_stmt(stmt); + + bzero((char *) &in_param_bind, sizeof (in_param_bind)); + + in_param_bind.buffer_type= MYSQL_TYPE_LONG; + in_param_bind.buffer= (char *) &int_data; + in_param_bind.length= 0; + in_param_bind.is_null= 0; + + rc= mysql_stmt_bind_param(stmt, &in_param_bind); + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + { + bzero(&out_param_bind, sizeof (out_param_bind)); + + out_param_bind.buffer_type= MYSQL_TYPE_LONG; + out_param_bind.is_null= &is_null; + out_param_bind.buffer= &int_data; + out_param_bind.buffer_length= sizeof (int_data); + + rc= mysql_stmt_bind_result(stmt, &out_param_bind); + check_execute(stmt, rc); + + rc= mysql_stmt_fetch(stmt); + rc= mysql_stmt_fetch(stmt); + DBUG_ASSERT(rc == MYSQL_NO_DATA); + + mysql_stmt_next_result(stmt); + mysql_stmt_fetch(stmt); + } + + rc= mysql_query(mysql, "DROP FUNCTION f1"); + myquery(rc); + + rc= mysql_query(mysql, "CREATE FUNCTION f1() RETURNS INT RETURN 1"); + myquery(rc); + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + + { + bzero(&out_param_bind, sizeof (out_param_bind)); + + out_param_bind.buffer_type= MYSQL_TYPE_LONG; + out_param_bind.is_null= &is_null; + out_param_bind.buffer= &int_data; + out_param_bind.buffer_length= sizeof (int_data); + + rc= mysql_stmt_bind_result(stmt, &out_param_bind); + check_execute(stmt, rc); + + rc= mysql_stmt_fetch(stmt); + rc= mysql_stmt_fetch(stmt); + DBUG_ASSERT(rc == MYSQL_NO_DATA); + + mysql_stmt_next_result(stmt); + mysql_stmt_fetch(stmt); + } + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP PROCEDURE p1"); + myquery(rc); + + rc= mysql_query(mysql, "DROP FUNCTION f1"); + myquery(rc); + + DBUG_VOID_RETURN; +} + +/* Read and parse arguments and MySQL options from my.cnf */ @@ -19197,6 +19361,7 @@ static struct my_tests_st my_tests[]= { { "test_wl4166_3", test_wl4166_3 }, { "test_wl4166_4", test_wl4166_4 }, { "test_bug36004", test_bug36004 }, + { "test_wl4284_1", test_wl4284_1 }, /* { "test_wl4435", test_wl4435 }, */ { "test_wl4435_2", test_wl4435_2 }, { "test_bug38486", test_bug38486 }, @@ -19208,6 +19373,7 @@ static struct my_tests_st my_tests[]= { #endif { "test_bug41078", test_bug41078 }, { "test_bug44495", test_bug44495 }, + /* XXX { "test_bug49972", test_bug49972 }, */ { 0, 0 } }; diff --git a/zlib/CMakeLists.txt b/zlib/CMakeLists.txt index 43235b631f6..20ba4bc51e0 100755 --- a/zlib/CMakeLists.txt +++ b/zlib/CMakeLists.txt @@ -20,8 +20,6 @@ INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/zlib) -SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG") - SET(ZLIB_SOURCES adler32.c compress.c crc32.c crc32.h deflate.c deflate.h gzio.c infback.c inffast.c inffast.h inffixed.h inflate.c inflate.h inftrees.c inftrees.h trees.c trees.h uncompr.c zconf.h zlib.h zutil.c zutil.h) |