diff options
author | Alexander Barkov <bar@mysql.com> | 2010-02-27 11:43:32 +0400 |
---|---|---|
committer | Alexander Barkov <bar@mysql.com> | 2010-02-27 11:43:32 +0400 |
commit | 06ca4ae33d514f3bfa3eaa8b943876110384a8a3 (patch) | |
tree | 79944b66b5194acfaac9f3ba322f1403d08f0080 /sql | |
parent | da2e47a5330e73472c4c224b2e41748a18e2946a (diff) | |
parent | 93287d61620ed94d49e23b65895cdd6748dde78d (diff) | |
download | mariadb-git-06ca4ae33d514f3bfa3eaa8b943876110384a8a3.tar.gz |
Mergine from mysql-next-me
Diffstat (limited to 'sql')
96 files changed, 11992 insertions, 7273 deletions
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 99b1a3dc206..8399b0c7219 100644..100755 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -12,36 +12,34 @@ # 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -INCLUDE("${PROJECT_SOURCE_DIR}/win/mysql_manifest.cmake") - -SET(CMAKE_CXX_FLAGS_DEBUG - "${CMAKE_CXX_FLAGS_DEBUG} -DUSE_SYMDIR /Zi") -SET(CMAKE_C_FLAGS_DEBUG - "${CMAKE_C_FLAGS_DEBUG} -DUSE_SYMDIR /Zi") -SET(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /MAP /MAPINFO:EXPORTS") - -INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/extra/yassl/include - ${CMAKE_SOURCE_DIR}/sql - ${CMAKE_SOURCE_DIR}/regex - ${CMAKE_SOURCE_DIR}/zlib + + +INCLUDE_DIRECTORIES( +${CMAKE_SOURCE_DIR}/include +${CMAKE_SOURCE_DIR}/sql +${CMAKE_SOURCE_DIR}/regex +${ZLIB_INCLUDE_DIR} +${SSL_INCLUDE_DIRS} +${CMAKE_BINARY_DIR}/sql ) -SET_SOURCE_FILES_PROPERTIES(${CMAKE_SOURCE_DIR}/sql/sql_yacc.h - ${CMAKE_SOURCE_DIR}/sql/sql_yacc.cc - ${CMAKE_SOURCE_DIR}/include/mysql_version.h - ${CMAKE_SOURCE_DIR}/sql/sql_builtin.cc - ${CMAKE_SOURCE_DIR}/sql/lex_hash.h - ${PROJECT_SOURCE_DIR}/include/mysqld_error.h - ${PROJECT_SOURCE_DIR}/include/mysqld_ername.h - ${PROJECT_SOURCE_DIR}/include/sql_state.h - PROPERTIES GENERATED 1) +SET(GEN_SOURCES +${CMAKE_BINARY_DIR}/sql/sql_yacc.h +${CMAKE_BINARY_DIR}/sql/sql_yacc.cc +${CMAKE_BINARY_DIR}/sql/sql_builtin.cc +${CMAKE_BINARY_DIR}/sql/lex_hash.h +) -ADD_DEFINITIONS(-DMYSQL_SERVER -D_CONSOLE -DHAVE_DLOPEN -DHAVE_EVENT_SCHEDULER) +SET_SOURCE_FILES_PROPERTIES(${GEN_SOURCES} PROPERTIES GENERATED 1) + +ADD_DEFINITIONS(-DMYSQL_SERVER -DHAVE_EVENT_SCHEDULER) +IF(SSL_DEFINES) + ADD_DEFINITIONS(${SSL_DEFINES}) +ENDIF() SET (SQL_SOURCE - ../sql-common/client.c derror.cc des_key_file.cc + ../sql-common/client.c derror.cc des_key_file.cc discover.cc ../libmysql/errmsg.c field.cc field_conv.cc filesort.cc gstream.cc ha_partition.cc @@ -54,7 +52,7 @@ SET (SQL_SOURCE log_event_old.cc rpl_record_old.cc message.h mf_iocache.cc my_decimal.cc ../sql-common/my_time.c mysqld.cc net_serv.cc keycaches.cc - nt_servc.cc nt_servc.h opt_range.cc opt_range.h opt_sum.cc + opt_range.cc opt_range.h opt_sum.cc ../sql-common/pack.c parse_file.cc password.c procedure.cc protocol.cc records.cc repl_failsafe.cc rpl_filter.cc set_var.cc slave.cc sp.cc sp_cache.cc sp_head.cc sp_pcontext.cc @@ -76,80 +74,212 @@ SET (SQL_SOURCE rpl_rli.cc rpl_mi.cc sql_servers.cc sql_audit.cc sql_connect.cc scheduler.cc sql_profile.cc event_parse_data.cc - sql_signal.cc rpl_handler.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 - ${PROJECT_SOURCE_DIR}/include/mysqld_ername.h - ${PROJECT_SOURCE_DIR}/include/sql_state.h - ${PROJECT_SOURCE_DIR}/include/mysql_version.h - ${PROJECT_SOURCE_DIR}/sql/sql_builtin.cc - ${PROJECT_SOURCE_DIR}/sql/lex_hash.h) -ADD_LIBRARY(sql ${SQL_SOURCE}) - -IF (NOT EXISTS cmake_dummy.cc) - FILE (WRITE cmake_dummy.cc "") -ENDIF (NOT EXISTS cmake_dummy.cc) -ADD_EXECUTABLE(mysqld cmake_dummy.cc) - -SET_TARGET_PROPERTIES(mysqld PROPERTIES OUTPUT_NAME mysqld${MYSQLD_EXE_SUFFIX}) -SET_TARGET_PROPERTIES(mysqld PROPERTIES ENABLE_EXPORTS TRUE) - -SET (MYSQLD_CORE_LIBS mysys zlib dbug strings yassl taocrypt vio regex sql) -TARGET_LINK_LIBRARIES(mysqld ${MYSQLD_CORE_LIBS} ${MYSQLD_STATIC_ENGINE_LIBS}) - - -IF(MSVC AND NOT WITHOUT_DYNAMIC_PLUGINS) - # Set module definition file. Also use non-incremental linker, - # incremental appears to crash from time to time,if used with /DEF option - SET_TARGET_PROPERTIES(mysqld PROPERTIES LINK_FLAGS "/DEF:mysqld.def /INCREMENTAL:NO") - - FOREACH (CORELIB ${MYSQLD_CORE_LIBS}) - GET_TARGET_PROPERTY(LOC ${CORELIB} LOCATION) - FILE(TO_NATIVE_PATH ${LOC} LOC) - SET (LIB_LOCATIONS ${LIB_LOCATIONS} ${LOC}) - ENDFOREACH (CORELIB ${MYSQLD_CORE_LIBS}) + sql_signal.cc rpl_handler.cc mdl.cc + transaction.cc sys_vars.cc + ${GEN_SOURCES} + ${MYSYS_LIBWRAP_SOURCE}) + +MYSQL_ADD_PLUGIN(partition ha_partition.cc STORAGE_ENGINE DEFAULT STATIC_ONLY +RECOMPILE_FOR_EMBEDDED) + +ADD_LIBRARY(sql STATIC ${SQL_SOURCE}) +DTRACE_INSTRUMENT(sql) +TARGET_LINK_LIBRARIES(sql ${MYSQLD_STATIC_PLUGIN_LIBS} + mysys dbug strings vio regex + ${LIBWRAP} ${LIBCRYPT} ${LIBDL} + ${SSL_LIBRARIES}) + - ADD_CUSTOM_COMMAND(TARGET mysqld PRE_LINK - COMMAND cscript ARGS //nologo ${PROJECT_SOURCE_DIR}/win/create_def_file.js - ${PLATFORM} ${LIB_LOCATIONS} > mysqld.def - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/sql) -ENDIF(MSVC AND NOT WITHOUT_DYNAMIC_PLUGINS) -ADD_DEPENDENCIES(sql GenError) +IF(WIN32) + SET(MYSQLD_SOURCE main.cc nt_servc.cc nt_servc.h) +ELSE() + SET(MYSQLD_SOURCE main.cc ${DTRACE_PROBES_ALL}) +ENDIF() -# Sql Parser custom command -ADD_CUSTOM_COMMAND( - OUTPUT ${PROJECT_SOURCE_DIR}/sql/sql_yacc.h - ${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc - COMMAND bison ARGS -y -p MYSQL --defines=sql_yacc.h - --output=sql_yacc.cc sql_yacc.yy - DEPENDS ${PROJECT_SOURCE_DIR}/sql/sql_yacc.yy) +MYSQL_ADD_EXECUTABLE(mysqld ${MYSQLD_SOURCE} DESTINATION ${INSTALL_SBINDIR}) + +IF(NOT WITHOUT_DYNAMIC_PLUGINS) + SET_TARGET_PROPERTIES(mysqld PROPERTIES ENABLE_EXPORTS TRUE) + GET_TARGET_PROPERTY(mysqld_link_flags mysqld LINK_FLAGS) + IF(NOT mysqld_link_flags) + SET(mysqld_link_flags) + ENDIF() + IF (MINGW OR CYGWIN) + SET_TARGET_PROPERTIES(mysqld PROPERTIES LINK_FLAGS "${mysqld_link_flags} -Wl,--export-all-symbols") + ENDIF() + IF(MSVC) + # Set module definition file. Also use non-incremental linker, + # incremental appears to crash from time to time,if used with /DEF option + SET_TARGET_PROPERTIES(mysqld PROPERTIES LINK_FLAGS "${mysqld_link_flags} /DEF:mysqld.def /INCREMENTAL:NO") + FOREACH (CORELIB sql mysys dbug strings) + GET_TARGET_PROPERTY(LOC ${CORELIB} LOCATION) + FILE(TO_NATIVE_PATH ${LOC} LOC) + SET (LIB_LOCATIONS ${LIB_LOCATIONS} ${LOC}) + ENDFOREACH (CORELIB ${MYSQLD_CORE_LIBS}) + SET(_PLATFORM x86) + IF(CMAKE_SIZEOF_VOID_P EQUAL 8) + SET(_PLATFORM x64) + ENDIF() + ADD_CUSTOM_COMMAND(TARGET mysqld PRE_LINK + COMMAND echo ${_PLATFORM} && cscript ARGS //nologo ${PROJECT_SOURCE_DIR}/win/create_def_file.js + ${_PLATFORM} ${LIB_LOCATIONS} > mysqld.def + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + ADD_DEPENDENCIES(sql GenError) + ENDIF() +ENDIF() + +SET_TARGET_PROPERTIES(mysqld PROPERTIES ENABLE_EXPORTS TRUE) +TARGET_LINK_LIBRARIES(mysqld sql) + +# Provide plugins with minimal set of libraries +SET(INTERFACE_LIBS ${LIBRT}) +IF(INTERFACE_LIBS) + SET_TARGET_PROPERTIES(mysqld PROPERTIES LINK_INTERFACE_LIBRARIES + "${INTERFACE_LIBS}") +ENDIF() + +# On Solaris, some extra effort is required in order to get dtrace probes +# from static libraries +DTRACE_INSTRUMENT_STATIC_LIBS(mysqld + "sql;mysys;${MYSQLD_STATIC_PLUGIN_LIBS}") + + +SET(WITH_MYSQLD_LDFLAGS "" CACHE STRING "Additional linker flags for mysqld") +MARK_AS_ADVANCED(WITH_MYSQLD_LDFLAGS) +IF(WITH_MYSQLD_LDFLAGS) + GET_TARGET_PROPERTY(mysqld LINK_FLAGS MYSQLD_LINK_FLAGS) + IF(NOT MYSQLD_LINK_FLAGS) + SET(MYSQLD_LINK_FLAGS) + ENDIF() + SET_TARGET_PROPERTIES(mysqld PROPERTIES LINK_FLAGS + "${MYSQLD_LINK_FLAGS} ${WITH_MYSQLD_LDFLAGS}") +ENDIF() +INSTALL_DEBUG_TARGET(mysqld DESTINATION ${INSTALL_SBINDIR} RENAME mysqld-debug) + +# Handle out-of-source build from source package with possibly broken +# bison. Copy bison output to from source to build directory, if not already +# there +IF (NOT ${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_BINARY_DIR}) + IF(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/sql_yacc.cc) + IF(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.cc) + CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/sql_yacc.cc + ${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.cc COPYONLY) + CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/sql_yacc.h + ${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.h COPYONLY) + ENDIF() + ENDIF() +ENDIF() + + +INCLUDE(${CMAKE_SOURCE_DIR}/cmake/bison.cmake) +RUN_BISON( + ${CMAKE_CURRENT_SOURCE_DIR}/sql_yacc.yy + ${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.cc + ${CMAKE_CURRENT_BINARY_DIR}/sql_yacc.h +) # Gen_lex_hash ADD_EXECUTABLE(gen_lex_hash gen_lex_hash.cc) -TARGET_LINK_LIBRARIES(gen_lex_hash dbug mysqlclient) -GET_TARGET_PROPERTY(GEN_LEX_HASH_EXE gen_lex_hash LOCATION) +TARGET_LINK_LIBRARIES(gen_lex_hash mysys) + ADD_CUSTOM_COMMAND( - OUTPUT ${PROJECT_SOURCE_DIR}/sql/lex_hash.h - COMMAND ${GEN_LEX_HASH_EXE} ARGS > lex_hash.h - DEPENDS ${GEN_LEX_HASH_EXE}) - -ADD_CUSTOM_TARGET( - GenServerSource ALL - DEPENDS ${PROJECT_SOURCE_DIR}/sql/sql_yacc.h - ${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc - ${PROJECT_SOURCE_DIR}/sql/message.h - ${PROJECT_SOURCE_DIR}/sql/message.rc - ${PROJECT_SOURCE_DIR}/sql/lex_hash.h) - -ADD_DEPENDENCIES(mysqld GenServerSource) - -# Remove the auto-generated files as part of 'Clean Solution' -SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES - "lex_hash.h;sql_yacc.h;sql_yacc.cc;mysqld.def") - -ADD_LIBRARY(udf_example MODULE udf_example.c udf_example.def) -ADD_DEPENDENCIES(udf_example strings GenError) -TARGET_LINK_LIBRARIES(udf_example strings) + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/lex_hash.h + COMMAND gen_lex_hash ARGS > lex_hash.h.tmp && + ${CMAKE_COMMAND} -E copy_if_different lex_hash.h.tmp lex_hash.h + COMMAND ${CMAKE_COMMAND} -E remove -f lex_hash.h.tmp + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/gen_lex_hash.cc) + +MYSQL_ADD_EXECUTABLE(mysql_tzinfo_to_sql tztime.cc) +SET_TARGET_PROPERTIES(mysql_tzinfo_to_sql PROPERTIES COMPILE_FLAGS "-DTZINFO2SQL") +TARGET_LINK_LIBRARIES(mysql_tzinfo_to_sql ${MYSQLD_STATIC_PLUGIN_LIBS} + mysys dbug strings vio regex + ${LIBWRAP} ${LIBCRYPT} ${LIBDL} + ${SSL_LIBRARIES}) + +ADD_CUSTOM_TARGET( + GenServerSource + DEPENDS ${GEN_SOURCES} +) + +#Need this only for embedded +SET_TARGET_PROPERTIES(GenServerSource PROPERTIES EXCLUDE_FROM_ALL TRUE) + +IF(WIN32 OR HAVE_DLOPEN AND NOT DISABLE_SHARED) + ADD_LIBRARY(udf_example MODULE udf_example.c) + SET_TARGET_PROPERTIES(udf_example PROPERTIES PREFIX "") + # udf_example depends on strings + IF(WIN32) + IF(MSVC) + SET_TARGET_PROPERTIES(udf_example PROPERTIES LINK_FLAGS "/DEF:${CMAKE_CURRENT_SOURCE_DIR}/udf_example.def") + ENDIF() + TARGET_LINK_LIBRARIES(udf_example strings) + ELSE() + # udf_example is using safemutex exported by mysqld + TARGET_LINK_LIBRARIES(udf_example mysqld) + ENDIF() +ENDIF() + +FOREACH(tool glibtoolize libtoolize aclocal autoconf autoheader automake gtar + tar bzr) + STRING(TOUPPER ${tool} TOOL) + FIND_PROGRAM(${TOOL}_EXECUTABLE ${tool} DOC "path to the executable") + MARK_AS_ADVANCED(${TOOL}_EXECUTABLE) +ENDFOREACH() + +CONFIGURE_FILE( + ${CMAKE_SOURCE_DIR}/cmake/make_dist.cmake.in ${CMAKE_BINARY_DIR}/make_dist.cmake @ONLY) + +ADD_CUSTOM_TARGET(dist + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/make_dist.cmake + DEPENDS ${CMAKE_BINARY_DIR}/sql/sql_yacc.cc ${CMAKE_BINARY_DIR}/sql/sql_yacc.h + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} +) + + + + +IF(INSTALL_LAYOUT STREQUAL "STANDALONE") + +# We need to create empty directories (data/test) the installation. +# This does not work with current CPack due to http://www.cmake.org/Bug/view.php?id=8767 +# Avoid completely empty directories and install dummy file instead. +SET(DUMMY_FILE ${CMAKE_CURRENT_BINARY_DIR}/.empty ) +FILE(WRITE ${DUMMY_FILE} "") +INSTALL(FILES ${DUMMY_FILE} DESTINATION data/test) + +# Install initial database on windows +IF(NOT CMAKE_CROSSCOMPILING) + GET_TARGET_PROPERTY(MYSQLD_EXECUTABLE mysqld LOCATION) +ENDIF() +IF(WIN32 AND MYSQLD_EXECUTABLE) + CONFIGURE_FILE( + ${CMAKE_SOURCE_DIR}/cmake/create_initial_db.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/create_initial_db.cmake + @ONLY + ) + + IF(MSVC_IDE OR CMAKE_GENERATOR MATCHES "Xcode") + SET (CONFIG_PARAM -DCONFIG=${CMAKE_CFG_INTDIR}) + ENDIF() + MAKE_DIRECTORY(${CMAKE_CURRENT_BINARY_DIR}/data) + ADD_CUSTOM_COMMAND( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/data/mysql/user.frm + COMMAND ${CMAKE_COMMAND} + ${CONFIG_PARAM} -P ${CMAKE_CURRENT_BINARY_DIR}/create_initial_db.cmake + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/data + DEPENDS mysqld + ) + ADD_CUSTOM_TARGET(initial_database + ALL + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/data/mysql/user.frm + ) + INSTALL(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/data/mysql DESTINATION data) +ELSE() + # Not windows or cross compiling, just install an empty directory + INSTALL(FILES ${DUMMY_FILE} DESTINATION data/mysql) +ENDIF() +ENDIF() + diff --git a/sql/Makefile.am b/sql/Makefile.am index e22de4931db..9a60cbcbf66 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -113,7 +113,8 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \ sql_partition.h partition_info.h partition_element.h \ sql_audit.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 \ @@ -124,7 +125,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ net_serv.cc protocol.cc sql_state.c \ lock.cc my_lock.c \ sql_string.cc sql_manager.cc sql_map.cc \ - mysqld.cc password.c hash_filo.cc hostname.cc \ + main.cc mysqld.cc password.c hash_filo.cc hostname.cc \ sql_connect.cc scheduler.cc sql_parse.cc \ keycaches.cc set_var.cc sql_yacc.yy sys_vars.cc \ sql_base.cc table.cc sql_select.cc sql_insert.cc \ @@ -158,9 +159,8 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ event_queue.cc event_db_repository.cc events.cc \ sql_plugin.cc sql_binlog.cc \ sql_builtin.cc sql_tablespace.cc partition_info.cc \ - sql_servers.cc sql_audit.cc \ - event_parse_data.cc sql_signal.cc \ - rpl_handler.cc + sql_servers.cc event_parse_data.cc sql_signal.cc \ + rpl_handler.cc mdl.cc transaction.cc sql_audit.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 fd2c5a556a8..e9bcecc459d 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)) { @@ -1053,8 +1053,8 @@ update_timing_fields_for_event(THD *thd, Turn off row binlogging of event timing updates. These are not used for RBR of events replicated to the slave. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); DBUG_ASSERT(thd->security_ctx->master_access & SUPER_ACL); @@ -1097,7 +1097,9 @@ end: if (table) close_thread_tables(thd); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(test(ret)); } @@ -1129,7 +1131,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)) { @@ -1144,7 +1146,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)) { @@ -1164,7 +1166,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 73f3427607d..d8bf549321e 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -297,15 +297,6 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, bool save_binlog_row_based; 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); @@ -335,8 +326,8 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, Turn off row binlogging of this statement and use statement-based so that all supporting tables are updated for CREATE EVENT command. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); mysql_mutex_lock(&LOCK_event_metadata); @@ -377,7 +368,9 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, sql_print_error("Event Error: An error occurred while creating query string, " "before writing it into binary log."); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(TRUE); } /* If the definer is not set or set to CURRENT_USER, the value of CURRENT_USER @@ -387,7 +380,9 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, } mysql_mutex_unlock(&LOCK_event_metadata); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(ret); } @@ -422,13 +417,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); @@ -471,8 +459,8 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, Turn off row binlogging of this statement and use statement-based so that all supporting tables are updated for UPDATE EVENT command. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); mysql_mutex_lock(&LOCK_event_metadata); @@ -509,7 +497,9 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, } mysql_mutex_unlock(&LOCK_event_metadata); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(ret); } @@ -546,20 +536,6 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists) bool save_binlog_row_based; 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); @@ -570,8 +546,8 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists) Turn off row binlogging of this statement and use statement-based so that all supporting tables are updated for DROP EVENT command. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); mysql_mutex_lock(&LOCK_event_metadata); /* On error conditions my_error() is called so no need to handle here */ @@ -585,7 +561,9 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists) } mysql_mutex_unlock(&LOCK_event_metadata); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(ret); } @@ -711,7 +689,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; @@ -766,7 +744,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 08ffdbf5987..fb985ab9930 100644 --- a/sql/field.h +++ b/sql/field.h @@ -472,7 +472,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 63716e8960e..b0276e14ebf 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++)) { @@ -8242,17 +8252,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); @@ -8317,12 +8325,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); @@ -8433,7 +8439,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 eb46958398a..ed2bdfbbe71 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 @@ -926,7 +932,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); @@ -1740,7 +1746,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))) @@ -1846,7 +1852,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)); @@ -1967,7 +1973,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) @@ -2084,7 +2090,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); @@ -2241,7 +2247,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; @@ -2328,22 +2334,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"); @@ -2377,28 +2382,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; } /* @@ -2423,10 +2411,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; @@ -3903,9 +3887,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 2b8806b824c..3289b529edf 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -2635,7 +2635,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; @@ -2646,7 +2646,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)); @@ -2655,7 +2655,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 @@ -5294,34 +5294,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 @@ -5360,7 +5361,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. @@ -5379,7 +5380,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 @@ -5510,7 +5511,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 @@ -5541,7 +5542,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 @@ -5576,19 +5577,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: @@ -5596,6 +5597,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) @@ -5688,13 +5697,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 e6b7472786c..b3a347612f3 100644 --- a/sql/ha_partition.h +++ b/sql/ha_partition.h @@ -945,7 +945,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() @@ -958,7 +958,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 d9bed4ec557..69ac4e72555 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. @@ -1151,7 +1152,7 @@ int ha_commit_trans(THD *thd, bool all) uint rw_ha_count; bool rw_trans; - DBUG_EXECUTE_IF("crash_commit_before", abort();); + DBUG_EXECUTE_IF("crash_commit_before", DBUG_ABORT();); /* Close all cursors that can not survive COMMIT */ if (is_real_trans) /* not a statement commit */ @@ -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); @@ -1203,7 +1204,7 @@ int ha_commit_trans(THD *thd, bool all) } status_var_increment(thd->status_var.ha_prepare_count); } - DBUG_EXECUTE_IF("crash_commit_after_prepare", abort();); + DBUG_EXECUTE_IF("crash_commit_after_prepare", DBUG_ABORT();); if (error || (is_real_trans && xid && (error= !(cookie= tc_log->log_xid(thd, xid))))) { @@ -1211,17 +1212,17 @@ int ha_commit_trans(THD *thd, bool all) error= 1; goto end; } - DBUG_EXECUTE_IF("crash_commit_after_log", abort();); + DBUG_EXECUTE_IF("crash_commit_after_log", DBUG_ABORT();); } error=ha_commit_one_phase(thd, all) ? (cookie ? 2 : 1) : 0; - DBUG_EXECUTE_IF("crash_commit_before_unlog", abort();); + DBUG_EXECUTE_IF("crash_commit_before_unlog", DBUG_ABORT();); if (cookie) tc_log->unlog(cookie, xid); - DBUG_EXECUTE_IF("crash_commit_after", abort();); + DBUG_EXECUTE_IF("crash_commit_after", DBUG_ABORT();); 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 f4064b51de9..1734e5727dc 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -1011,6 +1011,7 @@ typedef struct st_key_create_information enum ha_key_alg algorithm; ulong block_size; LEX_STRING parser_name; + LEX_STRING comment; } KEY_CREATE_INFO; @@ -2055,10 +2056,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); @@ -2134,13 +2131,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/item.cc b/sql/item.cc index f7643db57cd..9705b5f1bce 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -911,7 +911,7 @@ Item *Item_param::safe_charset_converter(CHARSET_INFO *tocs) cnvitem->max_length= cnvitem->str_value.numchars() * tocs->mbmaxlen; return cnvitem; } - return NULL; + return Item::safe_charset_converter(tocs); } @@ -1493,7 +1493,12 @@ left_is_superset(DTCollation *left, DTCollation *right) if (left->collation->state & MY_CS_UNICODE && (left->derivation < right->derivation || (left->derivation == right->derivation && - !(right->collation->state & MY_CS_UNICODE)))) + (!(right->collation->state & MY_CS_UNICODE) || + /* The code below makes 4-byte utf8 a superset over 3-byte utf8 */ + (left->collation->state & MY_CS_UNICODE_SUPPLEMENT && + !(right->collation->state & MY_CS_UNICODE_SUPPLEMENT) && + left->collation->mbmaxlen > right->collation->mbmaxlen && + left->collation->mbminlen == right->collation->mbminlen))))) return TRUE; /* Allow convert from ASCII */ if (right->repertoire == MY_REPERTOIRE_ASCII && @@ -1757,7 +1762,7 @@ bool agg_item_set_converter(DTCollation &coll, const char *fname, { Item* conv; uint32 dummy_offset; - if (!String::needs_conversion(0, (*arg)->collation.collation, + if (!String::needs_conversion(1, (*arg)->collation.collation, coll.collation, &dummy_offset)) continue; diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index db4a55bbcd0..08383325580 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -2866,9 +2866,7 @@ bool Item_func_case::fix_fields(THD *thd, Item **ref) buff should match stack usage from Item_func_case::val_int() -> Item_func_case::find_item() */ -#ifndef EMBEDDED_LIBRARY uchar buff[MAX_FIELD_WIDTH*2+sizeof(String)*2+sizeof(String*)*2+sizeof(double)*2+sizeof(longlong)*2]; -#endif bool res= Item_func::fix_fields(thd, ref); /* Call check_stack_overrun after fix_fields to be sure that stack variable @@ -4085,9 +4083,7 @@ Item_cond::fix_fields(THD *thd, Item **ref) DBUG_ASSERT(fixed == 0); List_iterator<Item> li(list); Item *item; -#ifndef EMBEDDED_LIBRARY uchar buff[sizeof(char*)]; // Max local vars in function -#endif not_null_tables_cache= used_tables_cache= 0; const_item_cache= 1; /* diff --git a/sql/item_func.cc b/sql/item_func.cc index 0a572a8f3e3..ca8f5d00bb1 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -151,9 +151,7 @@ Item_func::fix_fields(THD *thd, Item **ref) { DBUG_ASSERT(fixed == 0); Item **arg,**arg_end; -#ifndef EMBEDDED_LIBRARY // Avoid compiler warning uchar buff[STACK_BUFF_ALLOC]; // Max argument in function -#endif used_tables_cache= not_null_tables_cache= 0; const_item_cache=1; @@ -2848,9 +2846,7 @@ bool udf_handler::fix_fields(THD *thd, Item_result_field *func, uint arg_count, Item **arguments) { -#ifndef EMBEDDED_LIBRARY // Avoid compiler warning uchar buff[STACK_BUFF_ALLOC]; // Max argument in function -#endif DBUG_ENTER("Item_udf_func::fix_fields"); if (check_stack_overrun(thd, STACK_MIN_SIZE, buff)) diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 7bb2d358abf..dab29bb58c4 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -2404,17 +2404,27 @@ String *Item_func_char::val_str(String *str) int32 num=(int32) args[i]->val_int(); if (!args[i]->null_value) { - char char_num= (char) num; - if (num&0xFF000000L) { - str->append((char)(num>>24)); - goto b2; - } else if (num&0xFF0000L) { - b2: str->append((char)(num>>16)); - goto b1; - } else if (num&0xFF00L) { - b1: str->append((char)(num>>8)); + char tmp[4]; + if (num & 0xFF000000L) + { + mi_int4store(tmp, num); + str->append(tmp, 4, &my_charset_bin); + } + else if (num & 0xFF0000L) + { + mi_int3store(tmp, num); + str->append(tmp, 3, &my_charset_bin); + } + else if (num & 0xFF00L) + { + mi_int2store(tmp, num); + str->append(tmp, 2, &my_charset_bin); + } + else + { + tmp[0]= (char) num; + str->append(tmp, 1, &my_charset_bin); } - str->append(&char_num, 1); } } str->realloc(str->length()); // Add end 0 (for Purify) diff --git a/sql/lex.h b/sql/lex.h index 7961339c4f3..fbedddc6941 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -226,7 +226,6 @@ static SYMBOL symbols[] = { { "FORCE", SYM(FORCE_SYM)}, { "FOREIGN", SYM(FOREIGN)}, { "FOUND", SYM(FOUND_SYM)}, - { "FRAC_SECOND", SYM(FRAC_SECOND_SYM)}, { "FROM", SYM(FROM)}, { "FULL", SYM(FULL)}, { "FULLTEXT", SYM(FULLTEXT_SYM)}, @@ -418,7 +417,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)}, @@ -517,7 +516,6 @@ static SYMBOL symbols[] = { { "SQL_NO_CACHE", SYM(SQL_NO_CACHE_SYM)}, { "SQL_SMALL_RESULT", SYM(SQL_SMALL_RESULT)}, { "SQL_THREAD", SYM(SQL_THREAD)}, - { "SQL_TSI_FRAC_SECOND", SYM(FRAC_SECOND_SYM)}, { "SQL_TSI_SECOND", SYM(SECOND_SYM)}, { "SQL_TSI_MINUTE", SYM(MINUTE_SYM)}, { "SQL_TSI_HOUR", SYM(HOUR_SYM)}, diff --git a/sql/lock.cc b/sql/lock.cc index 6106e20678a..fa58e43f6bb 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 }; @@ -177,6 +153,25 @@ int mysql_lock_tables_check(THD *thd, TABLE **tables, uint count, uint flags) { system_count++; } + + /* + If we are going to lock a non-temporary table we must own metadata + lock of appropriate type on it (I.e. for table to be locked for + write we must own metadata lock of MDL_SHARED_WRITE or stronger + type. For table to be locked for read we must own metadata lock + of MDL_SHARED_READ or stronger type). + The only exception are HANDLER statements which are allowed to + lock table for read while having only MDL_SHARED lock on it. + */ + DBUG_ASSERT(t->s->tmp_table || + thd->mdl_context.is_lock_owner(MDL_key::TABLE, + t->s->db.str, t->s->table_name.str, + t->reginfo.lock_type >= TL_WRITE_ALLOW_WRITE ? + MDL_SHARED_WRITE : MDL_SHARED_READ) || + (t->open_by_handler && + thd->mdl_context.is_lock_owner(MDL_key::TABLE, + t->s->db.str, t->s->table_name.str, + MDL_SHARED))); } /* @@ -193,9 +188,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 +241,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 +290,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 +329,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)); @@ -321,7 +336,8 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, rc= thr_lock_errno_to_mysql[(int) thr_multi_lock(sql_lock->locks + sql_lock->lock_count, sql_lock->lock_count, - thd->lock_id)]; + thd->lock_id, + thd->variables.lock_wait_timeout)]; if (rc > 1) /* a timeout or a deadlock */ { if (sql_lock->table_count) @@ -330,24 +346,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 +379,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 +527,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 +549,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 +702,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 +757,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 +952,117 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, *****************************************************************************/ /** - Lock and wait for the named lock. - - @param thd Thread handler - @param table_list Lock first table in this list - - - @note - Works together with global read lock. - - @retval - 0 ok - @retval - 1 error -*/ - -int lock_and_wait_for_table_name(THD *thd, TABLE_LIST *table_list) -{ - int lock_retcode; - int error= -1; - DBUG_ENTER("lock_and_wait_for_table_name"); - - if (wait_if_global_read_lock(thd, 0, 1)) - DBUG_RETURN(1); - 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 + Obtain exclusive metadata locks on the list of tables. - @note - One must have a lock on LOCK_open! + @param thd Thread handle + @param table_list List of tables to lock - @warning - If you are going to update the table, you should use - lock_and_wait_for_table_name instead of this function as this works - together with 'FLUSH TABLES WITH READ LOCK' + @note This function assumes that no metadata locks were acquired + before calling it. Also it cannot be called while holding + LOCK_open mutex. Both these invariants are enforced by asserts + in MDL_context::acquire_locks(). - @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; -} - + mdl_requests.push_front(&global_request); -/** - Unlock all tables in list with a name lock. - - @param thd Thread handle. - @param table_list Names of tables to lock. - - @note - This function needs to be protected by LOCK_open. If we're - under LOCK TABLES, this function does not work as advertised. Namely, - it does not exclude other threads from using this table and does not - put an exclusive name lock on this table into the table cache. - - @see lock_table_names - @see unlock_table_names + if (thd->mdl_context.acquire_locks(&mdl_requests, + thd->variables.lock_wait_timeout)) + return 1; - @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. - - @param[in] thd The current thread handler - @param[in] table_list Table container containing the single table to be - tested + Release all metadata locks previously obtained by lock_table_names(). - @note Needs to be protected by LOCK_open mutex. + @param thd Thread handle. - @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. - - @param - thd Thread handle - @param - table_list Names of tables to unlock - @param - last_table Don't unlock any tables after this one. - (default 0, which will unlock all tables) - - @note - One must have a lock on LOCK_open when calling this. + global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); + mdl_request.init(mdl_type, db, name, MDL_EXCLUSIVE); - @note - This function will broadcast refresh signals to inform other threads - that the name locks are removed. + mdl_requests.push_front(&mdl_request); + mdl_requests.push_front(&global_request); - @retval - 0 ok - @retval - 1 Fatal error (end of memory ?) -*/ + if (thd->mdl_context.acquire_locks(&mdl_requests, + thd->variables.lock_wait_timeout)) + 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 +1178,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. + + 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 lock_global_read_lock(THD *thd) +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 +1239,44 @@ 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, + thd->variables.lock_wait_timeout)) + { + /* 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 +1290,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 +1308,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 +1324,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 +1380,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 +1406,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 +1424,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 +1462,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 +1486,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 +1503,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 5a583e9e134..ce9d75089d1 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -454,7 +454,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; @@ -467,14 +467,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 @@ -619,7 +615,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"); @@ -631,14 +627,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; @@ -775,29 +767,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) @@ -1593,7 +1581,7 @@ binlog_truncate_trx_cache(THD *thd, binlog_cache_mngr *cache_mngr, bool all) transaction cache to remove the statement. */ else - cache_mngr->trx_cache.restore_prev_position(); + cache_mngr->trx_cache.restore_prev_position(); /* We need to step the table map version on a rollback to ensure that a new @@ -2826,7 +2814,7 @@ bool MYSQL_BIN_LOG::open(const char *log_name, sql_print_error("MSYQL_BIN_LOG::open failed to sync the index file."); DBUG_RETURN(1); } - DBUG_EXECUTE_IF("crash_create_non_critical_before_update_index", abort();); + DBUG_EXECUTE_IF("crash_create_non_critical_before_update_index", DBUG_ABORT();); #endif write_error= 0; @@ -2923,7 +2911,7 @@ bool MYSQL_BIN_LOG::open(const char *log_name, if (write_file_name_to_index_file) { #ifdef HAVE_REPLICATION - DBUG_EXECUTE_IF("crash_create_critical_before_update_index", abort();); + DBUG_EXECUTE_IF("crash_create_critical_before_update_index", DBUG_ABORT();); #endif DBUG_ASSERT(my_b_inited(&index_file) != 0); @@ -2942,7 +2930,7 @@ bool MYSQL_BIN_LOG::open(const char *log_name, goto err; #ifdef HAVE_REPLICATION - DBUG_EXECUTE_IF("crash_create_after_update_index", abort();); + DBUG_EXECUTE_IF("crash_create_after_update_index", DBUG_ABORT();); #endif } } @@ -3404,7 +3392,7 @@ int MYSQL_BIN_LOG::purge_first_log(Relay_log_info* rli, bool included) /* Store where we are in the new file for the execution thread */ flush_relay_log_info(rli); - DBUG_EXECUTE_IF("crash_before_purge_logs", abort();); + DBUG_EXECUTE_IF("crash_before_purge_logs", DBUG_ABORT();); mysql_mutex_lock(&rli->log_space_lock); rli->relay_log.purge_logs(to_purge_if_included, included, @@ -3532,7 +3520,7 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log, break; } - DBUG_EXECUTE_IF("crash_purge_before_update_index", abort();); + DBUG_EXECUTE_IF("crash_purge_before_update_index", DBUG_ABORT();); if ((error= sync_purge_index_file())) { @@ -3547,7 +3535,7 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log, goto err; } - DBUG_EXECUTE_IF("crash_purge_critical_after_update_index", abort();); + DBUG_EXECUTE_IF("crash_purge_critical_after_update_index", DBUG_ABORT();); err: /* Read each entry from purge_index_file and delete the file. */ @@ -3557,7 +3545,7 @@ err: " that would be purged."); close_purge_index_file(); - DBUG_EXECUTE_IF("crash_purge_non_critical_after_update_index", abort();); + DBUG_EXECUTE_IF("crash_purge_non_critical_after_update_index", DBUG_ABORT();); if (need_mutex) mysql_mutex_unlock(&LOCK_index); @@ -4324,7 +4312,7 @@ THD::binlog_start_trans_and_stmt() cache_mngr->trx_cache.get_prev_position() == 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); /* @@ -4591,7 +4579,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, event_info->use_trans_cache())) DBUG_RETURN(error); @@ -4680,12 +4668,19 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info) { BINLOG_USER_VAR_EVENT *user_var_event; get_dynamic(&thd->user_var_events,(uchar*) &user_var_event, i); + + /* setting flags for user var log event */ + uchar flags= User_var_log_event::UNDEF_F; + if (user_var_event->user_var_event->unsigned_flag) + flags|= User_var_log_event::UNSIGNED_F; + User_var_log_event e(thd, user_var_event->user_var_event->name.str, user_var_event->user_var_event->name.length, user_var_event->value, user_var_event->length, user_var_event->type, - user_var_event->charset_number); + user_var_event->charset_number, + flags); if (e.write(file)) goto err; } @@ -5096,7 +5091,7 @@ bool MYSQL_BIN_LOG::write(THD *thd, IO_CACHE *cache, Log_event *commit_event, DBUG_PRINT("info", ("error writing binlog cache: %d", write_error)); DBUG_PRINT("info", ("crashing before writing xid")); - abort(); + DBUG_ABORT(); }); if ((write_error= write_cache(cache, false, false))) @@ -5111,7 +5106,7 @@ bool MYSQL_BIN_LOG::write(THD *thd, IO_CACHE *cache, Log_event *commit_event, bool synced= 0; if (flush_and_sync(&synced)) goto err; - DBUG_EXECUTE_IF("half_binlogged_transaction", abort();); + DBUG_EXECUTE_IF("half_binlogged_transaction", DBUG_ABORT();); if (cache->error) // Error on read { sql_print_error(ER(ER_ERROR_ON_READ), cache->file_name, errno); diff --git a/sql/log_event.cc b/sql/log_event.cc index be167451cb3..b0deee62fd7 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -30,6 +30,7 @@ #include "rpl_mi.h" #include "rpl_filter.h" #include "rpl_record.h" +#include "transaction.h" #include <my_dir.h> #endif /* MYSQL_CLIENT */ @@ -3157,7 +3158,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); } /* @@ -3360,7 +3361,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 @@ -3369,7 +3370,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. */ @@ -4717,10 +4719,10 @@ int Load_log_event::do_apply_event(NET* net, Relay_log_info const *rli, thd->warning_info->opt_clear_warning_info(thd->query_id); TABLE_LIST tables; - bzero((char*) &tables,sizeof(tables)); - tables.db= thd->strmake(thd->db, thd->db_length); - tables.alias = tables.table_name = (char*) table_name; - tables.lock_type = TL_WRITE; + tables.init_one_table(thd->strmake(thd->db, thd->db_length), + thd->db_length, + table_name, strlen(table_name), + table_name, TL_WRITE); tables.updating= 1; // the table will be opened in mysql_load @@ -5443,10 +5445,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 @@ -5495,7 +5503,9 @@ void User_var_log_event::pack_info(Protocol* protocol) case INT_RESULT: if (!(buf= (char*) my_malloc(val_offset + 22, MYF(MY_WME)))) return; - event_len= longlong10_to_str(uint8korr(val), buf + val_offset,-10)-buf; + event_len= longlong10_to_str(uint8korr(val), buf + val_offset, + ((flags & User_var_log_event::UNSIGNED_F) ? + 10 : -10))-buf; break; case DECIMAL_RESULT: { @@ -5553,12 +5563,14 @@ User_var_log_event(const char* buf, :Log_event(buf, description_event) { /* The Post-Header is empty. The Variable Data part begins immediately. */ + const char *start= buf; buf+= description_event->common_header_len + description_event->post_header_len[USER_VAR_EVENT-1]; name_len= uint4korr(buf); name= (char *) buf + UV_NAME_LEN_SIZE; buf+= UV_NAME_LEN_SIZE + name_len; is_null= (bool) *buf; + flags= User_var_log_event::UNDEF_F; // defaults to UNDEF_F if (is_null) { type= STRING_RESULT; @@ -5574,6 +5586,27 @@ User_var_log_event(const char* buf, UV_CHARSET_NUMBER_SIZE); val= (char *) (buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE + UV_CHARSET_NUMBER_SIZE + UV_VAL_LEN_SIZE); + + /** + We need to check if this is from an old server + that did not pack information for flags. + We do this by checking if there are extra bytes + after the packed value. If there are we take the + extra byte and it's value is assumed to contain + the flags value. + + Old events will not have this extra byte, thence, + we keep the flags set to UNDEF_F. + */ + uint bytes_read= ((val + val_len) - start); + DBUG_ASSERT(bytes_read==data_written || + bytes_read==(data_written-1)); + if ((data_written - bytes_read) > 0) + { + flags= (uint) *(buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE + + UV_CHARSET_NUMBER_SIZE + UV_VAL_LEN_SIZE + + val_len); + } } } @@ -5585,6 +5618,7 @@ bool User_var_log_event::write(IO_CACHE* file) char buf1[UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE + UV_CHARSET_NUMBER_SIZE + UV_VAL_LEN_SIZE]; uchar buf2[max(8, DECIMAL_MAX_FIELD_SIZE + 2)], *pos= buf2; + uint unsigned_len= 0; uint buf1_length; ulong event_length; @@ -5606,6 +5640,7 @@ bool User_var_log_event::write(IO_CACHE* file) break; case INT_RESULT: int8store(buf2, *(longlong*) val); + unsigned_len= 1; break; case DECIMAL_RESULT: { @@ -5630,13 +5665,14 @@ bool User_var_log_event::write(IO_CACHE* file) } /* Length of the whole event */ - event_length= sizeof(buf)+ name_len + buf1_length + val_len; + event_length= sizeof(buf)+ name_len + buf1_length + val_len + unsigned_len; return (write_header(file, event_length) || my_b_safe_write(file, (uchar*) buf, sizeof(buf)) || my_b_safe_write(file, (uchar*) name, name_len) || my_b_safe_write(file, (uchar*) buf1, buf1_length) || - my_b_safe_write(file, pos, val_len)); + my_b_safe_write(file, pos, val_len) || + my_b_safe_write(file, &flags, unsigned_len)); } #endif @@ -5677,7 +5713,8 @@ void User_var_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info) break; case INT_RESULT: char int_buf[22]; - longlong10_to_str(uint8korr(val), int_buf, -10); + longlong10_to_str(uint8korr(val), int_buf, + ((flags & User_var_log_event::UNSIGNED_F) ? 10 : -10)); my_b_printf(&cache, ":=%s%s\n", int_buf, print_event_info->delimiter); break; case DECIMAL_RESULT: @@ -5824,7 +5861,8 @@ int User_var_log_event::do_apply_event(Relay_log_info const *rli) a single record and with a single column. Thus, like a column value, it could always have IMPLICIT derivation. */ - e.update_hash(val, val_len, type, charset, DERIVATION_IMPLICIT, 0); + e.update_hash(val, val_len, type, charset, DERIVATION_IMPLICIT, + (flags & User_var_log_event::UNSIGNED_F)); free_root(thd->mem_root,0); return 0; @@ -6491,9 +6529,7 @@ int Append_block_log_event::do_apply_event(Relay_log_info const *rli) DBUG_EXECUTE_IF("remove_slave_load_file_before_write", { - mysql_file_close(fd, MYF(0)); - fd= -1; - mysql_file_delete(0, fname, MYF(0)); + my_delete_allow_opened(fname, MYF(0)); }); if (mysql_file_write(fd, (uchar*) block, block_len, MYF(MY_WME+MY_NABP))) @@ -7382,8 +7418,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); } @@ -7462,7 +7497,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); } @@ -7494,7 +7529,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); } DBUG_PRINT("debug", ("Table: %s.%s is compatible with master" @@ -7681,12 +7716,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) { @@ -7772,7 +7801,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|= (error ? trans_rollback_stmt(thd) : trans_commit_stmt(thd)); /* Now what if this is not a transactional engine? we still need to @@ -8251,15 +8280,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.h b/sql/log_event.h index 5530444b0d4..81669e24708 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -2498,6 +2498,10 @@ private: class User_var_log_event: public Log_event { public: + enum { + UNDEF_F= 0, + UNSIGNED_F= 1 + }; char *name; uint name_len; char *val; @@ -2505,12 +2509,14 @@ public: Item_result type; uint charset_number; bool is_null; + uchar flags; #ifndef MYSQL_CLIENT User_var_log_event(THD* thd_arg, char *name_arg, uint name_len_arg, char *val_arg, ulong val_len_arg, Item_result type_arg, - uint charset_number_arg) + uint charset_number_arg, uchar flags_arg) :Log_event(), name(name_arg), name_len(name_len_arg), val(val_arg), - val_len(val_len_arg), type(type_arg), charset_number(charset_number_arg) + val_len(val_len_arg), type(type_arg), charset_number(charset_number_arg), + flags(flags_arg) { is_null= !val; } void pack_info(Protocol* protocol); #else diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc index 8670631cfa0..caa69269e28 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); } @@ -88,7 +88,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); } @@ -108,13 +108,8 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info if (!ptr->m_tabledef.compatible_with(thd, const_cast<Relay_log_info*>(rli), ptr->table, &conv_table)) { - DBUG_PRINT("debug", ("Table: %s.%s is not compatible with master", - ptr->table->s->db.str, - ptr->table->s->table_name.str)); - 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); } DBUG_PRINT("debug", ("Table: %s.%s is compatible with master" @@ -243,13 +238,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(), @@ -1447,8 +1435,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); } @@ -1479,7 +1466,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) { @@ -1503,7 +1491,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); } @@ -1530,7 +1518,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) DBUG_RETURN(error); } 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))) @@ -1548,7 +1536,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); } } @@ -1569,10 +1557,8 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli) if (ptr->m_tabledef.compatible_with(thd, const_cast<Relay_log_info*>(rli), ptr->table, &conv_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); } ptr->m_conv_table= conv_table; @@ -1745,13 +1731,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, @@ -1833,7 +1812,7 @@ 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= (binlog_error ? trans_rollback_stmt(thd) : trans_commit_stmt(thd)))) rli->report(ERROR_LEVEL, error, "Error in %s event: commit of row events failed, " "table `%s`.`%s`", diff --git a/sql/main.cc b/sql/main.cc new file mode 100644 index 00000000000..249a2a883fe --- /dev/null +++ b/sql/main.cc @@ -0,0 +1,25 @@ +/* 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 */ + +/* + main() for mysqld. + Calls mysqld_main() entry point exported by sql library. +*/ +extern int mysqld_main(int argc, char **argv); + +int main(int argc, char **argv) +{ + return mysqld_main(argc, argv); +} diff --git a/sql/mdl.cc b/sql/mdl.cc new file mode 100644 index 00000000000..28cff420e0d --- /dev/null +++ b/sql/mdl.cc @@ -0,0 +1,2258 @@ +/* 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. */ + mysql_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; + /** + Maximum depth for deadlock searches. After this depth is + achieved we will unconditionally declare that there is a + deadlock. + + @note This depth should be small enough to avoid stack + being exhausted by recursive search algorithm. + + TODO: Find out what is the optimal value for this parameter. + Current value is safe, but probably sub-optimal, + as there is an anecdotal evidence that real-life + deadlocks are even shorter typically. + */ + static const uint MAX_SEARCH_DEPTH= 32; +}; + + +/** + 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() +{ + mysql_mutex_init(NULL /* pfs key */,&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); + mysql_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; + my_hash_value_type hash_value; + + hash_value= my_calc_hash(&m_locks, mdl_key->ptr(), mdl_key->length()); + +retry: + mysql_mutex_lock(&m_mutex); + if (!(lock= (MDL_lock*) my_hash_search_using_hash_value(&m_locks, + hash_value, + mdl_key->ptr(), + mdl_key->length()))) + { + lock= MDL_lock::create(mdl_key); + if (!lock || my_hash_insert(&m_locks, (uchar*)lock)) + { + mysql_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; + my_hash_value_type hash_value; + + hash_value= my_calc_hash(&m_locks, mdl_key->ptr(), mdl_key->length()); + +retry: + mysql_mutex_lock(&m_mutex); + if (!(lock= (MDL_lock*) my_hash_search_using_hash_value(&m_locks, + hash_value, + mdl_key->ptr(), + mdl_key->length()))) + { + mysql_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); + mysql_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++; + mysql_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. + */ + mysql_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); + mysql_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); + mysql_mutex_init(NULL /* pfs key */, &m_signal_lock, NULL); + mysql_cond_init(NULL /* pfs key */, &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); + mysql_mutex_destroy(&m_signal_lock); + mysql_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, + mysql_cond_t *cond, + mysql_mutex_t *mutex, + const char *calling_func, + const char *calling_file, + const unsigned int calling_line) +{ + mysql_mutex_assert_owner(mutex); + + mysys_var->current_mutex= mutex; + mysys_var->current_cond= 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, + mysql_mutex_t *mutex, + const char* old_msg, + const char *calling_func, + const char *calling_file, + const unsigned int calling_line) +{ + DBUG_ASSERT(mutex == mysys_var->current_mutex); + + mysql_mutex_unlock(mutex); + mysql_mutex_lock(&mysys_var->mutex); + mysys_var->current_mutex= NULL; + mysys_var->current_cond= NULL; + 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::timed_wait(struct timespec + *abs_timeout) +{ + const char *old_msg; + mdl_signal_type result; + st_my_thread_var *mysys_var= my_thread_var; + int wait_result= 0; + + mysql_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 && + wait_result != ETIMEDOUT && wait_result != ETIME) + wait_result= mysql_cond_timedwait(&m_signal_cond, &m_signal_lock, + abs_timeout); + + result= (m_signal != NO_WAKE_UP || mysys_var->abort) ? + 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]); +} + + +/** + 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 + + @param lock_wait_timeout [in] Seconds to wait before timeout. + + @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, ulong lock_wait_timeout) +{ + return acquire_lock_impl(mdl_request, lock_wait_timeout); +} + + +/** + 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->has_stronger_or_equal_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(mdl_request->ticket->has_stronger_or_equal_type(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. + + @param lock_wait_timeout Seconds to wait before timeout. + + @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, + ulong lock_wait_timeout) +{ + MDL_lock *lock; + MDL_ticket *ticket; + bool not_used; + st_my_thread_var *mysys_var= my_thread_var; + MDL_key *key= &mdl_request->key; + struct timespec abs_timeout; + struct timespec abs_shortwait; + set_timespec(abs_timeout, lock_wait_timeout); + + 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(); + bool is_timeout= FALSE; + if (!is_deadlock) + { + set_timespec(abs_shortwait, 1); + bool timeout_is_near= cmp_timespec(abs_shortwait, abs_timeout) > 0; + mdl_signal_type wait_result= + timed_wait(timeout_is_near ? &abs_timeout : &abs_shortwait); + + if (timeout_is_near && wait_result == TIMEOUT_WAKE_UP) + is_timeout= TRUE; + else if (wait_result == VICTIM_WAKE_UP) + is_deadlock= TRUE; + } + + stop_waiting(); + + if (mysys_var->abort || is_deadlock || is_timeout) + { + lock->remove_ticket(&MDL_lock::m_waiting, ticket); + MDL_ticket::destroy(ticket); + if (is_deadlock) + my_error(ER_LOCK_DEADLOCK, MYF(0)); + else if (is_timeout) + my_error(ER_LOCK_WAIT_TIMEOUT, 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. + + @param lock_wait_timeout Seconds to wait before timeout. + + @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, + ulong lock_wait_timeout) +{ + MDL_request_list::Iterator it(*mdl_requests); + MDL_request **sort_buf, **p_req; + MDL_ticket *mdl_svp= mdl_savepoint(); + 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, lock_wait_timeout)) + goto err; + } + my_free(sort_buf, MYF(0)); + return FALSE; + +err: + /* + Release locks we have managed to acquire so far. + Use rollback_to_savepoint() since there may be duplicate + requests that got assigned the same ticket. + */ + rollback_to_savepoint(mdl_svp); + /* Reset lock requests back to its initial state. */ + for (req_count= p_req - sort_buf, p_req= sort_buf; + p_req < sort_buf + req_count; p_req++) + { + (*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. + + @param lock_wait_timeout Seconds to wait before timeout. + + @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, + ulong lock_wait_timeout) +{ + 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, lock_wait_timeout)) + 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() +{ + while (1) + { + /* + The fact that we use fresh instance of deadlock_ctx for each + search performed by find_deadlock() below is important, code + responsible for victim selection relies on this. + */ + Deadlock_detection_context deadlock_ctx(this); + + 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! + + @param lock_wait_timeout Seconds to wait before timeout. + + @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, ulong lock_wait_timeout) +{ + MDL_lock *lock; + st_my_thread_var *mysys_var= my_thread_var; + struct timespec abs_timeout; + set_timespec(abs_timeout, lock_wait_timeout); + + mysql_mutex_assert_not_owner(&LOCK_open); + + DBUG_ASSERT(mdl_request->ticket == NULL); + + while (TRUE) + { + /* + 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(); + bool is_timeout= FALSE; + if (!is_deadlock) + { + mdl_signal_type wait_result= timed_wait(&abs_timeout); + if (wait_result == TIMEOUT_WAKE_UP) + is_timeout= TRUE; + else if (wait_result == VICTIM_WAKE_UP) + is_deadlock= TRUE; + } + + stop_waiting(); + + lock->remove_ticket(&MDL_lock::m_waiting, pending_ticket); + MDL_ticket::destroy(pending_ticket); + + if (mysys_var->abort || is_deadlock || is_timeout) + { + if (is_deadlock) + my_error(ER_LOCK_DEADLOCK, MYF(0)); + else if (is_timeout) + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); + return TRUE; + } + } + return TRUE; +} + + +/** + 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..59bc1f64762 --- /dev/null +++ b/sql/mdl.h @@ -0,0 +1,720 @@ +#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, ulong lock_wait_timeout); + bool acquire_locks(MDL_request_list *requests, ulong lock_wait_timeout); + bool upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket, + ulong lock_wait_timeout); + + bool clone_ticket(MDL_request *mdl_request); + + bool wait_for_lock(MDL_request *mdl_request, ulong lock_wait_timeout); + + 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= m_tickets.front(); + } + 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; } + + /** + Wake up context which is waiting for a change of MDL_lock state. + */ + void awake(mdl_signal_type signal) + { + mysql_mutex_lock(&m_signal_lock); + m_signal= signal; + mysql_cond_signal(&m_signal_cond); + mysql_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= needs_thr_lock_abort; + } + 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). + */ + mysql_mutex_t m_signal_lock; + mysql_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, ulong lock_wait_timeout); + + 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() + { + mysql_mutex_lock(&m_signal_lock); + m_signal= NO_WAKE_UP; + mysql_mutex_unlock(&m_signal_lock); + } + + mdl_signal_type timed_wait(struct timespec *abs_timeout); + + mdl_signal_type peek_signal() + { + mdl_signal_type result; + mysql_mutex_lock(&m_signal_lock); + result= m_signal; + mysql_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 b4b871e68ba..f10ef78cdc2 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -198,12 +198,12 @@ char* query_table_status(THD *thd,const char *db,const char *table_name); if (((THD *) Thd) != NULL) \ push_warning_printf(((THD *) Thd), MYSQL_ERROR::WARN_LEVEL_WARN, \ ER_WARN_DEPRECATED_SYNTAX, \ - ER(ER_WARN_DEPRECATED_SYNTAX_WITH_VER), \ - (Old), #VerHi "." #VerLo, (New)); \ + ER(ER_WARN_DEPRECATED_SYNTAX), \ + (Old), (New)); \ else \ sql_print_warning("The syntax '%s' is deprecated and will be removed " \ - "in MySQL %s. Please use %s instead.", \ - (Old), #VerHi "." #VerLo, (New)); \ + "in a future release. Please use %s instead.", \ + (Old), (New)); \ } while(0) extern MYSQL_PLUGIN_IMPORT CHARSET_INFO *system_charset_info; @@ -238,6 +238,7 @@ typedef struct my_locale_errmsgs extern char err_shared_dir[]; +/** @note Keep this a POD-type because we use offsetof() on it */ typedef struct my_locale_st { uint number; @@ -947,15 +948,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 */ @@ -1081,7 +1073,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, @@ -1109,6 +1100,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); @@ -1123,11 +1115,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); @@ -1252,10 +1241,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, @@ -1309,37 +1297,26 @@ 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 open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, uint db_stat, uint prgflag, uint ha_open_flags, TABLE *outparam, TABLE_LIST *table_desc, MEM_ROOT *mem_root); -bool wait_for_tables(THD *thd); -bool table_is_used(TABLE *table, bool wait_for_name_lock); -TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name); -void abort_locked_tables(THD *thd,const char *db, const char *table_name); void execute_init_command(THD *thd, LEX_STRING *init_command, mysql_rwlock_t *var_mutex); extern Field *not_found_field; @@ -1473,8 +1450,9 @@ 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); +void mysql_ha_move_tickets_after_trans_sentinel(THD *thd); /* sql_base.cc */ #define TMP_TABLE_KEY_EXTRA 8 @@ -1503,9 +1481,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); @@ -1584,24 +1565,43 @@ 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); +bool lock_tables(THD *thd, TABLE_LIST *tables, uint counter, uint flags, + bool *need_reopen); 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); @@ -1609,7 +1609,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, @@ -1621,10 +1622,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); @@ -1652,13 +1653,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 @@ -1672,7 +1670,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; @@ -1683,7 +1681,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; @@ -1777,19 +1774,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); @@ -2044,7 +2040,7 @@ extern bool in_bootstrap; extern uint volatile thread_count, global_read_lock; extern uint connection_count; extern my_bool opt_sql_bin_update, opt_safe_user_create, opt_no_mix_types; -extern my_bool opt_safe_show_db, opt_local_infile, opt_myisam_use_mmap; +extern my_bool opt_local_infile, opt_myisam_use_mmap; extern my_bool opt_slave_compressed_protocol, use_temp_pool; extern uint slave_exec_mode_options; extern ulonglong slave_type_conversions_options; @@ -2116,14 +2112,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[]; @@ -2162,16 +2159,44 @@ 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 +/** + Open tables using MDL_SHARED_HIGH_PRIO lock instead of one specified + in parser. +*/ +#define MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL 0x0800 + +/** 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); @@ -2179,30 +2204,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 */ @@ -2329,7 +2339,7 @@ ulong next_io_size(ulong pos); void append_unescaped(String *res, const char *pos, uint length); int create_frm(THD *thd, const char *name, const char *db, const char *table, uint reclength, uchar *fileinfo, - HA_CREATE_INFO *create_info, uint keys); + HA_CREATE_INFO *create_info, uint keys, KEY *key_info); void update_create_info_from_table(HA_CREATE_INFO *info, TABLE *form); int rename_file_ext(const char * from,const char * to,const char * ext); bool check_db_name(LEX_STRING *db); @@ -2339,6 +2349,7 @@ char *get_field(MEM_ROOT *mem, Field *field); bool get_field(MEM_ROOT *mem, Field *field, class String *res); int wild_case_compare(CHARSET_INFO *cs, const char *str,const char *wildstr); char *fn_rext(char *name); +bool check_duplicate_warning(THD *thd, char *msg, ulong length); /* Conversion functions */ #endif /* MYSQL_SERVER */ @@ -2545,14 +2556,14 @@ inline bool is_user_table(TABLE * table) #ifndef EMBEDDED_LIBRARY extern "C" void unireg_abort(int exit_code) __attribute__((noreturn)); void kill_delayed_threads(void); -bool check_stack_overrun(THD *thd, long margin, uchar *dummy); #else extern "C" void unireg_clear(int exit_code); #define unireg_abort(exit_code) do { unireg_clear(exit_code); DBUG_RETURN(exit_code); } while(0) inline void kill_delayed_threads(void) {} -#define check_stack_overrun(A, B, C) 0 #endif +bool check_stack_overrun(THD *thd, long margin, uchar *dummy); + /* This must match the path length limit in the ER_NOT_RW_DIR error msg. */ #define ER_NOT_RW_DIR_PATHSIZE 200 bool is_usable_directory(THD *thd, const char *varname, @@ -2650,7 +2661,6 @@ enum options_mysqld OPT_BOOTSTRAP, OPT_CONSOLE, OPT_DEBUG_SYNC_TIMEOUT, - OPT_DELAY_KEY_WRITE_ALL, OPT_ISAM_LOG, OPT_KEY_BUFFER_SIZE, OPT_KEY_CACHE_AGE_THRESHOLD, @@ -2669,19 +2679,16 @@ enum options_mysqld OPT_SAFE, OPT_SERVER_ID, OPT_SKIP_HOST_CACHE, - OPT_SKIP_LOCK, OPT_SKIP_NEW, OPT_SKIP_PRIOR, OPT_SKIP_RESOLVE, OPT_SKIP_STACK_TRACE, - OPT_SKIP_SYMLINKS, OPT_SLOW_QUERY_LOG, OPT_SSL_CA, OPT_SSL_CAPATH, OPT_SSL_CERT, OPT_SSL_CIPHER, OPT_SSL_KEY, - OPT_UPDATE_LOG, OPT_WANT_CORE, OPT_ENGINE_CONDITION_PUSHDOWN }; @@ -2715,7 +2722,7 @@ extern PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_prep_xids, key_master_info_data_lock, key_master_info_run_lock, key_mutex_slave_reporting_capability_err_lock, key_relay_log_info_data_lock, key_relay_log_info_log_space_lock, key_relay_log_info_run_lock, - key_structure_guard_mutex, key_TABLE_SHARE_mutex, key_LOCK_error_messages, + key_structure_guard_mutex, key_TABLE_SHARE_LOCK_ha_data, key_LOCK_error_messages, key_LOCK_thread_count; extern PSI_rwlock_key key_rwlock_LOCK_grant, key_rwlock_LOCK_logger, diff --git a/sql/mysqld.cc b/sql/mysqld.cc index c0f4f61c1b5..6bd133660b3 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -52,6 +52,10 @@ #include "sp_rcontext.h" #include "sp_cache.h" +#ifdef HAVE_POLL_H +#include <poll.h> +#endif + #define mysqld_charset &my_charset_latin1 /* stack traces are only supported on linux intel */ @@ -177,6 +181,9 @@ typedef fp_except fp_except_t; /* for IRIX to use set_fpc_csr() */ #include <sys/fpu.h> #endif +#ifdef HAVE_FPU_CONTROL_H +#include <fpu_control.h> +#endif #if defined(__i386__) && !defined(HAVE_FPU_CONTROL_H) # define fpu_control_t unsigned int # define _FPU_EXTENDED 0x300 @@ -317,6 +324,10 @@ static PSI_thread_key key_thread_handle_con_sockets; #ifdef __WIN__ static PSI_thread_key key_thread_handle_shutdown; #endif /* __WIN__ */ + +#if defined (HAVE_OPENSSL) && !defined(HAVE_YASSL) +static PSI_rwlock_key key_rwlock_openssl; +#endif #endif /* HAVE_PSI_INTERFACE */ /* the default log output is log tables */ @@ -928,8 +939,6 @@ static bool add_terminator(DYNAMIC_ARRAY *options); extern "C" my_bool mysqld_get_one_option(int, const struct my_option *, char *); static void set_server_version(void); static int init_thread_environment(); -static void init_error_log_mutex(); -static void clean_up_error_log_mutex(); static char *get_relative_path(const char *path); static int fix_paths(void); void handle_connections_sockets(); @@ -1346,6 +1355,18 @@ extern "C" sig_handler print_signal_warning(int sig) #ifndef EMBEDDED_LIBRARY +static void init_error_log_mutex() +{ + mysql_mutex_init(key_LOCK_error_log, &LOCK_error_log, MY_MUTEX_INIT_FAST); +} + + +static void clean_up_error_log_mutex() +{ + mysql_mutex_destroy(&LOCK_error_log); +} + + /** cleanup all memory and end program nicely. @@ -1430,8 +1451,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 */ @@ -1442,12 +1461,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(); @@ -1551,7 +1573,7 @@ static void clean_up_mutexes() mysql_mutex_destroy(&LOCK_des_key_file); #ifndef HAVE_YASSL for (int i= 0; i < CRYPTO_num_locks(); ++i) - rwlock_destroy(&openssl_stdlocks[i].lock); + mysql_rwlock_destroy(&openssl_stdlocks[i].lock); OPENSSL_free(openssl_stdlocks); #endif #endif @@ -3360,7 +3382,7 @@ static int init_common_variables() set the def_value member to 0 in my_long_options and initialize it to the correct value here. */ - default_storage_engine="MyISAM"; + default_storage_engine= const_cast<char *>("MyISAM"); /* Add server status variables to the dynamic list of @@ -3701,15 +3723,6 @@ You should consider changing lower_case_table_names to 1 or 2", return 0; } -static void init_error_log_mutex() -{ - mysql_mutex_init(key_LOCK_error_log, &LOCK_error_log, MY_MUTEX_INIT_FAST); -} - -static void clean_up_error_log_mutex() -{ - mysql_mutex_destroy(&LOCK_error_log); -} static int init_thread_environment() { @@ -3752,7 +3765,7 @@ static int init_thread_environment() openssl_stdlocks= (openssl_lock_t*) OPENSSL_malloc(CRYPTO_num_locks() * sizeof(openssl_lock_t)); for (int i= 0; i < CRYPTO_num_locks(); ++i) - my_rwlock_init(&openssl_stdlocks[i].lock, NULL); + mysql_rwlock_init(key_rwlock_openssl, &openssl_stdlocks[i].lock); CRYPTO_set_dynlock_create_callback(openssl_dynlock_create); CRYPTO_set_dynlock_destroy_callback(openssl_dynlock_destroy); CRYPTO_set_dynlock_lock_callback(openssl_lock); @@ -3806,7 +3819,7 @@ static unsigned long openssl_id_function() static openssl_lock_t *openssl_dynlock_create(const char *file, int line) { openssl_lock_t *lock= new openssl_lock_t; - my_rwlock_init(&lock->lock, NULL); + mysql_rwlock_init(key_rwlock_openssl, &lock->lock); return lock; } @@ -3814,7 +3827,7 @@ static openssl_lock_t *openssl_dynlock_create(const char *file, int line) static void openssl_dynlock_destroy(openssl_lock_t *lock, const char *file, int line) { - rwlock_destroy(&lock->lock); + mysql_rwlock_destroy(&lock->lock); delete lock; } @@ -3840,16 +3853,16 @@ static void openssl_lock(int mode, openssl_lock_t *lock, const char *file, switch (mode) { case CRYPTO_LOCK|CRYPTO_READ: what = "read lock"; - err = rw_rdlock(&lock->lock); + err= mysql_rwlock_rdlock(&lock->lock); break; case CRYPTO_LOCK|CRYPTO_WRITE: what = "write lock"; - err = rw_wrlock(&lock->lock); + err= mysql_rwlock_wrlock(&lock->lock); break; case CRYPTO_UNLOCK|CRYPTO_READ: case CRYPTO_UNLOCK|CRYPTO_WRITE: what = "unlock"; - err = rw_unlock(&lock->lock); + err= mysql_rwlock_unlock(&lock->lock); break; default: /* Unknown locking mode. */ @@ -3919,7 +3932,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); @@ -3961,6 +3975,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 @@ -3998,21 +4015,13 @@ static int init_server_components() Implementation of the above : - If mysqld is started with --log-update and --log-bin, ignore --log-update (print a warning), push a warning when SQL_LOG_UPDATE - is used, and turn off --sql-bin-update-same. + is used, This will completely ignore SQL_LOG_UPDATE - If mysqld is started with --log-update only, change it to --log-bin (with the filename passed to log-update, plus '-bin') (print a warning), push a warning when SQL_LOG_UPDATE is - used, and turn on --sql-bin-update-same. + used. This will translate SQL_LOG_UPDATE to SQL_LOG_BIN. - - Note that we tell the user that --sql-bin-update-same is deprecated and - does nothing, and we don't take into account if he used this option or - not; but internally we give this variable a value to have the behaviour - we want (i.e. have SQL_LOG_UPDATE influence SQL_LOG_BIN or not). - As sql-bin-update-same, log-update and log-bin cannot be changed by the - user after starting the server (they are not variables), the user will - not later interfere with the settings we do here. */ if (opt_bin_log) { @@ -4461,7 +4470,7 @@ static void test_lc_time_sz() #ifdef __WIN__ int win_main(int argc, char **argv) #else -int main(int argc, char **argv) +int mysqld_main(int argc, char **argv) #endif { /* @@ -5007,7 +5016,7 @@ default_service_handling(char **argv, } -int main(int argc, char **argv) +int mysqld_main(int argc, char **argv) { /* When several instances are running on the same machine, we @@ -5338,26 +5347,47 @@ void handle_connections_sockets() { my_socket sock,new_sock; uint error_count=0; - uint max_used_connection= (uint) (max(ip_sock,unix_sock)+1); - fd_set readFDs,clientFDs; THD *thd; struct sockaddr_storage cAddr; - int ip_flags=0,socket_flags=0,flags; + int ip_flags=0,socket_flags=0,flags,retval; st_vio *vio_tmp; +#ifdef HAVE_POLL + int socket_count= 0; + struct pollfd fds[2]; // for ip_sock and unix_sock +#else + fd_set readFDs,clientFDs; + uint max_used_connection= (uint) (max(ip_sock,unix_sock)+1); +#endif + DBUG_ENTER("handle_connections_sockets"); LINT_INIT(new_sock); +#ifndef HAVE_POLL FD_ZERO(&clientFDs); +#endif + if (ip_sock != INVALID_SOCKET) { +#ifdef HAVE_POLL + fds[socket_count].fd= ip_sock; + fds[socket_count].events= POLLIN; + socket_count++; +#else FD_SET(ip_sock,&clientFDs); +#endif #ifdef HAVE_FCNTL ip_flags = fcntl(ip_sock, F_GETFL, 0); #endif } #ifdef HAVE_SYS_UN_H +#ifdef HAVE_POLL + fds[socket_count].fd= unix_sock; + fds[socket_count].events= POLLIN; + socket_count++; +#else FD_SET(unix_sock,&clientFDs); +#endif #ifdef HAVE_FCNTL socket_flags=fcntl(unix_sock, F_GETFL, 0); #endif @@ -5367,12 +5397,15 @@ void handle_connections_sockets() MAYBE_BROKEN_SYSCALL; while (!abort_loop) { - readFDs=clientFDs; -#ifdef HPUX10 - if (select(max_used_connection,(int*) &readFDs,0,0,0) < 0) - continue; +#ifdef HAVE_POLL + retval= poll(fds, socket_count, -1); #else - if (select((int) max_used_connection,&readFDs,0,0,0) < 0) + readFDs=clientFDs; + + retval= select((int) max_used_connection,&readFDs,0,0,0); +#endif + + if (retval < 0) { if (socket_errno != SOCKET_EINTR) { @@ -5382,7 +5415,7 @@ void handle_connections_sockets() MAYBE_BROKEN_SYSCALL continue; } -#endif /* HPUX10 */ + if (abort_loop) { MAYBE_BROKEN_SYSCALL; @@ -5390,6 +5423,21 @@ void handle_connections_sockets() } /* Is this a new connection request ? */ +#ifdef HAVE_POLL + for (int i= 0; i < socket_count; ++i) + { + if (fds[i].revents & POLLIN) + { + sock= fds[i].fd; +#ifdef HAVE_FCNTL + flags= fcntl(sock, F_GETFL, 0); +#else + flags= 0; +#endif // HAVE_FCNTL + break; + } + } +#else // HAVE_POLL #ifdef HAVE_SYS_UN_H if (FD_ISSET(unix_sock,&readFDs)) { @@ -5397,11 +5445,12 @@ void handle_connections_sockets() flags= socket_flags; } else -#endif +#endif // HAVE_SYS_UN_H { sock = ip_sock; flags= ip_flags; } +#endif // HAVE_POLL #if !defined(NO_FCNTL_NONBLOCK) if (!(test_flags & TEST_BLOCKING)) @@ -5965,12 +6014,6 @@ struct my_option my_long_options[]= 0, 0, 0}, {"core-file", OPT_WANT_CORE, "Write core on errors.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"default-character-set", 'C', "Set the default character set (deprecated option, use --character-set-server instead).", - (uchar**) &default_character_set_name, (uchar**) &default_character_set_name, - 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, - {"default-collation", 0, "Set the default collation (deprecated option, use --collation-server instead).", - (uchar**) &default_collation_name, (uchar**) &default_collation_name, - 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, /* default-storage-engine should have "MyISAM" as def_value. Instead of initializing it here it is done in init_common_variables() due to a compiler bug in Sun Studio compiler. */ @@ -5980,9 +6023,6 @@ struct my_option my_long_options[]= {"default-time-zone", 0, "Set the default time zone.", (uchar**) &default_tz_name, (uchar**) &default_tz_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, - {"delay-key-write-for-all-tables", OPT_DELAY_KEY_WRITE_ALL, - "Don't flush key buffers between writes for any MyISAM table (Deprecated option, use --delay-key-write=all instead).", - 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, #ifdef HAVE_OPENSSL {"des-key-file", 0, "Load keys for des_encrypt() and des_encrypt from given file.", @@ -5996,10 +6036,6 @@ struct my_option my_long_options[]= (uchar**) &disconnect_slave_event_count, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, #endif /* HAVE_REPLICATION */ - {"enable-locking", 0, - "Deprecated option, use --external-locking instead.", - (uchar**) &opt_external_locking, (uchar**) &opt_external_locking, - 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, #ifdef HAVE_STACK_TRACE_ON_SEGV {"enable-pstack", 0, "Print a symbolic stack trace on failure.", (uchar**) &opt_do_pstack, (uchar**) &opt_do_pstack, 0, GET_BOOL, NO_ARG, 0, 0, @@ -6052,9 +6088,6 @@ struct my_option my_long_options[]= {"log-isam", OPT_ISAM_LOG, "Log all MyISAM changes to file.", (uchar**) &myisam_log_filename, (uchar**) &myisam_log_filename, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, - {"log-long-format", '0', - "Log some extra information to update log. Please note that this option is deprecated; see --log-short-format option.", - 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"log-short-format", 0, "Don't log extra information to update and slow-query logs.", (uchar**) &opt_short_log_format, (uchar**) &opt_short_log_format, @@ -6087,11 +6120,6 @@ struct my_option my_long_options[]= REQUIRED_ARG, TC_LOG_MIN_SIZE, TC_LOG_MIN_SIZE, ULONG_MAX, 0, TC_LOG_PAGE_SIZE, 0}, #endif - {"log-update", OPT_UPDATE_LOG, - "The update log is deprecated since version 5.0, is replaced by the binary \ -log and this option justs turns on --log-bin instead.", - (uchar**) &opt_update_logname, (uchar**) &opt_update_logname, 0, GET_STR, - OPT_ARG, 0, 0, 0, 0, 0, 0}, {"master-info-file", 0, "The location and name of the file that remembers the master and where the I/O replication \ thread is in the master's binlogs.", @@ -6155,11 +6183,6 @@ Can't be set to 1 if --log-slave-updates is used.", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"safe-mode", OPT_SAFE, "Skip some optimize stages (for testing).", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, -#ifndef TO_BE_DELETED - {"safe-show-database", 0, - "Deprecated option; use GRANT SHOW DATABASES instead...", - 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, -#endif {"safe-user-create", 0, "Don't allow new user creation by the user who has no write privileges to the mysql.user table.", (uchar**) &opt_safe_user_create, (uchar**) &opt_safe_user_create, 0, GET_BOOL, @@ -6172,9 +6195,6 @@ Can't be set to 1 if --log-slave-updates is used.", (uchar**)&sf_malloc_mem_limit, (uchar**)&sf_malloc_mem_limit, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, #endif - {"set-variable", 'O', - "Change the value of a variable. Please note that this option is deprecated;you can set variables directly with --variable-name=value.", - 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"show-slave-auth-info", 0, "Show user and password in SHOW SLAVE HOSTS on this master", (uchar**) &opt_show_slave_auth_info, (uchar**) &opt_show_slave_auth_info, 0, @@ -6187,9 +6207,6 @@ Can't be set to 1 if --log-slave-updates is used.", #endif {"skip-host-cache", OPT_SKIP_HOST_CACHE, "Don't cache host names.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"skip-locking", OPT_SKIP_LOCK, - "Deprecated option, use --skip-external-locking instead.", - 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"skip-name-resolve", OPT_SKIP_RESOLVE, "Don't resolve hostnames. All hostnames are IP's or 'localhost'.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, @@ -6201,8 +6218,6 @@ Can't be set to 1 if --log-slave-updates is used.", {"skip-stack-trace", OPT_SKIP_STACK_TRACE, "Don't print a stack trace on failure.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"skip-symlink", OPT_SKIP_SYMLINKS, "Don't allow symlinking of tables. Deprecated option. Use --skip-symbolic-links instead.", - 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"skip-thread-priority", OPT_SKIP_PRIOR, "Don't give threads different priorities. This option is deprecated " "because it has no effect; the implied behavior is already the default.", @@ -6214,10 +6229,6 @@ Can't be set to 1 if --log-slave-updates is used.", (uchar**) &opt_sporadic_binlog_dump_fail, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, #endif /* HAVE_REPLICATION */ - {"sql-bin-update-same", 0, - "The update log is deprecated since version 5.0, is replaced by the " - "binary log and this option does nothing anymore.", - 0, 0, 0, GET_DISABLED, NO_ARG, 0, 0, 0, 0, 0, 0}, #ifdef HAVE_OPENSSL {"ssl", 0, "Enable SSL for connection (automatically enabled with other flags).", @@ -6276,20 +6287,12 @@ Can't be set to 1 if --log-slave-updates is used.", 0, 0}, {"version", 'V', "Output version information and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, - {"warnings", 'W', "Deprecated; use --log-warnings instead.", - (uchar**) &global_system_variables.log_warnings, - (uchar**) &max_system_variables.log_warnings, 0, GET_ULONG, OPT_ARG, - 1, 0, ULONG_MAX, 0, 0, 0}, {"plugin-load", 0, "Optional semicolon-separated list of plugins to load, where each plugin is " "identified as name=library, where name is the plugin name and library " "is the plugin library in plugin_dir.", (uchar**) &opt_plugin_load, (uchar**) &opt_plugin_load, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"record_buffer", 0, "Deprecated; use --read-buffer-size instead.", - (uchar**) &global_system_variables.read_buff_size, - (uchar**) &max_system_variables.read_buff_size,0, GET_ULONG, REQUIRED_ARG, - 128*1024L, IO_SIZE*2, INT_MAX32, 0, IO_SIZE, 0}, {"table_cache", 0, "Deprecated; use --table-open-cache instead.", (uchar**) &table_cache_size, (uchar**) &table_cache_size, 0, GET_ULONG, REQUIRED_ARG, TABLE_OPEN_CACHE_DEFAULT, 1, 512*1024L, 0, 1, 0}, @@ -7203,9 +7206,6 @@ mysqld_get_one_option(int optid, case (int) OPT_ISAM_LOG: opt_myisam_log=1; break; - case (int) OPT_UPDATE_LOG: - opt_update_log=1; - break; case (int) OPT_BIN_LOG: opt_bin_log= test(argument != disabled_my_option); break; @@ -7326,9 +7326,6 @@ mysqld_get_one_option(int optid, "and will be removed in MySQL 7.0. This option has no effect " "as the implied behavior is already the default."); break; - case (int) OPT_SKIP_LOCK: - opt_external_locking=0; - break; case (int) OPT_SKIP_HOST_CACHE: opt_specialflag|= SPECIAL_NO_HOST_CACHE; break; @@ -7341,9 +7338,6 @@ mysqld_get_one_option(int optid, case (int) OPT_SKIP_STACK_TRACE: test_flags|=TEST_NO_STACKTRACE; break; - case (int) OPT_SKIP_SYMLINKS: - my_use_symdir=0; - break; case (int) OPT_BIND_ADDRESS: { struct addrinfo *res_lst, hints; @@ -7376,12 +7370,6 @@ mysqld_get_one_option(int optid, case OPT_SERVER_ID: server_id_supplied = 1; break; - case OPT_DELAY_KEY_WRITE_ALL: - if (argument != disabled_my_option) - delay_key_write_options= DELAY_KEY_WRITE_ALL; - else - delay_key_write_options= DELAY_KEY_WRITE_NONE; - break; case OPT_ONE_THREAD: thread_handling= SCHEDULER_ONE_THREAD_PER_CONNECTION; break; @@ -7681,7 +7669,7 @@ static char *get_relative_path(const char *path) strcmp(DEFAULT_MYSQL_HOME,FN_ROOTDIR)) { path+=(uint) strlen(DEFAULT_MYSQL_HOME); - while (*path == FN_LIBCHAR) + while (*path == FN_LIBCHAR || *path == FN_LIBCHAR2) path++; } return (char*) path; @@ -7918,7 +7906,7 @@ PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_prep_xids, key_master_info_data_lock, key_master_info_run_lock, key_mutex_slave_reporting_capability_err_lock, key_relay_log_info_data_lock, key_relay_log_info_log_space_lock, key_relay_log_info_run_lock, - key_structure_guard_mutex, key_TABLE_SHARE_mutex, key_LOCK_error_messages, + key_structure_guard_mutex, key_TABLE_SHARE_LOCK_ha_data, key_LOCK_error_messages, key_LOG_INFO_lock, key_LOCK_thread_count; static PSI_mutex_info all_server_mutexes[]= @@ -7970,7 +7958,7 @@ static PSI_mutex_info all_server_mutexes[]= { &key_relay_log_info_log_space_lock, "Relay_log_info::log_space_lock", 0}, { &key_relay_log_info_run_lock, "Relay_log_info::run_lock", 0}, { &key_structure_guard_mutex, "Query_cache::structure_guard_mutex", 0}, - { &key_TABLE_SHARE_mutex, "TABLE_SHARE::mutex", 0}, + { &key_TABLE_SHARE_LOCK_ha_data, "TABLE_SHARE::LOCK_ha_data", 0}, { &key_LOCK_error_messages, "LOCK_error_messages", PSI_FLAG_GLOBAL}, { &key_LOG_INFO_lock, "LOG_INFO::lock", 0}, { &key_LOCK_thread_count, "LOCK_thread_count", PSI_FLAG_GLOBAL} @@ -7982,6 +7970,9 @@ PSI_rwlock_key key_rwlock_LOCK_grant, key_rwlock_LOCK_logger, static PSI_rwlock_info all_server_rwlocks[]= { +#if defined (HAVE_OPENSSL) && !defined(HAVE_YASSL) + { &key_rwlock_openssl, "CRYPTO_dynlock_value::lock", 0}, +#endif { &key_rwlock_LOCK_grant, "LOCK_grant", PSI_FLAG_GLOBAL}, { &key_rwlock_LOCK_logger, "LOGGER::LOCK_logger", 0}, { &key_rwlock_LOCK_sys_init_connect, "LOCK_sys_init_connect", PSI_FLAG_GLOBAL}, diff --git a/sql/nt_servc.cc b/sql/nt_servc.cc index f41fa08f828..76dc2846ed0 100644 --- a/sql/nt_servc.cc +++ b/sql/nt_servc.cc @@ -10,6 +10,7 @@ #include <windows.h> #include <process.h> #include <stdio.h> +#include <stdlib.h> #include "nt_servc.h" diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 741815585e2..68285563239 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -2266,9 +2266,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, keys_to_use.intersect(head->keys_in_use_for_query); if (!keys_to_use.is_clear_all()) { -#ifndef EMBEDDED_LIBRARY // Avoid compiler warning uchar buff[STACK_BUFF_ALLOC]; -#endif MEM_ROOT alloc; SEL_TREE *tree= NULL; KEY_PART *key_parts; @@ -10955,17 +10953,7 @@ int QUICK_GROUP_MIN_MAX_SELECT::get_next() } while ((result == HA_ERR_KEY_NOT_FOUND || result == HA_ERR_END_OF_FILE) && is_last_prefix != 0); - if (result == 0) - { - /* - Partially mimic the behavior of end_select_send. Copy the - field data from Item_field::field into Item_field::result_field - of each non-aggregated field (the group fields, and optionally - other fields in non-ANSI SQL mode). - */ - copy_fields(&join->tmp_table_param); - } - else if (result == HA_ERR_KEY_NOT_FOUND) + if (result == HA_ERR_KEY_NOT_FOUND) result= HA_ERR_END_OF_FILE; DBUG_RETURN(result); diff --git a/sql/repl_failsafe.cc b/sql/repl_failsafe.cc index 5bf87dea90e..29443eb6e65 100644 --- a/sql/repl_failsafe.cc +++ b/sql/repl_failsafe.cc @@ -38,7 +38,7 @@ #define SLAVE_ERRMSG_SIZE (FN_REFLEN+64) -uint rpl_status=RPL_NULL; +RPL_STATUS rpl_status=RPL_NULL; mysql_mutex_t LOCK_rpl_status; mysql_cond_t COND_rpl_status; HASH slave_list; diff --git a/sql/repl_failsafe.h b/sql/repl_failsafe.h index dd6770be0b4..94b151aaee7 100644 --- a/sql/repl_failsafe.h +++ b/sql/repl_failsafe.h @@ -26,7 +26,7 @@ typedef enum {RPL_AUTH_MASTER=0,RPL_IDLE_SLAVE,RPL_ACTIVE_SLAVE, RPL_LOST_SOLDIER,RPL_TROOP_SOLDIER, RPL_RECOVERY_CAPTAIN,RPL_NULL /* inactive */, RPL_ANY /* wild card used by change_rpl_status */ } RPL_STATUS; -extern uint rpl_status; +extern RPL_STATUS rpl_status; extern mysql_mutex_t LOCK_rpl_status; extern mysql_cond_t COND_rpl_status; diff --git a/sql/rpl_handler.cc b/sql/rpl_handler.cc index ebd6e4e0c0b..5a406866513 100644 --- a/sql/rpl_handler.cc +++ b/sql/rpl_handler.cc @@ -190,8 +190,8 @@ int Trans_delegate::after_commit(THD *thd, bool all) { Trans_param param; bool is_real_trans= (all || thd->transaction.all.ha_list == 0); - if (is_real_trans) - param.flags |= TRANS_IS_REAL_TRANS; + + param.flags = is_real_trans ? TRANS_IS_REAL_TRANS : 0; Trans_binlog_info *log_info= my_pthread_getspecific_ptr(Trans_binlog_info*, RPL_TRANS_BINLOG_INFO); @@ -218,8 +218,8 @@ int Trans_delegate::after_rollback(THD *thd, bool all) { Trans_param param; bool is_real_trans= (all || thd->transaction.all.ha_list == 0); - if (is_real_trans) - param.flags |= TRANS_IS_REAL_TRANS; + + param.flags = is_real_trans ? TRANS_IS_REAL_TRANS : 0; Trans_binlog_info *log_info= my_pthread_getspecific_ptr(Trans_binlog_info*, RPL_TRANS_BINLOG_INFO); @@ -228,7 +228,7 @@ int Trans_delegate::after_rollback(THD *thd, bool all) param.log_pos= log_info ? log_info->log_pos : 0; int ret= 0; - FOREACH_OBSERVER(ret, after_commit, thd, (¶m)); + FOREACH_OBSERVER(ret, after_rollback, thd, (¶m)); /* This is the end of a real transaction or autocommit statement, we diff --git a/sql/rpl_injector.cc b/sql/rpl_injector.cc index 64f79092057..08d81e10d59 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); } injector::transaction::~transaction() @@ -83,11 +84,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); + trans_commit_stmt(m_thd); + if (!trans_commit(m_thd)) + { + close_thread_tables(m_thd); + m_thd->mdl_context.release_transactional_locks(); + } DBUG_RETURN(error); } + int injector::transaction::use_table(server_id_type sid, table tbl) { DBUG_ENTER("injector::transaction::use_table"); @@ -111,7 +117,7 @@ int injector::transaction::write_row (server_id_type sid, table tbl, record_type record) { DBUG_ENTER("injector::transaction::write_row(...)"); - + int error= check_state(ROW_STATE); if (error) DBUG_RETURN(error); diff --git a/sql/rpl_mi.cc b/sql/rpl_mi.cc index 38382fd2a0e..7dad340cfa6 100644 --- a/sql/rpl_mi.cc +++ b/sql/rpl_mi.cc @@ -33,8 +33,8 @@ int init_dynarray_intvar_from_file(DYNAMIC_ARRAY* arr, IO_CACHE* f); Master_info::Master_info(bool is_slave_recovery) :Slave_reporting_capability("I/O"), ssl(0), ssl_verify_server_cert(0), fd(-1), io_thd(0), - port(MYSQL_PORT), connect_retry(DEFAULT_CONNECT_RETRY), inited(0), - rli(is_slave_recovery), abort_slave(0), + rli(is_slave_recovery), port(MYSQL_PORT), + connect_retry(DEFAULT_CONNECT_RETRY), inited(0), abort_slave(0), slave_running(0), slave_run_id(0), sync_counter(0), heartbeat_period(0), received_heartbeats(0), master_id(0) { @@ -369,7 +369,7 @@ file '%s')", fname); mi->rli.is_relay_log_recovery= FALSE; // now change cache READ -> WRITE - must do this before flush_master_info reinit_io_cache(&mi->file, WRITE_CACHE, 0L, 0, 1); - if ((error=test(flush_master_info(mi, 1)))) + if ((error=test(flush_master_info(mi, TRUE, TRUE)))) sql_print_error("Failed to flush master info file"); mysql_mutex_unlock(&mi->data_lock); DBUG_RETURN(error); @@ -395,7 +395,9 @@ err: 1 - flush master info failed 0 - all ok */ -int flush_master_info(Master_info* mi, bool flush_relay_log_cache) +int flush_master_info(Master_info* mi, + bool flush_relay_log_cache, + bool need_lock_relay_log) { IO_CACHE* file = &mi->file; char lbuf[22]; @@ -418,8 +420,19 @@ int flush_master_info(Master_info* mi, bool flush_relay_log_cache) */ if (flush_relay_log_cache) { + mysql_mutex_t *log_lock= mi->rli.relay_log.get_log_lock(); IO_CACHE *log_file= mi->rli.relay_log.get_log_file(); - if (flush_io_cache(log_file)) + + if (need_lock_relay_log) + mysql_mutex_lock(log_lock); + + mysql_mutex_assert_owner(log_lock); + err= flush_io_cache(log_file); + + if (need_lock_relay_log) + mysql_mutex_unlock(log_lock); + + if (err) DBUG_RETURN(2); } diff --git a/sql/rpl_mi.h b/sql/rpl_mi.h index 6dd9fab7904..c4ca5714306 100644 --- a/sql/rpl_mi.h +++ b/sql/rpl_mi.h @@ -119,7 +119,9 @@ int init_master_info(Master_info* mi, const char* master_info_fname, bool abort_if_no_master_info_file, int thread_mask); void end_master_info(Master_info* mi); -int flush_master_info(Master_info* mi, bool flush_relay_log_cache); +int flush_master_info(Master_info* mi, + bool flush_relay_log_cache, + bool need_lock_relay_log); int change_master_server_id_cmp(ulong *id1, ulong *id2); #endif /* HAVE_REPLICATION */ diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index a982cfd25a4..e1c43771b32 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); @@ -123,7 +124,7 @@ int init_relay_log_info(Relay_log_info* rli, /* The relay log will now be opened, as a SEQ_READ_APPEND IO_CACHE. Note that the I/O thread flushes it to disk after writing every - event, in flush_master_info(mi, 1). + event, in flush_master_info(mi, 1, ?). */ /* @@ -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 54b5c9c30b2..b1ed75146a0 100644 --- a/sql/rpl_rli.h +++ b/sql/rpl_rli.h @@ -361,6 +361,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/CMakeLists.txt b/sql/share/CMakeLists.txt new file mode 100644 index 00000000000..1868200f038 --- /dev/null +++ b/sql/share/CMakeLists.txt @@ -0,0 +1,54 @@ +# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +SET (dirs +danish +german +slovak +dutch +greek +norwegian +spanish +english +hungarian +norwegian-ny +swedish +italian +polish +ukrainian +japanese +portuguese +romanian +estonian +korean +russian +czech +french +serbian +) + +SET(files + errmsg-utf8.txt +) + +FOREACH (dir ${dirs}) + INSTALL(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${dir} + DESTINATION ${INSTALL_MYSQLSHAREDIR}) +ENDFOREACH() +INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/charsets DESTINATION ${INSTALL_MYSQLSHAREDIR} + PATTERN "languages.html" EXCLUDE +) + +INSTALL(FILES ${files} DESTINATION ${INSTALL_MYSQLSHAREDIR}) diff --git a/sql/share/Makefile.am b/sql/share/Makefile.am index 203dbe54254..da19ed1c5a7 100644 --- a/sql/share/Makefile.am +++ b/sql/share/Makefile.am @@ -15,8 +15,9 @@ ## Process this file with automake to create Makefile.in -EXTRA_DIST= errmsg-utf8.txt - +EXTRA_DIST= errmsg-utf8.txt \ + CMakeLists.txt + dist-hook: for dir in charsets @AVAILABLE_LANGUAGES@; do \ test -d $(distdir)/$$dir || mkdir $(distdir)/$$dir; \ diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 33a68f1489e..7ea8c75e43e 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -5018,10 +5018,8 @@ ER_UNKNOWN_STORAGE_ENGINE 42000 ger "Unbekannte Speicher-Engine '%s'" por "Motor de tabela desconhecido '%s'" spa "Desconocido motor de tabla '%s'" -# When using this error code, use ER(ER_WARN_DEPRECATED_SYNTAX_WITH_VER) -# for the message string. See, for example, code in mysql_priv.h. ER_WARN_DEPRECATED_SYNTAX - eng "'%s' is deprecated; use '%s' instead" + eng "'%s' is deprecated and will be removed in a future release. Please use %s instead" ger "'%s' ist veraltet. Bitte benutzen Sie '%s'" por "'%s' é desatualizado. Use '%s' em seu lugar" spa "'%s' está desaprobado, use '%s' en su lugar" @@ -6280,8 +6278,8 @@ ER_BINLOG_UNSAFE_INSERT_DELAYED eng "Statement uses INSERT DELAYED. This is unsafe because the time when rows are inserted cannot be predicted." ER_BINLOG_UNSAFE_SYSTEM_TABLE eng "Statement uses the general_log, slow_log or performance_schema table(s). This is unsafe because system tables may differ on slave." -ER_BINLOG_UNSAFE_TWO_AUTOINC_COLUMNS - eng "Statement updates two AUTO_INCREMENT columns. This is unsafe because the generated value cannot be predicted by slave." +ER_BINLOG_UNSAFE_AUTOINC_COLUMNS + eng "Statement invokes a trigger or a stored function that inserts into AUTO_INCREMENT column which is unsafe to binlog in STATEMENT format because slave may execute it non-deterministically." ER_BINLOG_UNSAFE_UDF eng "Statement uses a UDF. It cannot be determined if the UDF will return the same value on slave." ER_BINLOG_UNSAFE_SYSTEM_VARIABLE @@ -6311,8 +6309,15 @@ ER_WRONG_NATIVE_TABLE_STRUCTURE ER_WRONG_PERFSCHEMA_USAGE eng "Invalid performance_schema usage." +ER_WARN_I_S_SKIPPED_TABLE + eng "Table '%s'.'%s' was skipped since its definition is being modified by concurrent DDL statement" ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT eng "Cannot modify @@session.binlog_direct_non_transactional_updates inside a transaction" ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT eng "Cannot change the binlog direct flag inside a stored function or trigger" +ER_SPATIAL_MUST_HAVE_GEOM_COL 42000 + eng "A SPATIAL index may only contain a geometrical type column" + +ER_TOO_LONG_INDEX_COMMENT + eng "Comment for index '%-.64s' is too long (max = %lu)" diff --git a/sql/slave.cc b/sql/slave.cc index a4f16f6ed28..45c73a3e190 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> @@ -510,7 +511,7 @@ int terminate_slave_threads(Master_info* mi,int thread_mask,bool skip_lock) DBUG_PRINT("info",("Flushing relay log and master info file.")); if (current_thd) thd_proc_info(current_thd, "Flushing relay log and master info files."); - if (flush_master_info(mi, TRUE /* flush relay log */)) + if (flush_master_info(mi, TRUE, FALSE)) DBUG_RETURN(ER_ERROR_DURING_FLUSH_LOGS); if (my_sync(mi->rli.relay_log.get_log_file()->file, MYF(MY_WME))) @@ -1111,7 +1112,7 @@ int init_dynarray_intvar_from_file(DYNAMIC_ARRAY* arr, IO_CACHE* f) memcpy(buf_act, buf, read_size); snd_size= my_b_gets(f, buf_act + read_size, max_size - read_size); if (snd_size == 0 || - (snd_size + 1 == max_size - read_size) && buf[max_size - 2] != '\n') + ((snd_size + 1 == max_size - read_size) && buf[max_size - 2] != '\n')) { /* failure to make the 2nd read or short read again @@ -1601,7 +1602,7 @@ static void write_ignored_events_info_to_relay_log(THD *thd, Master_info *mi) " to the relay log, SHOW SLAVE STATUS may be" " inaccurate"); rli->relay_log.harvest_bytes_written(&rli->log_space_total); - if (flush_master_info(mi, 1)) + if (flush_master_info(mi, TRUE, TRUE)) sql_print_error("Failed to flush master info file"); delete ev; } @@ -2517,7 +2518,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); @@ -2928,7 +2931,7 @@ Stopping slave I/O thread due to out-of-memory error from master"); goto err; } - if (flush_master_info(mi, 1)) + if (flush_master_info(mi, TRUE, TRUE)) { sql_print_error("Failed to flush master info file"); goto err; @@ -3943,8 +3946,8 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) /* everything is filtered out from non-master */ (s_id != mi->master_id || /* for the master meta information is necessary */ - buf[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT && - buf[EVENT_TYPE_OFFSET] != ROTATE_EVENT))) + (buf[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT && + buf[EVENT_TYPE_OFFSET] != ROTATE_EVENT)))) { /* Do not write it to the relay log. @@ -3964,9 +3967,9 @@ static int queue_event(Master_info* mi,const char* buf, ulong event_len) as well as rli->group_relay_log_pos. */ if (!(s_id == ::server_id && !mi->rli.replicate_same_server_id) || - buf[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT && - buf[EVENT_TYPE_OFFSET] != ROTATE_EVENT && - buf[EVENT_TYPE_OFFSET] != STOP_EVENT) + (buf[EVENT_TYPE_OFFSET] != FORMAT_DESCRIPTION_EVENT && + buf[EVENT_TYPE_OFFSET] != ROTATE_EVENT && + buf[EVENT_TYPE_OFFSET] != STOP_EVENT)) { mi->master_log_pos+= inc_pos; memcpy(rli->ign_master_log_name_end, mi->master_log_name, FN_REFLEN); diff --git a/sql/sp.cc b/sql/sp.cc index cbc0d003c9f..39a8b6a009f 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> @@ -392,12 +391,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); @@ -427,11 +427,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); @@ -442,6 +442,7 @@ static TABLE *open_proc_table_for_update(THD *thd) close_thread_tables(thd); DBUG_RETURN(NULL); + } @@ -527,7 +528,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"); @@ -927,8 +928,13 @@ sp_create_routine(THD *thd, int type, sp_head *sp) row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); + + /* 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; @@ -1096,7 +1102,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(); @@ -1127,7 +1136,6 @@ sp_create_routine(THD *thd, int type, sp_head *sp) ret= SP_INTERNAL_ERROR; thd->variables.sql_mode= 0; } - } done: @@ -1136,7 +1144,9 @@ done: close_thread_tables(thd); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(ret); } @@ -1174,8 +1184,13 @@ sp_drop_routine(THD *thd, int type, sp_name *name) row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); + + /* 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); @@ -1190,11 +1205,27 @@ sp_drop_routine(THD *thd, int type, sp_name *name) if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) ret= SP_INTERNAL_ERROR; 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); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(ret); } @@ -1228,18 +1259,48 @@ 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 statement. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); 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) { + 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(); @@ -1266,10 +1327,12 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics) ret= SP_INTERNAL_ERROR; sp_cache_invalidate(); } - +err: close_thread_tables(thd); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(ret); } @@ -1352,10 +1415,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", @@ -1365,28 +1425,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); } @@ -1570,7 +1631,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; @@ -1584,73 +1645,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(); } @@ -1660,11 +1660,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 @@ -1672,7 +1672,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 @@ -1687,28 +1687,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; @@ -1722,24 +1719,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; } @@ -1747,13 +1747,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) { /* @@ -1761,12 +1762,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; } @@ -1795,7 +1797,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; @@ -1810,23 +1813,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); } } @@ -1836,243 +1840,139 @@ 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); -} - - -/** - 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. + int ret= 0; + sp_cache **spc= (type == TYPE_ENUM_FUNCTION ? + &thd->sp_func_cache : &thd->sp_proc_cache); - @param thd thread context - @param lex LEX respresenting statement - @param table Table list element for table with trigger + DBUG_ENTER("sp_cache_routine"); - @retval - 0 success - @retval - non-0 failure -*/ + DBUG_ASSERT(type == TYPE_ENUM_FUNCTION || type == TYPE_ENUM_PROCEDURE); -int -sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex, - TABLE_LIST *table) -{ - int ret= 0; - DBUG_ENTER("sp_cache_routines_and_add_tables_for_triggers"); + *sp= sp_cache_lookup(spc, name); - Sroutine_hash_entry **last_cached_routine_ptr= - (Sroutine_hash_entry **)lex->sroutines_list.next; + if (lookup_only) + DBUG_RETURN(SP_OK); - if (static_cast<int>(table->lock_type) >= - static_cast<int>(TL_WRITE_ALLOW_WRITE)) + if (*sp) { - for (int i= 0; i < (int)TRG_EVENT_MAX; i++) - { - if (table->trg_event_map & - static_cast<uint8>(1 << static_cast<int>(i))) + sp_cache_flush_obsolete(spc, sp); + if (*sp) + DBUG_RETURN(SP_OK); + } + + switch ((ret= db_find_routine(thd, type, name, sp))) + { + 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); DBUG_RETURN(ret); } @@ -68,6 +68,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); @@ -86,22 +95,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); @@ -110,7 +154,7 @@ 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); sp_head * sp_load_for_information_schema(THD *thd, TABLE *proc_table, String *db, 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 8d06003e2e4..e50aa208798 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,11 @@ 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), unsafe_flags(0), m_recursion_level(0), m_next_cached_sp(0), + m_flags(0), + m_sp_cache_version(0), + unsafe_flags(0), + m_recursion_level(0), + m_next_cached_sp(0), m_cont_level(0) { const LEX_STRING str_reset= { NULL, 0 }; @@ -586,9 +591,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 +620,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 +732,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 +1173,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 +1242,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 +1254,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 +1319,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); @@ -2745,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. @@ -3997,6 +3982,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 */ @@ -4038,7 +4026,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 fd02c799975..d1e152765f2 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() {} }; @@ -180,12 +165,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; @@ -195,7 +174,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; /** Boolean combination of (1<<flag), where flag is a member of @@ -288,9 +294,6 @@ public: void set_stmt_end(THD *thd); - int - create(THD *thd); - virtual ~sp_head(); /// Free memory @@ -457,10 +460,11 @@ public: #endif /* - This method is intended for attributes of a routine which need to - propagate upwards to the LEX of the caller. + This method is intended for attributes of a routine which need + 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) { DBUG_ENTER("sp_head::propagate_attributes"); /* @@ -470,10 +474,10 @@ public: the substatements not). */ DBUG_PRINT("info", ("lex->get_stmt_unsafe_flags(): 0x%x", - lex->get_stmt_unsafe_flags())); + prelocking_ctx->get_stmt_unsafe_flags())); DBUG_PRINT("info", ("sp_head(0x%p=%s)->unsafe_flags: 0x%x", this, name(), unsafe_flags)); - lex->set_stmt_unsafe_flags(unsafe_flags); + prelocking_ctx->set_stmt_unsafe_flags(unsafe_flags); DBUG_VOID_RETURN; } diff --git a/sql/spatial.cc b/sql/spatial.cc index 671b8544b8a..9a31b099e92 100644 --- a/sql/spatial.cc +++ b/sql/spatial.cc @@ -1612,9 +1612,8 @@ int Gis_multi_polygon::area(double *ar, const char **end_of_data) const int Gis_multi_polygon::centroid(String *result) const { uint32 n_polygons; - bool first_loop= 1; Gis_polygon p; - double UNINIT_VAR(res_area), UNINIT_VAR(res_cx), UNINIT_VAR(res_cy); + double res_area= 0.0, res_cx= 0.0, res_cy= 0.0; double cur_area, cur_cx, cur_cy; const char *data= m_data; @@ -1631,20 +1630,13 @@ int Gis_multi_polygon::centroid(String *result) const p.centroid_xy(&cur_cx, &cur_cy)) return 1; - if (!first_loop) - { - double sum_area= res_area + cur_area; - res_cx= (res_area * res_cx + cur_area * cur_cx) / sum_area; - res_cy= (res_area * res_cy + cur_area * cur_cy) / sum_area; - } - else - { - first_loop= 0; - res_area= cur_area; - res_cx= cur_cx; - res_cy= cur_cy; - } + res_area+= cur_area; + res_cx+= cur_area * cur_cx; + res_cy+= cur_area * cur_cy; } + + res_cx/= res_area; + res_cy/= res_area; return create_point(result, res_cx, res_cy); } diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 012542a9467..fb257a6e5ec 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= TRUE; 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)) { @@ -723,7 +717,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); } @@ -1586,9 +1582,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 /* @@ -3113,14 +3107,15 @@ 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 row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); #ifdef HAVE_REPLICATION /* @@ -3137,7 +3132,9 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, if (!(thd->spcont || rpl_filter->tables_ok(0, tables))) { /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(FALSE); } } @@ -3153,7 +3150,9 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, { // Should never happen close_thread_tables(thd); /* purecov: deadcode */ /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(TRUE); /* purecov: deadcode */ } @@ -3281,7 +3280,9 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list, /* Tables are automatically closed */ thd->lex->restore_backup_query_tables_list(&backup); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } @@ -3340,14 +3341,15 @@ 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 row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); #ifdef HAVE_REPLICATION /* @@ -3364,7 +3366,9 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, if (!(thd->spcont || rpl_filter->tables_ok(0, tables))) { /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(FALSE); } } @@ -3374,7 +3378,9 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, { // Should never happen close_thread_tables(thd); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(TRUE); } @@ -3452,7 +3458,9 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc, mysql_rwlock_unlock(&LOCK_grant); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); /* Tables are automatically closed */ DBUG_RETURN(result); @@ -3490,14 +3498,15 @@ 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 row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); #ifdef HAVE_REPLICATION /* @@ -3514,7 +3523,9 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, if (!(thd->spcont || rpl_filter->tables_ok(0, tables))) { /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(FALSE); } } @@ -3524,7 +3535,9 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, { // This should never happen close_thread_tables(thd); /* purecov: deadcode */ /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(TRUE); /* purecov: deadcode */ } @@ -3585,7 +3598,9 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, if (!result) my_ok(thd); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } @@ -3833,11 +3848,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)) { @@ -3903,7 +3917,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. @@ -3934,7 +3950,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 @@ -5187,6 +5205,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 /* @@ -5797,14 +5816,16 @@ bool mysql_create_user(THD *thd, List <LEX_USER> &list) row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); /* CREATE USER may be skipped on replication client. */ if ((result= open_grant_tables(thd, tables))) { /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result != 1); } @@ -5850,7 +5871,9 @@ bool mysql_create_user(THD *thd, List <LEX_USER> &list) mysql_rwlock_unlock(&LOCK_grant); close_thread_tables(thd); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } @@ -5885,14 +5908,16 @@ bool mysql_drop_user(THD *thd, List <LEX_USER> &list) row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); /* DROP USER may be skipped on replication client. */ if ((result= open_grant_tables(thd, tables))) { /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result != 1); } @@ -5932,7 +5957,9 @@ bool mysql_drop_user(THD *thd, List <LEX_USER> &list) close_thread_tables(thd); thd->variables.sql_mode= old_sql_mode; /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } @@ -5967,14 +5994,16 @@ bool mysql_rename_user(THD *thd, List <LEX_USER> &list) row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); /* RENAME USER may be skipped on replication client. */ if ((result= open_grant_tables(thd, tables))) { /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result != 1); } @@ -6024,7 +6053,9 @@ bool mysql_rename_user(THD *thd, List <LEX_USER> &list) mysql_rwlock_unlock(&LOCK_grant); close_thread_tables(thd); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result); } @@ -6057,13 +6088,15 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); if ((result= open_grant_tables(thd, tables))) { /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result != 1); } @@ -6219,7 +6252,9 @@ bool mysql_revoke_all(THD *thd, List <LEX_USER> &list) if (result && !binlog_error) my_message(ER_REVOKE_GRANTS, ER(ER_REVOKE_GRANTS), MYF(0)); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(result || binlog_error); } @@ -6328,8 +6363,8 @@ bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name, row-based replication. The flag will be reset at the end of the statement. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); /* Remove procedure access */ do @@ -6366,7 +6401,9 @@ bool sp_revoke_privileges(THD *thd, const char *sp_db, const char *sp_name, thd->pop_internal_handler(); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(error_handler.has_errors()); } diff --git a/sql/sql_base.cc b/sql/sql_base.cc index eb59600b360..d5a664df0d0 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> @@ -95,55 +97,39 @@ 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 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; } @@ -151,7 +137,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)) { @@ -162,45 +149,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() @@ -264,13 +253,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; @@ -280,7 +268,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; @@ -290,13 +277,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; @@ -310,6 +322,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() @@ -335,16 +459,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))) @@ -353,12 +487,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 @@ -387,7 +515,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: @@ -395,20 +522,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); } @@ -419,22 +541,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)); @@ -451,13 +567,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. @@ -527,32 +646,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"); @@ -563,10 +670,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 { @@ -574,12 +682,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); } @@ -589,9 +695,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; } @@ -624,66 +728,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 @@ -714,11 +777,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; @@ -732,21 +793,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))) { @@ -757,8 +804,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; } @@ -781,7 +831,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; } @@ -801,22 +851,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; } @@ -836,6 +875,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 @@ -843,18 +919,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)); @@ -863,165 +944,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); } @@ -1074,7 +1136,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); @@ -1107,43 +1169,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. @@ -1171,6 +1244,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; @@ -1200,12 +1275,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 */ @@ -1216,6 +1289,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. @@ -1237,7 +1383,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 @@ -1247,6 +1392,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 @@ -1288,18 +1447,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 */ @@ -1308,8 +1468,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; /* @@ -1317,14 +1482,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 */ } @@ -1346,21 +1511,31 @@ void close_thread_tables(THD *thd) /* Note that we need to hold LOCK_open while changing the open_tables list. Another thread may work on it. - (See: remove_table_from_cache(), mysql_wait_completed_table()) + (See: mysql_notify_thread_having_shared_lock()) Closing a MERGE child before the parent would be fatal if the other thread tries to abort the MERGE lock in between. */ if (thd->open_tables) close_open_tables(thd); - if (prelocked_mode == PRELOCKED) + /* + - 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; @@ -1376,48 +1551,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); } @@ -1822,9 +1978,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 @@ -1863,7 +2020,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); } @@ -1880,19 +2037,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; @@ -1979,198 +2123,85 @@ 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. - - @param[in] thd thread context - @param[in] table table to prepare - @param[in,out] prev_pp pointer to pointer of previous table - - @detail - If the table is a MERGE parent, just detach the children. - If the table is a MERGE child, close the parent (incl. detach). -*/ + Force all other threads to stop using the table by upgrading + metadata lock on it and remove unused TABLE instances from cache. -static void unlink_open_merge(THD *thd, TABLE *table, TABLE ***prev_pp) -{ - DBUG_ENTER("unlink_open_merge"); + @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 - 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); - } + @note When returning, the table will be unusable for other threads + until metadata lock is downgraded. - 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; + 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)); - 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); - - /* 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, thd->variables.lock_wait_timeout)) + 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; } @@ -2219,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. @@ -2456,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) @@ -2510,6 +2313,101 @@ 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, + thd->variables.lock_wait_timeout)) + 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_OPEN_FORCE_SHARED_HIGH_PRIO_MDL))); + + mdl_request->set_type(MDL_SHARED); + } + else if (flags & MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL) + { + 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. @@ -2517,53 +2415,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); @@ -2575,7 +2485,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) { @@ -2597,7 +2508,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; @@ -2607,10 +2518,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); } /* @@ -2620,7 +2537,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; @@ -2629,17 +2547,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 @@ -2673,6 +2598,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")); @@ -2682,29 +2614,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); } } /* @@ -2714,30 +2652,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); /* @@ -2754,234 +2699,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); + + /* make a new table */ + if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME)))) + goto err_unlock; - if (table_list->create) + 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: @@ -2998,7 +2935,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; @@ -3017,17 +2953,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)) @@ -3037,682 +2994,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) + +void Locked_tables_list::unlink_from_list(THD *thd, + TABLE_LIST *table_list, + bool remove_from_locked_tables) { - bool found= send_refresh; - DBUG_ENTER("close_old_data_files"); + /* + 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); - for (; table ; table=table->next) + /* Clear the pointer, the table will be returned to the table cache. */ + table_list->table->pos_in_locked_tables= NULL; + + /* Mark the table as closed in the locked tables list. */ + table_list->table= NULL; + + /* + 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) { - 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; - } - } + *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; } - if (found) - broadcast_refresh(); - DBUG_VOID_RETURN; } +/** + 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. -/* - 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 + @note This function is a no-op if we're not under LOCK TABLES. */ -bool table_is_used(TABLE *table, bool wait_for_name_lock) +void Locked_tables_list:: +unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count) { - DBUG_ENTER("table_is_used"); - do + /* If we managed to take a lock, unlock tables and free the lock. */ + if (lock) + mysql_unlock_tables(thd, lock); + /* + 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(). + */ + if (reopen_count) { - 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)) + mysql_mutex_lock(&LOCK_open); + while (reopen_count--) { - 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) + 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 ( (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); -} - - -/* Wait until all used tables are refreshed */ + DBUG_ASSERT(thd->open_tables == m_reopen_array[reopen_count]); -bool wait_for_tables(THD *thd) -{ - bool result; - DBUG_ENTER("wait_for_tables"); + thd->open_tables->pos_in_locked_tables->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); + close_thread_table(thd, &thd->open_tables); + } + broadcast_refresh(); + mysql_mutex_unlock(&LOCK_open); } - if (thd->killed) - result= 1; // aborted - else + /* 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) { - /* 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); + 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; + } } - mysql_mutex_unlock(&LOCK_open); - thd_proc_info(thd, 0); - DBUG_RETURN(result); } -/* - drop tables from locked list - - SYNOPSIS - drop_locked_tables() - thd Thread thandler - db Database - table_name Table name - - INFORMATION - This is only called on drop tables +/** + Reopen the tables locked with LOCK TABLES and temporarily closed + by a DDL statement or FLUSH TABLES. - The TABLE object for the dropped table is unlocked but still kept around - as a name lock, which means that the table will be available for other - thread as soon as we call unlock_table_names(). - If there is multiple copies of the table locked, all copies except - the first, which acts as a name lock, is removed. + @note This function is a no-op if we're not under LOCK TABLES. - RETURN - # If table existed, return table - 0 Table was not locked + @return TRUE if an error reopening the tables. May happen in + case of some fatal system error only, e.g. a disk + corruption, out of memory or a serious bug in the + locking. */ - -TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name) +bool +Locked_tables_list::reopen_tables(THD *thd) { - TABLE *table,*next,**prev, *found= 0; - prev= &thd->open_tables; - DBUG_ENTER("drop_locked_tables"); + Open_table_context ot_ctx_unused(thd); + bool lt_refresh_unused; + size_t reopen_count= 0; + MYSQL_LOCK *lock; + MYSQL_LOCK *merged_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. - */ - for (table= thd->open_tables; table ; table=next) + for (TABLE_LIST *table_list= m_locked_tables; + table_list; table_list= table_list->next_global) { - next=table->next; - if (!strcmp(table->s->table_name.str, table_name) && - !strcmp(table->s->db.str, db)) - { - /* If MERGE child, forward lock handling to parent. */ - mysql_lock_remove(thd, thd->locked_tables, - table->parent ? table->parent : table, TRUE); - /* - When closing a MERGE parent or child table, detach the children first. - Clear child table references in case this object is opened again. - */ - if (table->child_l || table->parent) - detach_merge_children(table, TRUE); + if (table_list->table) /* The table was not closed */ + continue; - 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 + /* Links into thd->open_tables upon success */ + if (open_table(thd, table_list, thd->mem_root, &ot_ctx_unused, + MYSQL_OPEN_REOPEN)) { - *prev=table; - prev= &table->next; + unlink_all_closed_tables(thd, 0, reopen_count); + return TRUE; } - } - *prev=0; - if (found) - broadcast_refresh(); - if (thd->locked_tables && thd->locked_tables->table_count == 0) - { - my_free((uchar*) thd->locked_tables,MYF(0)); - thd->locked_tables=0; - } - DBUG_RETURN(found); -} + 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; - -/* - If we have the table open, which only happens when a LOCK TABLE has been - done on the table, change the lock type to a lock that will abort all - other threads trying to get the lock. -*/ - -void abort_locked_tables(THD *thd,const char *db, const char *table_name) -{ - TABLE *table; - for (table= thd->open_tables; table ; table= table->next) + DBUG_ASSERT(reopen_count < m_locked_tables_count); + m_reopen_array[reopen_count++]= table_list->table; + } + if (reopen_count) { - if (!strcmp(table->s->table_name.str, table_name) && - !strcmp(table->s->db.str, db)) - { - /* If MERGE child, forward lock handling to parent. */ - mysql_lock_abort(thd, table->parent ? table->parent : table, TRUE); - break; + 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; } @@ -3818,7 +3467,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) { @@ -3840,210 +3489,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,7 +3643,7 @@ retry: FALSE, FALSE, FALSE, errcode)) { my_free(query, MYF(0)); - goto err; + return TRUE; } my_free(query, MYF(0)); } @@ -4080,352 +3656,272 @@ 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)) - { - (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) + if (m_global_mdl_request) + m_global_mdl_request->ticket= NULL; + /* Execute the action. */ + switch (m_action) + { + case OT_WAIT_MDL_LOCK: + result= thd->mdl_context.wait_for_lock(mdl_request, + thd->variables.lock_wait_timeout); + 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, + thd->variables.lock_wait_timeout))) 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. + 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); - @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. + 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; - 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. + mdl_global_request.init(MDL_key::GLOBAL, "", "", + MDL_INTENTION_EXCLUSIVE); + mdl_xlock_request.set_type(MDL_EXCLUSIVE); - If a '*_last' pointer is NULL, the respective list is assumed to be - NULL terminated. -*/ + mdl_requests.push_front(&mdl_xlock_request); + mdl_requests.push_front(&mdl_global_request); -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)); - - /* 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, + thd->variables.lock_wait_timeout))) + 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; } @@ -4463,49 +3959,522 @@ 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) + { + /* + We still need to take a MDL lock on the merged view to protect + it from concurrent changes. + */ + if (!open_table_get_mdl_lock(thd, tables, &tables->mdl_request, + ot_ctx, flags)) + goto process_view_routines; + /* Fall-through to return error. */ + } + else 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, + thd->variables.lock_wait_timeout)) + 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 @@ -4513,310 +4482,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()) + /* Schema tables may not have a TABLE object here. */ + if (tbl && tbl->file->ht->db_type == DB_TYPE_MRG_MYISAM) { - /* 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)) + /* 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; } @@ -4861,6 +4918,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 @@ -4886,7 +4945,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"); @@ -4902,7 +4961,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. */ @@ -4940,23 +4999,44 @@ 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) + /* + 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 */ @@ -4968,9 +5048,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; @@ -4981,70 +5060,97 @@ 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; + 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); } @@ -5109,6 +5215,7 @@ static void mark_real_tables_as_free_for_reuse(TABLE_LIST *table) 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 @@ -5120,16 +5227,17 @@ static void mark_real_tables_as_free_for_reuse(TABLE_LIST *table) 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; @@ -5138,29 +5246,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(thd->decide_logging_format(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()) @@ -5170,8 +5279,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. @@ -5180,24 +5287,16 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) */ if (thd->variables.binlog_format != BINLOG_FORMAT_ROW && tables && has_write_table_with_auto_increment(thd->lex->first_not_own_table())) - { - thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_TWO_AUTOINC_COLUMNS); - thd->set_current_stmt_binlog_format_row_if_mixed(); - } + thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_AUTOINC_COLUMNS); } 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) @@ -5207,17 +5306,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". @@ -5234,10 +5322,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); } } } @@ -5246,8 +5333,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 @@ -5272,7 +5359,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) @@ -5282,14 +5369,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); } } /* @@ -5300,8 +5387,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; } } @@ -5309,30 +5397,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); } @@ -8239,36 +8356,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 @@ -8280,167 +8367,238 @@ 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)) - { - DBUG_PRINT("info",("Table was not in use")); - relink_unused(table); - } - else if (in_use != thd) + 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 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); - } + DBUG_ASSERT(share->used_tables.is_empty()); } - else + else if (remove_type == TDC_RT_REMOVE_NOT_OWN) { - 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); + } +} + + +/** + 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. +*/ - 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))) +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; + struct timespec abstime; + set_timespec(abstime, thd->variables.lock_wait_timeout); + int wait_result= 0; + + 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++)) { - 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); - } - } + /* Skip requests on non-TDC objects. */ + if (mdl_request->key.mdl_namespace() != MDL_key::TABLE) + continue; - if (result && (flags & RTFC_WAIT_OTHER_THREAD_FLAG)) + if ((share= get_cached_table_share(mdl_request->key.db_name(), + mdl_request->key.name())) && + share->version != refresh_version) + break; + } + if (!mdl_request) { /* - Signal any thread waiting for tables to be freed to - reopen their tables + Reset wait_result here in case this was the final check + after getting a timeout from mysql_cond_timedwait(). */ - 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--; - continue; - } + wait_result= 0; + mysql_mutex_unlock(&LOCK_open); + break; } - break; + if (wait_result == ETIMEDOUT || wait_result == ETIME) + { + /* + Test for timeout here instead of right after mysql_cond_timedwait(). + This allows for a final iteration and a final check before reporting + ER_LOCK_WAIT_TIMEOUT. + */ + mysql_mutex_unlock(&LOCK_open); + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); + break; + } + old_msg= thd->enter_cond(&COND_refresh, &LOCK_open, "Waiting for table"); + wait_result= mysql_cond_timedwait(&COND_refresh, &LOCK_open, &abstime); + /* LOCK_open mutex is unlocked by THD::exit_cond() as side-effect. */ + thd->exit_cond(old_msg); } - DBUG_RETURN(result); + return thd->killed || wait_result == ETIMEDOUT || wait_result == ETIME; } @@ -8541,7 +8699,6 @@ open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, } err: - bzero(outparam, sizeof(TABLE)); // do not run repair DBUG_RETURN(1); } @@ -8574,149 +8731,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 -*/ - -/* 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 */ + Tells if two (or more) tables have auto_increment columns and we want to + lock those tables with a write lock. - -/* 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 @@ -8766,40 +8795,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); @@ -8814,13 +8841,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); @@ -8870,7 +8897,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 | @@ -8919,49 +8946,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_builtin.cc.in b/sql/sql_builtin.cc.in index f8767949fe3..d9d9b610baf 100644 --- a/sql/sql_builtin.cc.in +++ b/sql/sql_builtin.cc.in @@ -17,7 +17,12 @@ typedef struct st_mysql_plugin builtin_plugin[]; -extern builtin_plugin +#ifdef _MSC_VER +extern "C" +#else +extern +#endif +builtin_plugin @mysql_mandatory_plugins@ @mysql_optional_plugins@ builtin_binlog_plugin; struct st_mysql_plugin *mysql_optional_plugins[]= @@ -29,5 +34,3 @@ struct st_mysql_plugin *mysql_mandatory_plugins[]= { builtin_binlog_plugin, @mysql_mandatory_plugins@ 0 }; - - 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 e8a00974a56..b7ded3b632f 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -44,6 +44,7 @@ #include "sp_rcontext.h" #include "sp_cache.h" +#include "transaction.h" #include "debug_sync.h" /* @@ -204,12 +205,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 */ @@ -254,13 +249,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); @@ -447,7 +445,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), @@ -461,7 +459,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), @@ -479,6 +476,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 @@ -490,7 +488,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; @@ -549,6 +547,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); @@ -986,29 +987,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); @@ -1044,6 +1057,7 @@ THD::~THD() if (!cleanup_done) cleanup(); + mdl_context.destroy(); ha_close_connection(this); mysql_audit_release(this); plugin_thdvar_cleanup(this); @@ -1412,8 +1426,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; @@ -1736,7 +1749,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; @@ -3014,28 +3027,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; } @@ -3296,6 +3312,22 @@ void THD::set_query_id(query_id_t new_query_id) /** + Leave explicit LOCK TABLES or prelocked mode and restore value of + transaction sentinel in MDL subsystem. +*/ + +void THD::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()); + /* Also ensure that we don't release metadata locks for open HANDLERs. */ + if (handler_tables_hash.records) + mysql_ha_move_tickets_after_trans_sentinel(this); +} + + +/** Mark transaction to rollback and mark error as fatal to a sub-statement. @param thd Thread handle @@ -3536,7 +3568,7 @@ int THD::decide_logging_format(TABLE_LIST *tables) { DBUG_ENTER("THD::decide_logging_format"); DBUG_PRINT("info", ("query: %s", query())); - DBUG_PRINT("info", ("variables.binlog_format: %ld", + DBUG_PRINT("info", ("variables.binlog_format: %u", variables.binlog_format)); DBUG_PRINT("info", ("lex->get_stmt_unsafe_flags(): 0x%x", lex->get_stmt_unsafe_flags())); @@ -3573,7 +3605,7 @@ int THD::decide_logging_format(TABLE_LIST *tables) "PRELOCKED_UNDER_LOCK_TABLES", }; DBUG_PRINT("debug", ("prelocked_mode: %s", - prelocked_mode_name[prelocked_mode])); + prelocked_mode_name[locked_tables_mode])); } #endif @@ -3677,7 +3709,7 @@ int THD::decide_logging_format(TABLE_LIST *tables) lock history on the slave will be different from the master. */ if (mixed_engine || - trans_has_updated_trans_table(this) && !all_trans_engines) + (trans_has_updated_trans_table(this) && !all_trans_engines)) lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_NONTRANS_AFTER_TRANS); DBUG_PRINT("info", ("flags_all_set: 0x%llx", flags_all_set)); @@ -3807,7 +3839,7 @@ int THD::decide_logging_format(TABLE_LIST *tables) DBUG_PRINT("info", ("decision: no logging since " "mysql_bin_log.is_open() = %d " "and (options & OPTION_BIN_LOG) = 0x%llx " - "and binlog_format = %ld " + "and binlog_format = %u " "and binlog_filter->db_ok(db) = %d", mysql_bin_log.is_open(), (variables.option_bits & OPTION_BIN_LOG), @@ -4405,7 +4437,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, is_trans)) DBUG_RETURN(error); diff --git a/sql/sql_class.h b/sql/sql_class.h index 7bebc639c37..2efff63354a 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -26,6 +26,7 @@ #include <mysql/plugin_audit.h> #include "log.h" #include "rpl_tblmap.h" +#include "mdl.h" class Reprepare_observer; @@ -38,6 +39,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 }; @@ -340,6 +342,7 @@ typedef struct system_variables ulong auto_increment_increment, auto_increment_offset; ulong bulk_insert_buff_size; ulong join_buff_size; + ulong lock_wait_timeout; ulong max_allowed_packet; ulong max_error_count; ulong max_length_for_sort_data; @@ -765,6 +768,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}; @@ -848,12 +853,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 +}; /** @@ -892,11 +902,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 @@ -906,19 +911,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 @@ -928,29 +927,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; @@ -962,30 +966,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 @@ -1148,6 +1180,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. */ @@ -1173,6 +1416,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); /** @@ -1185,6 +1467,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; @@ -1478,6 +1762,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; @@ -1697,7 +1982,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) @@ -1722,8 +2007,6 @@ public: char scramble[SCRAMBLE_LENGTH+1]; 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 locked, some_tables_deleted; bool last_cuted_field; bool no_errors, password; @@ -1834,6 +2117,8 @@ public: */ Parser_state *m_parser_state; + Locked_tables_list locked_tables_list; + #ifdef WITH_PARTITION_STORAGE_ENGINE partition_info *work_part_info; #endif @@ -1856,7 +2141,6 @@ public: /* Debug Sync facility. See debug_sync.cc. */ struct st_debug_sync_control *debug_sync_control; #endif /* defined(ENABLED_DEBUG_SYNC) */ - THD(); ~THD(); @@ -1969,11 +2253,11 @@ public: } /** 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. @@ -2009,7 +2293,7 @@ public: void add_changed_table(const char *key, long key_length); CHANGED_TABLE_LIST * changed_table_dup(const char *key, long key_length); int send_explain_fields(select_result *result); -#ifndef EMBEDDED_LIBRARY + /** Clear the current error, if any. We do not clear is_fatal_error or is_fatal_sub_stmt_error since we @@ -2025,6 +2309,7 @@ public: is_slave_error= 0; DBUG_VOID_RETURN; } +#ifndef EMBEDDED_LIBRARY inline bool vio_ok() const { return net.vio != 0; } /** Return FALSE if connection to client is broken. */ bool is_connected() @@ -2032,7 +2317,6 @@ public: return vio_ok() ? vio_is_connected(net.vio) : FALSE; } #else - void clear_error(); inline bool vio_ok() const { return TRUE; } inline bool is_connected() { return TRUE; } #endif @@ -2043,7 +2327,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")); } @@ -2119,8 +2403,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); @@ -2395,6 +2679,14 @@ 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); + + mdl_context.set_trans_sentinel(); + locked_tables_mode= mode_arg; + } + void leave_locked_tables_mode(); int decide_logging_format(TABLE_LIST *tables); private: @@ -3117,6 +3409,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. @@ -3128,6 +3450,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 1be84751581..75855020127 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -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; @@ -785,7 +785,7 @@ not_silent: 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 +812,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); @@ -861,7 +861,7 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info) 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); } @@ -906,7 +906,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; @@ -934,10 +934,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); @@ -1072,7 +1068,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); } @@ -1187,6 +1183,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 */ @@ -1995,8 +1996,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); diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 1d1f673d126..ea466da8ea1 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. @@ -1051,11 +1052,21 @@ static bool mysql_truncate_by_delete(THD *thd, TABLE_LIST *table_list) bool error, save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); DBUG_ENTER("mysql_truncate_by_delete"); table_list->lock_type= TL_WRITE; + table_list->mdl_request.set_type(MDL_SHARED_WRITE); mysql_init_select(thd->lex); thd->clear_current_stmt_binlog_format_row(); + /* 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); + } if (save_binlog_row_based) thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(error); @@ -1071,7 +1082,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) @@ -1079,15 +1091,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))) @@ -1100,7 +1119,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); @@ -1127,6 +1146,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) { @@ -1152,13 +1177,56 @@ 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, + thd->variables.lock_wait_timeout)) + 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, @@ -1169,6 +1237,12 @@ 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. */ @@ -1177,16 +1251,13 @@ end: if (!error) 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..4a69b46ddb7 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,28 @@ 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(); + tables->table->open_by_handler= 0; + 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 +141,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 +177,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 +207,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,10 +218,50 @@ 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 @@ -236,111 +269,79 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) 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 prevents opening of views and merge tables. + For temporary tables, TABLE::next can be set even if only one table + was opened for HANDLER as it is used to link them together + (see thd->temporary_tables). + */ + DBUG_ASSERT(hash_tables->table->next == NULL || + hash_tables->table->s->tmp_table); + /* 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. + being used by this handler. For non-temp tables we use this flag + in asserts. */ - 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 +369,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 +388,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 +443,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 +480,42 @@ 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 || + hash_tables->table->s->tmp_table); /* 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); - /* restore previous context */ + lock= mysql_lock_tables(thd, &thd->open_tables, 1, 0, &need_reopen); + + /* + 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 +523,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 +737,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 +754,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 +784,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 +829,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); @@ -814,3 +837,27 @@ void mysql_ha_cleanup(THD *thd) DBUG_VOID_RETURN; } + +/** + Move tickets for metadata locks corresponding to open HANDLERs + after transaction sentinel in order to protect them from being + released at the end of transaction. + + @param thd Thread identifier. +*/ + +void mysql_ha_move_tickets_after_trans_sentinel(THD *thd) +{ + TABLE_LIST *hash_tables; + DBUG_ENTER("mysql_ha_move_tickets_after_trans_sentinel"); + + 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->mdl_ticket) + thd->mdl_context. + move_ticket_after_trans_sentinel(hash_tables->table->mdl_ticket); + } + DBUG_VOID_RETURN; +} + 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 6d8af8fbd40..45c9c0363dd 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" #include "sql_audit.h" #ifndef EMBEDDED_LIBRARY @@ -442,7 +443,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; @@ -519,7 +520,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 @@ -628,8 +629,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); @@ -757,7 +760,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); } @@ -881,7 +891,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; @@ -1778,7 +1789,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; @@ -1786,8 +1797,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) { DBUG_ENTER("Delayed_insert constructor"); thd.security_ctx->user=thd.security_ctx->priv_user=(char*) delayed_user; @@ -1813,6 +1823,12 @@ public: */ thd.lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_INSERT_DELAYED); thd.set_current_stmt_binlog_format_row_if_mixed(); + /* + Prevent changes to global.lock_wait_timeout from affecting + delayed insert threads as any timeouts in delayed inserts + are not communicated to the client. + */ + thd.variables.lock_wait_timeout= LONG_TIMEOUT; bzero((char*) &thd.net, sizeof(thd.net)); // Safety bzero((char*) &table_list, sizeof(table_list)); // Safety @@ -1873,6 +1889,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); }; @@ -1946,9 +1963,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 @@ -2020,6 +2036,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()) @@ -2027,20 +2048,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); @@ -2097,17 +2117,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; } } @@ -2349,6 +2385,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 */ @@ -2411,29 +2483,10 @@ pthread_handler_t handle_delayed_insert(void *arg) thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_INSERT_DELAYED); thd->set_current_stmt_binlog_format_row_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); @@ -2443,7 +2496,7 @@ pthread_handler_t handle_delayed_insert(void *arg) for (;;) { - if (thd->killed == THD::KILL_CONNECTION) + if (thd->killed) { uint lock_count; /* @@ -2460,7 +2513,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); @@ -2471,7 +2525,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; mysql_audit_release(thd); @@ -2488,13 +2542,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); @@ -2506,9 +2555,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. @@ -2521,11 +2570,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); } @@ -2534,7 +2600,6 @@ pthread_handler_t handle_delayed_insert(void *arg) if (di->handle_inserts()) { /* Some fatal error */ - di->dead= 1; thd->killed= THD::KILL_CONNECTION; } } @@ -2554,7 +2619,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_audit_release(thd); mysql_mutex_lock(&di->mutex); @@ -2575,7 +2640,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; } @@ -2587,7 +2652,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); @@ -2650,7 +2714,8 @@ bool Delayed_insert::handle_inserts(void) table->use_all_columns(); thd_proc_info(&thd, "upgrading lock"); - if (thr_upgrade_write_delay_lock(*thd.lock->locks, delayed_lock)) + if (thr_upgrade_write_delay_lock(*thd.lock->locks, delayed_lock, + thd.variables.lock_wait_timeout)) { /* This can happen if thread is killed either by a shutdown @@ -2664,7 +2729,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 @@ -2835,7 +2900,8 @@ bool Delayed_insert::handle_inserts(void) goto err; } query_cache_invalidate3(&thd, table, 1); - if (thr_reschedule_write_lock(*thd.lock->locks)) + if (thr_reschedule_write_lock(*thd.lock->locks, + thd.variables.lock_wait_timeout)) { /* This is not known to happen. */ my_error(ER_DELAYED_CANT_CHANGE_LOCK,MYF(ME_FATALERROR), @@ -3093,7 +3159,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 @@ -3159,7 +3225,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); } @@ -3286,7 +3352,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); @@ -3373,7 +3440,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(); /* @@ -3421,46 +3488,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, @@ -3535,14 +3599,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 @@ -3557,22 +3619,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 @@ -3581,9 +3649,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); } @@ -3592,6 +3661,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)) @@ -3601,9 +3675,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); @@ -3637,18 +3709,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= thd->decide_logging_format(&all_tables)) + TABLE_LIST *save_next_global= create_table->next_global; + + create_table->next_global= select_tables; + + error= thd->decide_logging_format(create_table); + + create_table->next_global= save_next_global; + + if (error) return error; TABLE const *const table = *tables; @@ -3663,7 +3745,8 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u) } select_create *ptr; - TABLE_LIST all_tables; + TABLE_LIST *create_table; + TABLE_LIST *select_tables; }; MY_HOOKS hooks(this, create_table, select_tables); @@ -3685,8 +3768,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) @@ -3751,7 +3833,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 & @@ -3868,8 +3950,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); diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 0d423ba85eb..6da734592dc 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -48,7 +48,7 @@ Query_tables_list::binlog_stmt_unsafe_errcode[BINLOG_STMT_UNSAFE_COUNT] = ER_BINLOG_UNSAFE_LIMIT, ER_BINLOG_UNSAFE_INSERT_DELAYED, ER_BINLOG_UNSAFE_SYSTEM_TABLE, - ER_BINLOG_UNSAFE_TWO_AUTOINC_COLUMNS, + ER_BINLOG_UNSAFE_AUTOINC_COLUMNS, ER_BINLOG_UNSAFE_UDF, ER_BINLOG_UNSAFE_SYSTEM_VARIABLE, ER_BINLOG_UNSAFE_SYSTEM_FUNCTION, diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 800a16cf2b6..7ec87806ea5 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -1076,6 +1076,16 @@ 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; + } /** Enumeration listing of all types of unsafe statement. @@ -1101,13 +1111,15 @@ public: */ BINLOG_STMT_UNSAFE_SYSTEM_TABLE, /** - Update of two autoincrement columns is unsafe. With one - autoincrement column, we store the counter in the binlog so that - slave can restore the correct value. But we can only store one - such counter per statement, so updating more than one - autoincrement column is not safe. + Inserting into an autoincrement column in a stored routine is unsafe. + Even with just one autoincrement column, if the routine is invoked more than + once slave is not guaranteed to execute the statement graph same way as + the master. + And since it's impossible to estimate how many times a routine can be invoked at + the query pre-execution phase (see lock_tables), the statement is marked + pessimistically unsafe. */ - BINLOG_STMT_UNSAFE_TWO_AUTOINC_COLUMNS, + BINLOG_STMT_UNSAFE_AUTOINC_COLUMNS, /** Using a UDF (user-defined function) is unsafe. */ @@ -1903,7 +1915,9 @@ struct LEX: public Query_tables_list uint profile_options; uint uint_geom_type; uint grant, grant_tot_col, which_columns; - uint fk_delete_opt, fk_update_opt, fk_match_option; + enum Foreign_key::fk_match_opt fk_match_option; + enum Foreign_key::fk_option fk_update_opt; + enum Foreign_key::fk_option fk_delete_opt; uint slave_thd_opt, start_transaction_opt; int nest_level; /* @@ -1938,6 +1952,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 b8ba16f2fb1..1eabce0b474 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -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; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 73ed2e3f017..714e3af5296 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_audit.h" #include "sql_prepare.h" #include "probes_mysql.h" @@ -90,127 +91,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 /** @@ -237,6 +117,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. @@ -251,45 +167,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; @@ -304,34 +242,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 | @@ -340,22 +279,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; } @@ -636,80 +606,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 /** @@ -929,29 +825,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 @@ -1252,6 +1132,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; @@ -1302,6 +1183,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); @@ -1321,13 +1206,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 @@ -1483,7 +1373,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(); @@ -1877,7 +1767,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) */ @@ -2061,10 +1950,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: @@ -2074,9 +1993,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: { @@ -2133,8 +2053,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); @@ -2341,20 +2261,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 @@ -2415,14 +2326,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; @@ -2434,6 +2341,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; @@ -2493,13 +2410,11 @@ case SQLCOM_PREPARE: if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) { - lex->link_first_table_back(create_table, link_to_local); - create_table->create= TRUE; /* Base table and temporary table are not in the same name space. */ - create_table->skip_temporary= 1; + create_table->open_type= OT_BASE_ONLY; } - 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 @@ -2508,7 +2423,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); @@ -2535,6 +2449,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. */ @@ -2553,10 +2474,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 { @@ -2565,21 +2485,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: @@ -2604,8 +2525,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 @@ -2645,7 +2564,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)); @@ -2720,16 +2640,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, @@ -2771,7 +2681,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; } @@ -2818,7 +2728,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 { @@ -2933,9 +2844,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()); @@ -2966,15 +2874,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 @@ -3074,12 +2973,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, @@ -3114,12 +3007,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()); @@ -3172,11 +3059,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; @@ -3184,13 +3066,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; @@ -3202,12 +3084,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, @@ -3223,13 +3099,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; @@ -3284,8 +3153,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 { @@ -3359,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); @@ -3373,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; @@ -3410,51 +3270,73 @@ 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); /* 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: { @@ -3464,11 +3346,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)) @@ -3494,17 +3371,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); @@ -3528,7 +3406,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)); @@ -3540,11 +3418,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) || @@ -3567,7 +3440,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, @@ -3607,7 +3480,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)); @@ -3707,8 +3580,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); @@ -3719,8 +3590,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); @@ -3731,8 +3600,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); @@ -3740,8 +3607,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; @@ -3753,9 +3618,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, @@ -3950,128 +3812,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: @@ -4107,9 +3893,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) @@ -4127,7 +3910,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 @@ -4138,6 +3921,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. @@ -4209,7 +4002,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. @@ -4305,67 +4097,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: @@ -4385,10 +4132,57 @@ create_sp_error: case SQLCOM_DROP_PROCEDURE: case SQLCOM_DROP_FUNCTION: { +#ifdef HAVE_DLOPEN + if (lex->sql_command == SQLCOM_DROP_FUNCTION && + ! lex->spname->m_explicit_name) + { + /* DROP FUNCTION <non qualified name> */ + udf_func *udf = find_udf(lex->spname->m_name.str, + lex->spname->m_name.length); + if (udf) + { + if (check_access(thd, DELETE_ACL, "mysql", NULL, NULL, 1, 0)) + goto error; + + if (!(res = mysql_drop_function(thd, &lex->spname->m_name))) + { + my_ok(thd); + break; + } + my_error(ER_SP_DROP_FAILED, MYF(0), + "FUNCTION (UDF)", lex->spname->m_name.str); + goto error; + } + + if (lex->spname->m_db.str == NULL) + { + if (lex->drop_if_exists) + { + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST), + "FUNCTION (UDF)", lex->spname->m_name.str); + res= FALSE; + my_ok(thd); + break; + } + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), + "FUNCTION (UDF)", lex->spname->m_name.str); + goto error; + } + /* Fall thought to test for a stored function */ + } +#endif + int sp_result; 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) @@ -4400,53 +4194,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 - { -#ifdef HAVE_DLOPEN - if (lex->sql_command == SQLCOM_DROP_FUNCTION) - { - udf_func *udf = find_udf(lex->spname->m_name.str, - lex->spname->m_name.length); - if (udf) - { - if (check_access(thd, DELETE_ACL, "mysql", NULL, NULL, 1, 0)) - goto error; - - if (!(res = mysql_drop_function(thd, &lex->spname->m_name))) - { - my_ok(thd); - break; - } - } - } -#endif - 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; - } } res= sp_result; switch (sp_result) { @@ -4459,7 +4230,7 @@ create_sp_error: res= 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); + SP_COM_STRING(lex), lex->spname->m_qname.str); if (!res) my_ok(thd); break; @@ -4477,21 +4248,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: @@ -4499,13 +4262,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 */ @@ -4539,16 +4300,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); @@ -4556,9 +4313,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); @@ -4566,193 +4320,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); @@ -4887,14 +4484,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()); } @@ -5572,7 +5184,6 @@ bool check_global_access(THD *thd, ulong want_access) Check stack size; Send error if there isn't enough stack to continue ****************************************************************************/ -#ifndef EMBEDDED_LIBRARY #if STACK_DIRECTION < 0 #define used_stack(A,B) (long) (A - B) @@ -5610,7 +5221,7 @@ bool check_stack_overrun(THD *thd, long margin, #endif return 0; } -#endif /* EMBEDDED_LIBRARY */ + #define MY_YACC_INIT 1000 // Start with big alloc #define MY_YACC_MAX 32000 // Because of 'short' @@ -5695,7 +5306,7 @@ void THD::reset_for_next_command() 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; @@ -5933,9 +5544,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); @@ -6362,6 +5970,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); } @@ -6597,6 +6208,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; } @@ -6906,6 +6519,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) @@ -6915,46 +6534,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(); @@ -7427,6 +7065,9 @@ bool multi_delete_set_locks_and_link_aux_tables(LEX *lex) } walk->updating= target_tbl->updating; walk->lock_type= target_tbl->lock_type; + /* We can assume that tables to be deleted from are locked for write. */ + DBUG_ASSERT(walk->lock_type >= TL_WRITE_ALLOW_WRITE); + walk->mdl_request.set_type(MDL_SHARED_WRITE); target_tbl->correspondent_table= walk; // Remember corresponding table } DBUG_RETURN(FALSE); @@ -7518,6 +7159,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 90d5c553cdf..d400e96ce7e 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" @@ -4230,39 +4231,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); @@ -4369,7 +4353,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) @@ -4383,16 +4366,12 @@ 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) && @@ -4559,12 +4538,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 && @@ -6269,30 +6247,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"); } /* @@ -6306,17 +6267,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); } @@ -6483,6 +6466,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; @@ -6582,10 +6566,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. @@ -6618,7 +6602,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") || @@ -6660,14 +6644,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 @@ -6685,7 +6670,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") || @@ -6701,7 +6686,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; @@ -6748,23 +6733,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") || @@ -6775,7 +6755,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") || @@ -6803,7 +6783,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 442c342fd31..f0d9560dff4 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -1454,10 +1454,7 @@ static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv) new_thd->db= my_strdup("mysql", MYF(0)); new_thd->db_length= 5; bzero((char*) &thd.net, sizeof(thd.net)); - 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 /* @@ -1744,9 +1741,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); @@ -1819,9 +1814,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 12de2bbcecc..31e3baca764 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,35 +1662,40 @@ 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) { + /* Base table and temporary table are not in the same name space. */ if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)) - { - lex->link_first_table_back(create_table, link_to_local); - create_table->create= TRUE; - /* Base table and temporary table are not in the same name space. */ - create_table->skip_temporary= true; - } + create_table->open_type= OT_BASE_ONLY; - 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 @@ -1697,12 +1703,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); } @@ -1732,7 +1737,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; @@ -2141,9 +2146,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)) @@ -2422,6 +2424,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 @@ -2515,9 +2524,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; @@ -2967,8 +2973,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); @@ -3133,6 +3138,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. @@ -3191,6 +3197,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 @@ -3214,6 +3226,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; @@ -3223,20 +3242,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 @@ -3412,7 +3417,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; @@ -3436,7 +3441,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 */ @@ -3577,13 +3582,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); @@ -3643,19 +3647,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..74d7beede47 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. */ @@ -134,6 +131,26 @@ int make_profile_table_for_show(THD *thd, ST_SCHEMA_TABLE *schema_table) #define RUSAGE_USEC(tv) ((tv).tv_sec*1000*1000 + (tv).tv_usec) #define RUSAGE_DIFF_USEC(tv1, tv2) (RUSAGE_USEC((tv1))-RUSAGE_USEC((tv2))) +#ifdef _WIN32 +static ULONGLONG FileTimeToQuadWord(FILETIME *ft) +{ + // Overlay FILETIME onto a ULONGLONG. + union { + ULONGLONG qwTime; + FILETIME ft; + } u; + + u.ft = *ft; + return u.qwTime; +} + + +// Get time difference between to FILETIME objects in seconds. +static double GetTimeDiffInSeconds(FILETIME *a, FILETIME *b) +{ + return ((FileTimeToQuadWord(a) - FileTimeToQuadWord(b)) / 1e7); +} +#endif PROF_MEASUREMENT::PROF_MEASUREMENT(QUERY_PROFILE *profile_arg, const char *status_arg) @@ -224,6 +241,12 @@ void PROF_MEASUREMENT::collect() time_usecs= (double) my_getsystime() / 10.0; /* 1 sec was 1e7, now is 1e6 */ #ifdef HAVE_GETRUSAGE getrusage(RUSAGE_SELF, &rusage); +#elif defined(_WIN32) + FILETIME ftDummy; + // NOTE: Get{Process|Thread}Times has a granularity of the clock interval, + // which is typically ~15ms. So intervals shorter than that will not be + // measurable by this function. + GetProcessTimes(GetCurrentProcess(), &ftDummy, &ftDummy, &ftKernel, &ftUser); #endif } @@ -593,6 +616,23 @@ int PROFILING::fill_statistics_info(THD *thd, TABLE_LIST *tables, Item *cond) table->field[5]->store_decimal(&cpu_stime_decimal); table->field[4]->set_notnull(); table->field[5]->set_notnull(); +#elif defined(_WIN32) + my_decimal cpu_utime_decimal, cpu_stime_decimal; + + double2my_decimal(E_DEC_FATAL_ERROR, + GetTimeDiffInSeconds(&entry->ftUser, + &previous->ftUser), + &cpu_utime_decimal); + double2my_decimal(E_DEC_FATAL_ERROR, + GetTimeDiffInSeconds(&entry->ftKernel, + &previous->ftKernel), + &cpu_stime_decimal); + + // Store the result. + table->field[4]->store_decimal(&cpu_utime_decimal); + table->field[5]->store_decimal(&cpu_stime_decimal); + table->field[4]->set_notnull(); + table->field[5]->set_notnull(); #else /* TODO: Add CPU-usage info for non-BSD systems */ #endif diff --git a/sql/sql_profile.h b/sql/sql_profile.h index bffe1cb576b..33597ca337e 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); @@ -173,6 +165,8 @@ private: char *status; #ifdef HAVE_GETRUSAGE struct rusage rusage; +#elif defined(_WIN32) + FILETIME ftKernel, ftUser; #endif char *function; diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc index 7c52a12c072..009563319cd 100644 --- a/sql/sql_rename.cc +++ b/sql/sql_rename.cc @@ -45,16 +45,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 +134,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))) @@ -185,12 +187,10 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) 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); + thd->global_read_lock.start_waiting_global_read_lock(thd); DBUG_RETURN(error || binlog_error); } diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 436e979df1e..510f9d8dc01 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -828,11 +828,11 @@ impossible position"; { if (coord) { - DBUG_ASSERT(heartbeat_ts && heartbeat_period != LL(0)); + DBUG_ASSERT(heartbeat_ts && heartbeat_period != 0); set_timespec_nsec(*heartbeat_ts, heartbeat_period); } ret= mysql_bin_log.wait_for_update_bin_log(thd, heartbeat_ts); - DBUG_ASSERT(ret == 0 || heartbeat_period != LL(0) && coord != NULL); + DBUG_ASSERT(ret == 0 || (heartbeat_period != 0 && coord != NULL)); if (ret == ETIMEDOUT || ret == ETIME) { #ifndef DBUG_OFF @@ -1525,7 +1525,7 @@ bool change_master(THD* thd, Master_info* mi) Relay log's IO_CACHE may not be inited, if rli->inited==0 (server was never a slave before). */ - if (flush_master_info(mi, 0)) + if (flush_master_info(mi, FALSE, FALSE)) { my_error(ER_RELAY_LOG_INIT, MYF(0), "Failed to flush master info file"); ret= TRUE; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index bddfbb1bd0b..468f81a7d87 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1038,7 +1038,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) @@ -2516,9 +2516,7 @@ static ha_rows get_quick_record_count(THD *thd, SQL_SELECT *select, { int error; DBUG_ENTER("get_quick_record_count"); -#ifndef EMBEDDED_LIBRARY // Avoid compiler warning uchar buff[STACK_BUFF_ALLOC]; -#endif if (check_stack_overrun(thd, STACK_MIN_SIZE, buff)) DBUG_RETURN(0); // Fatal error flag is set if (select) @@ -4392,7 +4390,7 @@ best_access_path(JOIN *join, */ if (table->quick_keys.is_set(key) && (const_part & ((1 << table->quick_key_parts[key])-1)) == - ((1 << table->quick_key_parts[key])-1) && + (((key_part_map)1 << table->quick_key_parts[key])-1) && table->quick_n_ranges[key] == 1 && records > (double) table->quick_rows[key]) { @@ -6960,7 +6958,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 ? @@ -11166,6 +11164,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 { @@ -12316,6 +12321,12 @@ end_send(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), if (!end_of_records) { int error; + if (join->tables && + join->join_tab->is_using_loose_index_scan()) + { + /* Copy non-aggregated fields when loose index scan is used. */ + copy_fields(&join->tmp_table_param); + } if (join->having && join->having->val_int() == 0) DBUG_RETURN(NESTED_LOOP_OK); // Didn't match having error=0; diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc index a7109af9f64..0af076d6102 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)) { @@ -284,7 +275,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); @@ -394,9 +387,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))) @@ -612,9 +603,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); @@ -736,9 +725,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 bb7fa61f39b..8b8e223ad02 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -1375,7 +1375,6 @@ int store_create_info(THD *thd, TABLE_LIST *table_list, String *packet, end= longlong10_to_str(create_info.auto_increment_value, buff,10); packet->append(buff, (uint) (end - buff)); } - if (share->table_charset && !(thd->variables.sql_mode & MODE_MYSQL323) && @@ -1520,6 +1519,14 @@ static void store_key_options(THD *thd, String *packet, TABLE *table, end= longlong10_to_str(key_info->block_size, buff, 10); packet->append(buff, (uint) (end - buff)); } + DBUG_ASSERT(test(key_info->flags & HA_USES_COMMENT) == + (key_info->comment.length > 0)); + if (key_info->flags & HA_USES_COMMENT) + { + packet->append(STRING_WITH_LEN(" COMMENT ")); + append_unescaped(packet, key_info->comment.str, + key_info->comment.length); + } } } @@ -2881,7 +2888,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 @@ -2894,7 +2905,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; @@ -2922,7 +2934,10 @@ 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 | + MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL | + (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 @@ -2948,7 +2963,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); } @@ -3061,6 +3077,49 @@ 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, + thd->variables.lock_wait_timeout))) + break; + } + return error; +} + + +/** @brief Fill I_S table with data from FRM file only @param[in] thd thread handler @@ -3069,6 +3128,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 @@ -3081,7 +3144,8 @@ static int fill_schema_table_from_frm(THD *thd, TABLE_LIST *tables, 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 *table= tables->table; TABLE_SHARE *share; @@ -3089,6 +3153,7 @@ static int fill_schema_table_from_frm(THD *thd, TABLE_LIST *tables, TABLE_LIST table_list; uint res= 0; 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]; @@ -3116,6 +3181,37 @@ static int fill_schema_table_from_frm(THD *thd, TABLE_LIST *tables, 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; + } + if (schema_table->i_s_requested_object & OPEN_TRIGGER_ONLY) { init_sql_alloc(&tbl.mem_root, TABLE_ALLOC_BLOCK_SIZE, 0); @@ -3132,9 +3228,10 @@ static int fill_schema_table_from_frm(THD *thd, TABLE_LIST *tables, } 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, ¬_used); + key_length, OPEN_VIEW, ¬_used, hash_value); if (!share) { res= 0; @@ -3184,10 +3281,15 @@ static int fill_schema_table_from_frm(THD *thd, TABLE_LIST *tables, } end_share: - release_table_share(share, RELEASE_NORMAL); + release_table_share(share); end_unlock: - mysql_mutex_unlock(&LOCK_open); + 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. + */ end: thd->clear_error(); @@ -3232,22 +3334,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); @@ -3263,6 +3378,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; } @@ -3374,7 +3490,8 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond) !with_i_schema) { if (!fill_schema_table_from_frm(thd, tables, schema_table, db_name, - table_name, schema_table_idx)) + table_name, schema_table_idx, + can_deadlock)) continue; } @@ -3399,7 +3516,9 @@ 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 | + MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL | + (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, @@ -3435,7 +3554,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) @@ -4516,7 +4636,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; enum enum_schema_tables schema_table_idx= get_schema_table_idx(tables->schema_table); DBUG_ENTER("fill_schema_proc"); @@ -4654,6 +4774,11 @@ static int get_schema_stat_record(THD *thd, TABLE_LIST *tables, else table->field[14]->store("", 0, cs); table->field[14]->set_notnull(); + DBUG_ASSERT(test(key_info->flags & HA_USES_COMMENT) == + (key_info->comment.length > 0)); + if (key_info->flags & HA_USES_COMMENT) + table->field[15]->store(key_info->comment.str, + key_info->comment.length, cs); if (schema_table_store_record(thd, table)) DBUG_RETURN(1); } @@ -6612,7 +6737,8 @@ ST_FIELD_INFO tables_fields_info[]= (MY_I_S_MAYBE_NULL | MY_I_S_UNSIGNED), "Checksum", OPEN_FULL_TABLE}, {"CREATE_OPTIONS", 255, MYSQL_TYPE_STRING, 0, 1, "Create_options", OPEN_FRM_ONLY}, - {"TABLE_COMMENT", 80, MYSQL_TYPE_STRING, 0, 0, "Comment", OPEN_FRM_ONLY}, + {"TABLE_COMMENT", TABLE_COMMENT_MAXLEN, MYSQL_TYPE_STRING, 0, 0, + "Comment", OPEN_FRM_ONLY}, {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE} }; @@ -6646,7 +6772,8 @@ ST_FIELD_INFO columns_fields_info[]= {"COLUMN_KEY", 3, MYSQL_TYPE_STRING, 0, 0, "Key", OPEN_FRM_ONLY}, {"EXTRA", 27, MYSQL_TYPE_STRING, 0, 0, "Extra", OPEN_FRM_ONLY}, {"PRIVILEGES", 80, MYSQL_TYPE_STRING, 0, 0, "Privileges", OPEN_FRM_ONLY}, - {"COLUMN_COMMENT", 255, MYSQL_TYPE_STRING, 0, 0, "Comment", OPEN_FRM_ONLY}, + {"COLUMN_COMMENT", COLUMN_COMMENT_MAXLEN, MYSQL_TYPE_STRING, 0, 0, + "Comment", OPEN_FRM_ONLY}, {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE} }; @@ -6804,6 +6931,8 @@ ST_FIELD_INFO stat_fields_info[]= {"NULLABLE", 3, MYSQL_TYPE_STRING, 0, 0, "Null", OPEN_FRM_ONLY}, {"INDEX_TYPE", 16, MYSQL_TYPE_STRING, 0, 0, "Index_type", OPEN_FULL_TABLE}, {"COMMENT", 16, MYSQL_TYPE_STRING, 0, 1, "Comment", OPEN_FRM_ONLY}, + {"INDEX_COMMENT", INDEX_COMMENT_MAXLEN, MYSQL_TYPE_STRING, 0, 0, + "Index_comment", OPEN_FRM_ONLY}, {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE} }; @@ -7498,14 +7627,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); @@ -7519,25 +7648,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; } @@ -7553,7 +7676,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). */ @@ -7603,8 +7727,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_string.cc b/sql/sql_string.cc index e4e51aba622..75e8ca30cf0 100644 --- a/sql/sql_string.cc +++ b/sql/sql_string.cc @@ -412,11 +412,25 @@ bool String::append(const char *s) bool String::append(const char *s,uint32 arg_length, CHARSET_INFO *cs) { - uint32 dummy_offset; + uint32 offset; - if (needs_conversion(arg_length, cs, str_charset, &dummy_offset)) + if (needs_conversion(arg_length, cs, str_charset, &offset)) { - uint32 add_length= arg_length / cs->mbminlen * str_charset->mbmaxlen; + uint32 add_length; + if ((cs == &my_charset_bin) && offset) + { + DBUG_ASSERT(str_charset->mbminlen > offset); + offset= str_charset->mbminlen - offset; // How many characters to pad + add_length= arg_length + offset; + if (realloc(str_length + add_length)) + return TRUE; + bzero((char*) Ptr + str_length, offset); + memcpy(Ptr + str_length + offset, s, arg_length); + str_length+= add_length; + return FALSE; + } + + add_length= arg_length / cs->mbminlen * str_charset->mbmaxlen; uint dummy_errors; if (realloc(str_length + add_length)) return TRUE; @@ -966,6 +980,24 @@ well_formed_copy_nchars(CHARSET_INFO *to_cs, uint pad_length= to_cs->mbminlen - from_offset; bzero(to, pad_length); memmove(to + pad_length, from, from_offset); + /* + In some cases left zero-padding can create an incorrect character. + For example: + INSERT INTO t1 (utf32_column) VALUES (0x110000); + We'll pad the value to 0x00110000, which is a wrong UTF32 sequence! + The valid characters range is limited to 0x00000000..0x0010FFFF. + + Make sure we didn't pad to an incorrect character. + */ + if (to_cs->cset->well_formed_len(to_cs, + to, to + to_cs->mbminlen, 1, + &well_formed_error) != + to_cs->mbminlen) + { + *from_end_pos= *well_formed_error_pos= from; + *cannot_convert_error_pos= NULL; + return 0; + } nchars--; from+= from_offset; from_length-= from_offset; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index f323b99dfed..61530ebedf2 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__ @@ -292,7 +295,8 @@ uint explain_filename(THD* thd, { if (explain_mode == EXPLAIN_ALL_VERBOSE) { - to_p= strnmov(to_p, ER(ER_DATABASE_NAME), end_p - to_p); + to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_DATABASE_NAME), + end_p - to_p); *(to_p++)= ' '; to_p= add_identifier(thd, to_p, end_p, db_name, db_name_len); to_p= strnmov(to_p, ", ", end_p - to_p); @@ -305,7 +309,7 @@ uint explain_filename(THD* thd, } if (explain_mode == EXPLAIN_ALL_VERBOSE) { - to_p= strnmov(to_p, ER(ER_TABLE_NAME), end_p - to_p); + to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_TABLE_NAME), end_p - to_p); *(to_p++)= ' '; to_p= add_identifier(thd, to_p, end_p, table_name, table_name_len); } @@ -322,18 +326,22 @@ uint explain_filename(THD* thd, if (name_type != NORMAL) { if (name_type == TEMP) - to_p= strnmov(to_p, ER(ER_TEMPORARY_NAME), end_p - to_p); + to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_TEMPORARY_NAME), + end_p - to_p); else - to_p= strnmov(to_p, ER(ER_RENAMED_NAME), end_p - to_p); + to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_RENAMED_NAME), + end_p - to_p); to_p= strnmov(to_p, " ", end_p - to_p); } - to_p= strnmov(to_p, ER(ER_PARTITION_NAME), end_p - to_p); + to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_PARTITION_NAME), + end_p - to_p); *(to_p++)= ' '; to_p= add_identifier(thd, to_p, end_p, part_name, part_name_len); if (subpart_name) { to_p= strnmov(to_p, ", ", end_p - to_p); - to_p= strnmov(to_p, ER(ER_SUBPARTITION_NAME), end_p - to_p); + to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_SUBPARTITION_NAME), + end_p - to_p); *(to_p++)= ' '; to_p= add_identifier(thd, to_p, end_p, subpart_name, subpart_name_len); } @@ -1802,31 +1810,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); @@ -1891,16 +1894,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; @@ -1913,16 +1914,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) @@ -1964,12 +2009,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 @@ -1978,7 +2027,6 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, */ if (!drop_temporary && thd->is_current_stmt_binlog_format_row() && !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). @@ -1997,22 +2045,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 */ @@ -2021,6 +2068,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) && @@ -2073,6 +2125,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()) @@ -2088,11 +2141,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()) @@ -2176,10 +2224,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); } @@ -2563,6 +2646,21 @@ CHARSET_INFO* get_sql_field_charset(Create_field *sql_field, } +bool check_duplicate_warning(THD *thd, char *msg, ulong length) +{ + List_iterator_fast<MYSQL_ERROR> it(thd->warning_info->warn_list()); + MYSQL_ERROR *err; + while ((err= it++)) + { + if (strncmp(msg, err->get_message_text(), length) == 0) + { + return true; + } + } + return false; +} + + /* Preparation for table creation @@ -2693,7 +2791,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, sql_field->interval_list); List_iterator<String> int_it(sql_field->interval_list); String conv, *tmp; - char comma_buf[2]; + char comma_buf[4]; /* 4 bytes for utf32 */ int comma_length= cs->cset->wc_mb(cs, ',', (uchar*) comma_buf, (uchar*) comma_buf + sizeof(comma_buf)); @@ -3191,11 +3289,19 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, { column->length*= sql_field->charset->mbmaxlen; - if (key->type == Key::SPATIAL && column->length) + if (key->type == Key::SPATIAL) { - my_error(ER_WRONG_SUB_KEY, MYF(0)); - DBUG_RETURN(TRUE); - } + if (column->length) + { + my_error(ER_WRONG_SUB_KEY, MYF(0)); + DBUG_RETURN(TRUE); + } + if (!f_is_geom(sql_field->pack_flag)) + { + my_error(ER_SPATIAL_MUST_HAVE_GEOM_COL, MYF(0)); + DBUG_RETURN(TRUE); + } + } if (f_is_blob(sql_field->pack_flag) || (f_is_geom(sql_field->pack_flag) && key->type != Key::SPATIAL)) @@ -3290,22 +3396,21 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, } } } + // Catch invalid use of partial keys else if (!f_is_geom(sql_field->pack_flag) && - ((column->length > length && - !Field::type_can_have_key_part (sql_field->sql_type)) || - ((f_is_packed(sql_field->pack_flag) || - ((file->ha_table_flags() & HA_NO_PREFIX_CHAR_KEYS) && - (key_info->flags & HA_NOSAME))) && - column->length != length))) - { - /* Catch invalid uses of partial keys. - A key is identified as 'partial' if column->length != length. - A partial key is invalid if they data type does - not allow it, or the field is packed (as in MyISAM), - or the storage engine doesn't allow prefixed search and - the key is primary key. - */ - + // is the key partial? + column->length != length && + // is prefix length bigger than field length? + (column->length > length || + // can the field have a partial key? + !Field::type_can_have_key_part (sql_field->sql_type) || + // a packed field can't be used in a partial key + f_is_packed(sql_field->pack_flag) || + // does the storage engine allow prefixed search? + ((file->ha_table_flags() & HA_NO_PREFIX_CHAR_KEYS) && + // and is this a 'unique' key? + (key_info->flags & HA_NOSAME)))) + { my_message(ER_WRONG_SUB_KEY, ER(ER_WRONG_SUB_KEY), MYF(0)); DBUG_RETURN(TRUE); } @@ -3396,6 +3501,40 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, my_error(ER_TOO_LONG_KEY,MYF(0),max_key_length); DBUG_RETURN(TRUE); } + + uint tmp_len= system_charset_info->cset->charpos(system_charset_info, + key->key_create_info.comment.str, + key->key_create_info.comment.str + + key->key_create_info.comment.length, + INDEX_COMMENT_MAXLEN); + + if (tmp_len < key->key_create_info.comment.length) + { + if ((thd->variables.sql_mode & + (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES))) + { + my_error(ER_TOO_LONG_INDEX_COMMENT, MYF(0), + key_info->name, (uint) INDEX_COMMENT_MAXLEN); + DBUG_RETURN(-1); + } + char warn_buff[MYSQL_ERRMSG_SIZE]; + my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_TOO_LONG_INDEX_COMMENT), + key_info->name, (uint) INDEX_COMMENT_MAXLEN); + /* do not push duplicate warnings */ + if (!check_duplicate_warning(thd, warn_buff, strlen(warn_buff))) + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_TOO_LONG_INDEX_COMMENT, warn_buff); + + key->key_create_info.comment.length= tmp_len; + } + + key_info->comment.length= key->key_create_info.comment.length; + if (key_info->comment.length > 0) + { + key_info->flags|= HA_USES_COMMENT; + key_info->comment.str= key->key_create_info.comment.str; + } + key_info++; } if (!unique_key && !primary_key && @@ -3642,9 +3781,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 @@ -3888,7 +4027,7 @@ 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; goto err; } my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alias); @@ -4037,7 +4176,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); @@ -4052,7 +4191,6 @@ 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); goto unlock_and_end; } @@ -4061,20 +4199,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); @@ -4088,45 +4224,45 @@ 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 + - Row-based logging is used and we are creating a temporary table + Otherwise, the statement shall be binlogged. + */ + if (!result && + (!thd->is_current_stmt_binlog_format_row() || + (thd->is_current_stmt_binlog_format_row() && + !(create_info->options & HA_LEX_CREATE_TMP_TABLE)))) + result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + + if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { - 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); @@ -4264,82 +4400,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) @@ -4356,29 +4416,63 @@ 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, + thd->variables.lock_wait_timeout)) + 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 @@ -4386,16 +4480,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. @@ -4442,68 +4536,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); } @@ -4537,8 +4632,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)); @@ -4551,13 +4644,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)); @@ -4585,11 +4679,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) { @@ -4641,8 +4746,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; @@ -4713,9 +4818,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()) @@ -4728,19 +4834,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; @@ -4769,8 +4869,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); @@ -4883,14 +4985,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; @@ -4906,13 +5011,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(); @@ -4992,19 +5103,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; } @@ -5013,9 +5143,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); @@ -5106,55 +5237,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 @@ -5173,174 +5255,72 @@ 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 - char tmp_path[FN_REFLEN]; -#endif - char ts_name[FN_LEN + 1]; - myf flags= MY_DONT_OVERWRITE_FILE; DBUG_ENTER("mysql_create_like_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. + 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 (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 - */ - 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);); - - if (opt_sync_frm && !(create_info->options & HA_LEX_CREATE_TMP_TABLE)) - flags|= MY_SYNC; + Adjust description of source table before using it for creation of + target table. - /* - 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))) 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). + Ensure that we have an exclusive lock on target table if we are creating + non-temporary table. */ -#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. - */ - 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) || + local_create_info.table_existed || + 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. @@ -5369,29 +5349,25 @@ 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. + 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. */ - table->table= name_lock; - mysql_mutex_lock(&LOCK_open); - if (reopen_name_locked_table(thd, table, FALSE)) - { - mysql_mutex_unlock(&LOCK_open); - 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) { + /* + Here we open the destination table, on which we already have + 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. + */ + if (open_table(thd, table, thd->mem_root, &ot_ctx_unused, + MYSQL_OPEN_REOPEN)) + goto err; + int result __attribute__((unused))= store_create_info(thd, table, &query, create_info, FALSE /* show_database */); @@ -5399,6 +5375,16 @@ binlog: DBUG_ASSERT(result == 0); // store_create_info() always return 0 if (write_bin_log(thd, TRUE, query.ptr(), query.length())) goto err; + + 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 @@ -5412,15 +5398,7 @@ binlog: else if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) goto err; - res= FALSE; - err: - if (name_lock) - { - mysql_mutex_lock(&LOCK_open); - unlink_open_table(thd, name_lock, FALSE); - mysql_mutex_unlock(&LOCK_open); - } DBUG_RETURN(res); } @@ -5493,17 +5471,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()); err: - ha_autocommit_or_rollback(thd, error); + trans_rollback_stmt(thd); thd->tablespace_op=FALSE; - + if (error == 0) { my_ok(thd); @@ -5511,7 +5489,7 @@ err: } table->file->print_error(error, MYF(0)); - + DBUG_RETURN(-1); } @@ -5904,7 +5882,6 @@ bool alter_table_manage_keys(TABLE *table, int indexes_were_disabled, DBUG_RETURN(error); } - /** maximum possible length for certain blob types. @@ -6268,6 +6245,8 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, key_create_info.block_size= key_info->block_size; if (key_info->flags & HA_USES_PARSER) key_create_info.parser_name= *plugin_name(key_info->parser); + if (key_info->flags & HA_USES_COMMENT) + key_create_info.comment= key_info->comment; if (key_info->flags & HA_SPATIAL) key_type= Key::SPATIAL; @@ -6397,7 +6376,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; @@ -6480,7 +6462,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) @@ -6527,22 +6509,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()) @@ -6550,24 +6533,43 @@ 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(), FALSE, TRUE, FALSE, 0); - if (error= mysql_bin_log.write(&qinfo)) + if ((error= mysql_bin_log.write(&qinfo))) goto view_err_unlock; } my_ok(thd); } view_err_unlock: - unlock_table_names(thd, table_list, (TABLE_LIST*) 0); + mysql_mutex_unlock(&LOCK_open); + 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 @@ -6575,7 +6577,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)) { @@ -6619,14 +6622,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)) @@ -6712,26 +6731,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; @@ -6744,53 +6752,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); } } @@ -6798,8 +6804,8 @@ 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) @@ -6813,11 +6819,25 @@ view_err: 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); } @@ -6843,7 +6863,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); @@ -6867,7 +6887,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; } @@ -7049,7 +7069,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, @@ -7129,13 +7148,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 { @@ -7144,10 +7165,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. @@ -7168,6 +7189,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, @@ -7176,14 +7201,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; @@ -7229,7 +7254,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)*/ @@ -7252,14 +7277,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)*/ @@ -7271,19 +7296,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; @@ -7292,45 +7319,40 @@ 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->is_current_stmt_binlog_format_row() && write_bin_log(thd, TRUE, thd->query(), thd->query_length())) DBUG_RETURN(TRUE); 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"); @@ -7339,10 +7361,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; /* @@ -7364,6 +7391,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)) { @@ -7387,10 +7415,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) @@ -7400,6 +7433,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) { @@ -7408,45 +7442,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"); @@ -7486,18 +7514,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: @@ -7505,10 +7531,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. */ @@ -7552,24 +7577,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 */ @@ -7762,7 +7786,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); @@ -7776,9 +7800,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: @@ -7790,6 +7814,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); } @@ -7817,6 +7843,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; @@ -7855,7 +7883,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_test.cc b/sql/sql_test.cc index fe56d6acf3e..bfd053d3333 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 */ @@ -390,7 +402,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 ee530a74b50..c2ab740f29b 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()); } - 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); @@ -1854,7 +1864,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 @@ -1876,15 +1886,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 @@ -2020,6 +2027,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 d0e446fb157..3bead5217f0 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)) { @@ -461,8 +458,8 @@ int mysql_create_function(THD *thd,udf_func *udf) Turn off row binlogging of this statement and use statement-based so that all supporting tables are updated for CREATE FUNCTION command. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); mysql_rwlock_wrlock(&THR_LOCK_udf); if ((my_hash_search(&udf_hash,(uchar*) udf->name.str, udf->name.length))) @@ -506,9 +503,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; @@ -533,11 +528,15 @@ int mysql_create_function(THD *thd,udf_func *udf) if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) { /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(1); } /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(0); err: @@ -545,7 +544,9 @@ int mysql_create_function(THD *thd,udf_func *udf) dlclose(dl); mysql_rwlock_unlock(&THR_LOCK_udf); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(1); } @@ -573,8 +574,8 @@ int mysql_drop_function(THD *thd,const LEX_STRING *udf_name) Turn off row binlogging of this statement and use statement-based so that all supporting tables are updated for DROP FUNCTION command. */ - save_binlog_row_based= thd->is_current_stmt_binlog_format_row(); - thd->clear_current_stmt_binlog_format_row(); + if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row())) + thd->clear_current_stmt_binlog_format_row(); mysql_rwlock_wrlock(&THR_LOCK_udf); if (!(udf=(udf_func*) my_hash_search(&udf_hash,(uchar*) udf_name->str, @@ -593,9 +594,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(); @@ -609,24 +609,31 @@ 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. */ + /* + Binlog the drop function. Keep the table open and locked + while binlogging, to avoid binlog inconsistency. + */ if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) { /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(1); } /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(0); - err: +err: mysql_rwlock_unlock(&THR_LOCK_udf); /* Restore the state of binlog format */ - thd->current_stmt_binlog_row_based= save_binlog_row_based; + DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); + if (save_binlog_row_based) + thd->set_current_stmt_binlog_format_row(); DBUG_RETURN(1); } diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 97abf2cd89b..a163dda2c69 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -207,6 +207,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 ( ; ; ) @@ -223,11 +224,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) || @@ -967,9 +968,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 */ @@ -1049,6 +1051,11 @@ reopen_tables: If we are using the binary log, we need TL_READ_NO_INSERT to get correct order of statements. Otherwise, we use a TL_READ lock to improve performance. + We don't downgrade metadata lock from SW to SR in this case as + there is no guarantee that the same ticket is not used by + another table instance used by this statement which is going to + be write-locked (for example, trigger to be invoked might try + to update this table). */ tl->lock_type= read_lock_type_for_table(thd, table); tl->updating= 0; @@ -1089,7 +1096,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); @@ -1107,10 +1114,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): @@ -1139,7 +1142,7 @@ reopen_tables: */ cleanup_items(thd->free_list); cleanup_items(thd->stmt_arena->free_list); - close_tables_for_reopen(thd, &table_list); + close_tables_for_reopen(thd, &table_list, start_of_statement_svp); DEBUG_SYNC(thd, "multi_update_reopen_tables"); diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 31e4a45ce4f..b9eb6a63552 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); @@ -667,7 +667,7 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, 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; @@ -1146,6 +1146,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 */ @@ -1262,7 +1276,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; @@ -1333,7 +1347,11 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, anyway. */ for (tbl= view_main_select_tables; tbl; tbl= tbl->next_local) + { tbl->lock_type= table->lock_type; + tbl->mdl_request.set_type((tbl->lock_type >= TL_WRITE_ALLOW_WRITE) ? + MDL_SHARED_WRITE : MDL_SHARED_READ); + } /* If the view is mergeable, we might want to INSERT/UPDATE/DELETE into tables of this view. Preserve the @@ -1581,6 +1599,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) { @@ -1629,11 +1662,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(); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index ab128a9b701..f03694cb359 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; @@ -756,6 +757,7 @@ static bool add_create_index (LEX *lex, Key::Keytype type, struct p_elem_val *p_elem_value; enum index_hint_type index_hint; enum enum_filetype filetype; + enum Foreign_key::fk_option m_fk_option; Diag_condition_item_name diag_condition_item_name; } @@ -765,10 +767,10 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %pure_parser /* We have threads */ /* - Currently there are 172 shift/reduce conflicts. + Currently there are 169 shift/reduce conflicts. We should not introduce new conflicts any more. */ -%expect 172 +%expect 169 /* Comments for TOKENS. @@ -966,7 +968,6 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token FOREIGN /* SQL-2003-R */ %token FOR_SYM /* SQL-2003-R */ %token FOUND_SYM /* SQL-2003-R */ -%token FRAC_SECOND_SYM %token FROM %token FULL /* SQL-2003-R */ %token FULLTEXT_SYM @@ -1170,7 +1171,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 @@ -1422,7 +1423,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); type type_with_opt_collate int_type real_type order_dir lock_option udf_type if_exists opt_local opt_table_options table_options table_option opt_if_not_exists opt_no_write_to_binlog - delete_option opt_temporary all_or_any opt_distinct + opt_temporary all_or_any opt_distinct opt_ignore_leaves fulltext_options spatial_type union_option start_transaction_opts opt_chain opt_release union_opt select_derived_init option_type2 @@ -1430,6 +1431,9 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); opt_ev_status opt_ev_on_completion ev_on_completion opt_ev_comment ev_alter_on_schedule_completion opt_ev_rename_to opt_ev_sql_stmt +%type <m_fk_option> + delete_option + %type <ulong_num> ulong_num real_ulong_num merge_insert_types @@ -1489,8 +1493,6 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %type <date_time_type> date_time_type; %type <interval> interval -%type <interval_time_st> interval_time_st - %type <interval_time_st> interval_time_stamp %type <db_type> storage_engines known_storage_engines @@ -1544,7 +1546,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); opt_precision opt_ignore opt_column opt_restrict grant revoke set lock unlock string_list field_options field_option field_opt_list opt_binary ascii unicode table_lock_list table_lock - ref_list opt_on_delete opt_on_delete_list opt_on_delete_item use + ref_list opt_match_clause opt_on_update_delete use opt_delete_options opt_delete_option varchar nchar nvarchar opt_outer table_list table_name table_alias_ref_list table_alias_ref opt_option opt_place @@ -2023,6 +2025,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 { @@ -2037,6 +2040,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 { @@ -4190,7 +4194,7 @@ size_number: create2: '(' create2a {} | opt_create_table_options - opt_partitioning + opt_create_partitioning create3 {} | LIKE table_ident { @@ -4223,10 +4227,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 {} @@ -4242,6 +4246,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. @@ -5072,19 +5089,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 { - /* Move the union list to the merge_list */ + Lex->select_lex.table_list.save_and_clear(&Lex->save_list); + } + '(' opt_table_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 @@ -5222,6 +5250,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 @@ -5282,10 +5318,6 @@ key_def: /* Only used for ALTER TABLE. Ignored otherwise. */ lex->alter_info.flags|= ALTER_FOREIGN_KEY; } - | constraint opt_check_constraint - { - Lex->col_list.empty(); /* Alloced by sql_alloc */ - } | opt_constraint check_constraint { Lex->col_list.empty(); /* Alloced by sql_alloc */ @@ -5298,7 +5330,7 @@ opt_check_constraint: ; check_constraint: - CHECK_SYM expr + CHECK_SYM '(' expr ')' ; opt_constraint: @@ -5837,21 +5869,20 @@ opt_primary: ; references: - REFERENCES table_ident - { - LEX *lex=Lex; - lex->fk_delete_opt= lex->fk_update_opt= lex->fk_match_option= 0; - lex->ref_list.empty(); - } + REFERENCES + table_ident opt_ref_list + opt_match_clause + opt_on_update_delete { $$=$2; } ; opt_ref_list: - /* empty */ opt_on_delete {} - | '(' ref_list ')' opt_on_delete {} + /* empty */ + { Lex->ref_list.empty(); } + | '(' ref_list ')' ; ref_list: @@ -5867,34 +5898,64 @@ ref_list: Key_part_spec *key= new Key_part_spec($1, 0); if (key == NULL) MYSQL_YYABORT; - Lex->ref_list.push_back(key); + LEX *lex= Lex; + lex->ref_list.empty(); + lex->ref_list.push_back(key); } ; -opt_on_delete: - /* empty */ {} - | opt_on_delete_list {} - ; - -opt_on_delete_list: - opt_on_delete_list opt_on_delete_item {} - | opt_on_delete_item {} +opt_match_clause: + /* empty */ + { Lex->fk_match_option= Foreign_key::FK_MATCH_UNDEF; } + | MATCH FULL + { Lex->fk_match_option= Foreign_key::FK_MATCH_FULL; } + | MATCH PARTIAL + { Lex->fk_match_option= Foreign_key::FK_MATCH_PARTIAL; } + | MATCH SIMPLE_SYM + { Lex->fk_match_option= Foreign_key::FK_MATCH_SIMPLE; } ; -opt_on_delete_item: - ON DELETE_SYM delete_option { Lex->fk_delete_opt= $3; } - | ON UPDATE_SYM delete_option { Lex->fk_update_opt= $3; } - | MATCH FULL { Lex->fk_match_option= Foreign_key::FK_MATCH_FULL; } - | MATCH PARTIAL { Lex->fk_match_option= Foreign_key::FK_MATCH_PARTIAL; } - | MATCH SIMPLE_SYM { Lex->fk_match_option= Foreign_key::FK_MATCH_SIMPLE; } +opt_on_update_delete: + /* empty */ + { + LEX *lex= Lex; + lex->fk_update_opt= Foreign_key::FK_OPTION_UNDEF; + lex->fk_delete_opt= Foreign_key::FK_OPTION_UNDEF; + } + | ON UPDATE_SYM delete_option + { + LEX *lex= Lex; + lex->fk_update_opt= $3; + lex->fk_delete_opt= Foreign_key::FK_OPTION_UNDEF; + } + | ON DELETE_SYM delete_option + { + LEX *lex= Lex; + lex->fk_update_opt= Foreign_key::FK_OPTION_UNDEF; + lex->fk_delete_opt= $3; + } + | ON UPDATE_SYM delete_option + ON DELETE_SYM delete_option + { + LEX *lex= Lex; + lex->fk_update_opt= $3; + lex->fk_delete_opt= $6; + } + | ON DELETE_SYM delete_option + ON UPDATE_SYM delete_option + { + LEX *lex= Lex; + lex->fk_update_opt= $6; + lex->fk_delete_opt= $3; + } ; delete_option: - RESTRICT { $$= (int) Foreign_key::FK_OPTION_RESTRICT; } - | CASCADE { $$= (int) Foreign_key::FK_OPTION_CASCADE; } - | SET NULL_SYM { $$= (int) Foreign_key::FK_OPTION_SET_NULL; } - | NO_SYM ACTION { $$= (int) Foreign_key::FK_OPTION_NO_ACTION; } - | SET DEFAULT { $$= (int) Foreign_key::FK_OPTION_DEFAULT; } + RESTRICT { $$= Foreign_key::FK_OPTION_RESTRICT; } + | CASCADE { $$= Foreign_key::FK_OPTION_CASCADE; } + | SET NULL_SYM { $$= Foreign_key::FK_OPTION_SET_NULL; } + | NO_SYM ACTION { $$= Foreign_key::FK_OPTION_NO_ACTION; } + | SET DEFAULT { $$= Foreign_key::FK_OPTION_DEFAULT; } ; normal_key_type: @@ -5999,6 +6060,7 @@ key_using_alg: all_key_opt: KEY_BLOCK_SIZE opt_equal ulong_num { Lex->key_create_info.block_size= $3; } + | COMMENT_SYM TEXT_STRING_sys { Lex->key_create_info.comment= $2; } ; normal_key_opt: @@ -6083,7 +6145,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(); @@ -6096,6 +6159,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 {} @@ -6124,7 +6188,7 @@ alter: lex->sql_command= SQLCOM_ALTER_DB_UPGRADE; lex->name= $3; } - | ALTER PROCEDURE sp_name + | ALTER PROCEDURE_SYM sp_name { LEX *lex= Lex; @@ -6479,12 +6543,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; } @@ -6495,6 +6563,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; @@ -6517,6 +6588,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; @@ -9218,8 +9292,8 @@ table_factor: lex->pop_context(); lex->nest_level--; } - else if ($3->select_lex && - $3->select_lex->master_unit()->is_union() || $5) + else if (($3->select_lex && + $3->select_lex->master_unit()->is_union()) || $5) { /* simple nested joins cannot have aliases or unions */ my_parse_error(ER(ER_SYNTAX_ERROR)); @@ -9458,7 +9532,7 @@ using_list: ; interval: - interval_time_st {} + interval_time_stamp {} | DAY_HOUR_SYM { $$=INTERVAL_DAY_HOUR; } | DAY_MICROSECOND_SYM { $$=INTERVAL_DAY_MICROSECOND; } | DAY_MINUTE_SYM { $$=INTERVAL_DAY_MINUTE; } @@ -9473,27 +9547,6 @@ interval: ; interval_time_stamp: - interval_time_st {} - | FRAC_SECOND_SYM - { - $$=INTERVAL_MICROSECOND; - /* - FRAC_SECOND was mistakenly implemented with - a wrong resolution. According to the ODBC - standard it should be nanoseconds, not - microseconds. Changing it to nanoseconds - in MySQL would mean making TIMESTAMPDIFF - and TIMESTAMPADD to return DECIMAL, since - the return value would be too big for BIGINT - Hence we just deprecate the incorrect - implementation without changing its - resolution. - */ - WARN_DEPRECATED(yythd, 6, 2, "FRAC_SECOND", "MICROSECOND"); - } - ; - -interval_time_st: DAY_SYM { $$=INTERVAL_DAY; } | WEEK_SYM { $$=INTERVAL_WEEK; } | HOUR_SYM { $$=INTERVAL_HOUR; } @@ -9852,7 +9905,7 @@ dec_num: procedure_clause: /* empty */ - | PROCEDURE ident /* Procedure name */ + | PROCEDURE_SYM ident /* Procedure name */ { LEX *lex=Lex; @@ -10052,7 +10105,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 @@ -10106,7 +10160,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) @@ -10865,7 +10919,7 @@ show_param: { Lex->sql_command = SQLCOM_SHOW_SLAVE_STAT; } - | CREATE PROCEDURE sp_name + | CREATE PROCEDURE_SYM sp_name { LEX *lex= Lex; @@ -10885,7 +10939,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; @@ -10899,7 +10953,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; @@ -12229,7 +12283,6 @@ keyword_sp: | FILE_SYM {} | FIRST_SYM {} | FIXED_SYM {} - | FRAC_SECOND_SYM {} | GEOMETRY_SYM {} | GEOMETRYCOLLECTION {} | GET_FORMAT {} @@ -13049,7 +13102,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) @@ -13091,7 +13144,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; @@ -14128,7 +14181,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/structs.h b/sql/structs.h index 041a6809804..6d2cf54d693 100644 --- a/sql/structs.h +++ b/sql/structs.h @@ -102,6 +102,7 @@ typedef struct st_key { int bdb_return_if_eq; } handler; TABLE *table; + LEX_STRING comment; } KEY; diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index fe3114c9d50..ba970925fc6 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" @@ -241,15 +242,25 @@ static bool check_has_super(sys_var *self, THD *thd, set_var *var) static bool binlog_format_check(sys_var *self, THD *thd, set_var *var) { /* - If RBR and open temporary tables, their CREATE TABLE may not be in the - binlog, so we can't toggle to SBR in this connection. + If RBR and open temporary tables, their CREATE TABLE may not be in the + binlog, so we can't toggle to SBR in this connection. + + If binlog_format=MIXED, there are open temporary tables, and an unsafe + statement is executed, then subsequent statements are logged in row + format and hence changes to temporary tables may be lost. So we forbid + switching @@SESSION.binlog_format from MIXED to STATEMENT when there are + open temp tables and we are logging in row format. */ - if ((thd->variables.binlog_format == BINLOG_FORMAT_ROW) && - thd->temporary_tables) + if (thd->temporary_tables && var->type == OPT_SESSION && + var->save_result.ulonglong_value == BINLOG_FORMAT_STMT && + ((thd->variables.binlog_format == BINLOG_FORMAT_MIXED && + thd->is_current_stmt_binlog_format_row()) || + thd->variables.binlog_format == BINLOG_FORMAT_ROW)) { my_error(ER_TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR, MYF(0)); return true; } + /* if in a stored function/trigger, it's too late to change mode */ @@ -324,7 +335,7 @@ static bool binlog_direct_check(sys_var *self, THD *thd, set_var *var) return true; if (var->type == OPT_GLOBAL || (thd->variables.binlog_direct_non_trans_update == - var->save_result.ulonglong_value)) + static_cast<my_bool>(var->save_result.ulonglong_value))) return false; return false; @@ -852,6 +863,12 @@ static Sys_var_mybool Sys_local_infile( "local_infile", "Enable LOAD DATA LOCAL INFILE", GLOBAL_VAR(opt_local_infile), CMD_LINE(OPT_ARG), DEFAULT(TRUE)); +static Sys_var_ulong Sys_lock_wait_timeout( + "lock_wait_timeout", + "Timeout in seconds to wait for a lock before returning an error.", + SESSION_VAR(lock_wait_timeout), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(1, LONG_TIMEOUT), DEFAULT(LONG_TIMEOUT), BLOCK_SIZE(1)); + #ifdef HAVE_MLOCKALL static Sys_var_mybool Sys_locked_in_memory( "locked_in_memory", @@ -1373,7 +1390,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; @@ -1395,7 +1412,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. @@ -1421,7 +1438,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; /* @@ -1433,10 +1450,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 */ @@ -1445,7 +1462,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: @@ -1937,13 +1954,6 @@ static Sys_var_ulong Sys_table_cache_size( VALID_RANGE(1, 512*1024), DEFAULT(TABLE_OPEN_CACHE_DEFAULT), BLOCK_SIZE(1)); -static Sys_var_ulong Sys_table_lock_wait_timeout( - "table_lock_wait_timeout", - "Timeout in seconds to wait for a table level lock before returning an " - "error. Used only if the connection has active cursors", - GLOBAL_VAR(table_lock_wait_timeout), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(1, 1024*1024*1024), DEFAULT(50), BLOCK_SIZE(1)); - static Sys_var_ulong Sys_thread_cache_size( "thread_cache_size", "How many threads we should keep in a cache for reuse", @@ -2129,11 +2139,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); @@ -2193,30 +2205,6 @@ static Sys_var_bit Sys_log_binlog( DEFAULT(TRUE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_has_super), ON_UPDATE(fix_sql_log_bin)); -static bool deprecated_log_update(sys_var *self, THD *thd, set_var *var) -{ - /* - The update log is not supported anymore since 5.0. - See sql/mysqld.cc/, comments in function init_server_components() for an - explaination of the different warnings we send below - */ - - if (opt_sql_bin_update) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_UPDATE_LOG_DEPRECATED_TRANSLATED, - ER(ER_UPDATE_LOG_DEPRECATED_TRANSLATED)); - else - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_UPDATE_LOG_DEPRECATED_IGNORED, - ER(ER_UPDATE_LOG_DEPRECATED_IGNORED)); - return check_has_super(self, thd, var); -} -static Sys_var_bit Sys_log_update( - "sql_log_update", "alias for sql_log_bin", - SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_BIN_LOG, - DEFAULT(TRUE), NO_MUTEX_GUARD, NOT_IN_BINLOG, - ON_CHECK(deprecated_log_update), ON_UPDATE(fix_sql_log_bin)); - static Sys_var_bit Sys_sql_warnings( "sql_warnings", "sql_warnings", SESSION_VAR(option_bits), NO_CMD_LINE, OPTION_WARNINGS, @@ -2956,14 +2944,14 @@ static bool check_locale(sys_var *self, THD *thd, set_var *var) static Sys_var_struct Sys_lc_messages( "lc_messages", "Set the language used for the error messages", SESSION_VAR(lc_messages), NO_CMD_LINE, - offsetof(MY_LOCALE, name), DEFAULT(&my_default_lc_messages), + my_offsetof(MY_LOCALE, name), DEFAULT(&my_default_lc_messages), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_locale)); static Sys_var_struct Sys_lc_time_names( "lc_time_names", "Set the language used for the month " "names and the days of the week", SESSION_VAR(lc_time_names), NO_CMD_LINE, - offsetof(MY_LOCALE, name), DEFAULT(&my_default_lc_time_names), + my_offsetof(MY_LOCALE, name), DEFAULT(&my_default_lc_time_names), NO_MUTEX_GUARD, IN_BINLOG, ON_CHECK(check_locale)); static Sys_var_tz Sys_time_zone( diff --git a/sql/table.cc b/sql/table.cc index 3e2e5adc2c7..0e66ff9da94 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -314,9 +314,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); } @@ -380,6 +383,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; } @@ -404,25 +410,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; @@ -525,7 +517,7 @@ int open_table_def(THD *thd, TABLE_SHARE *share, uint db_flags) int error, table_type; bool error_given; File file; - uchar head[288], *disk_buff; + uchar head[64], *disk_buff; char path[FN_REFLEN]; MEM_ROOT **root_ptr, *old_root; DBUG_ENTER("open_table_def"); @@ -668,6 +660,7 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, uint i,j; bool use_hash; char *keynames, *names, *comment_pos; + uchar forminfo[288]; uchar *record; uchar *disk_buff, *strpos, *null_flags, *null_pos; ulong pos, record_offset, *rec_per_key, rec_buff_length; @@ -690,6 +683,9 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, if (!(pos=get_form_pos(file,head,(TYPELIB*) 0))) goto err; /* purecov: inspected */ + mysql_file_seek(file,pos,MY_SEEK_SET,MYF(0)); + if (mysql_file_read(file, forminfo,288,MYF(MY_NABP))) + goto err; share->frm_version= head[2]; /* Check if .frm file created by MySQL 5.0. In this case we want to @@ -835,6 +831,20 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, keynames=(char*) key_part; strpos+= (strmov(keynames, (char *) strpos) - keynames)+1; + //reading index comments + for (keyinfo= share->key_info, i=0; i < keys; i++, keyinfo++) + { + if (keyinfo->flags & HA_USES_COMMENT) + { + keyinfo->comment.length= uint2korr(strpos); + keyinfo->comment.str= strmake_root(&share->mem_root, (char*) strpos+2, + keyinfo->comment.length); + strpos+= 2 + keyinfo->comment.length; + } + DBUG_ASSERT(test(keyinfo->flags & HA_USES_COMMENT) == + (keyinfo->comment.length > 0)); + } + share->reclength = uint2korr((head+16)); if (*(head+26) == 1) share->system= 1; /* one-record-database */ @@ -1015,6 +1025,25 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, } } } + if (forminfo[46] == (uchar)255) + { + //reading long table comment + if (next_chunk + 2 > buff_end) + { + DBUG_PRINT("error", + ("long table comment is not defined in .frm")); + my_free(buff, MYF(0)); + goto err; + } + share->comment.length = uint2korr(next_chunk); + if (! (share->comment.str= strmake_root(&share->mem_root, + (char*)next_chunk + 2, share->comment.length))) + { + my_free(buff, MYF(0)); + goto err; + } + next_chunk+= 2 + share->comment.length; + } my_free(buff, MYF(0)); } share->key_block_size= uint2korr(head+62); @@ -1031,29 +1060,30 @@ static int open_binary_frm(THD *thd, TABLE_SHARE *share, uchar *head, record_offset, MYF(MY_NABP))) goto err; /* purecov: inspected */ - mysql_file_seek(file, pos, MY_SEEK_SET, MYF(0)); - if (mysql_file_read(file, head, 288, MYF(MY_NABP))) - goto err; + mysql_file_seek(file, pos+288, MY_SEEK_SET, MYF(0)); #ifdef HAVE_CRYPTED_FRM if (crypted) { - crypted->decode((char*) head+256,288-256); - if (sint2korr(head+284) != 0) // Should be 0 + crypted->decode((char*) forminfo+256,288-256); + if (sint2korr(forminfo+284) != 0) // Should be 0 goto err; // Wrong password } #endif - share->fields= uint2korr(head+258); - pos= uint2korr(head+260); /* Length of all screens */ - n_length= uint2korr(head+268); - interval_count= uint2korr(head+270); - interval_parts= uint2korr(head+272); - int_length= uint2korr(head+274); - share->null_fields= uint2korr(head+282); - com_length= uint2korr(head+284); - share->comment.length= (int) (head[46]); - share->comment.str= strmake_root(&share->mem_root, (char*) head+47, - share->comment.length); + share->fields= uint2korr(forminfo+258); + pos= uint2korr(forminfo+260); /* Length of all screens */ + n_length= uint2korr(forminfo+268); + interval_count= uint2korr(forminfo+270); + interval_parts= uint2korr(forminfo+272); + int_length= uint2korr(forminfo+274); + share->null_fields= uint2korr(forminfo+282); + com_length= uint2korr(forminfo+284); + if (forminfo[46] != (uchar)255) + { + share->comment.length= (int) (forminfo[46]); + share->comment.str= strmake_root(&share->mem_root, (char*) forminfo+47, + share->comment.length); + } DBUG_PRINT("info",("i_count: %d i_parts: %d index: %d n_length: %d int_length: %d com_length: %d", interval_count,interval_parts, share->keys,n_length,int_length, com_length)); @@ -1995,7 +2025,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); } @@ -2445,12 +2475,14 @@ void append_unescaped(String *res, const char *pos, uint length) File create_frm(THD *thd, const char *name, const char *db, const char *table, uint reclength, uchar *fileinfo, - HA_CREATE_INFO *create_info, uint keys) + HA_CREATE_INFO *create_info, uint keys, KEY *key_info) { register File file; ulong length; uchar fill[IO_SIZE]; int create_flags= O_RDWR | O_TRUNC; + ulong key_comment_total_bytes= 0; + uint i; if (create_info->options & HA_LEX_CREATE_TMP_TABLE) create_flags|= O_EXCL | O_NOFOLLOW; @@ -2487,7 +2519,17 @@ File create_frm(THD *thd, const char *name, const char *db, 1 byte for the NAMES_SEP_CHAR (after the last name) 9 extra bytes (padding for safety? alignment?) */ - key_length= keys * (8 + MAX_REF_PARTS * 9 + NAME_LEN + 1) + 16; + for (i= 0; i < keys; i++) + { + DBUG_ASSERT(test(key_info[i].flags & HA_USES_COMMENT) == + (key_info[i].comment.length > 0)); + if (key_info[i].flags & HA_USES_COMMENT) + key_comment_total_bytes += 2 + key_info[i].comment.length; + } + + key_length= keys * (8 + MAX_REF_PARTS * 9 + NAME_LEN + 1) + 16 + + key_comment_total_bytes; + length= next_io_size((ulong) (IO_SIZE+key_length+reclength+ create_info->extra_size)); int4store(fileinfo+10,length); @@ -4577,24 +4619,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. @@ -4623,6 +4647,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); } /* @@ -4845,6 +4878,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/sql/unireg.cc b/sql/unireg.cc index b20e759efbb..c7d2f2f5b5b 100644 --- a/sql/unireg.cc +++ b/sql/unireg.cc @@ -188,27 +188,6 @@ bool mysql_create_frm(THD *thd, const char *file_name, if (key_info[i].parser_name) create_info->extra_size+= key_info[i].parser_name->length + 1; } - - if ((file=create_frm(thd, file_name, db, table, reclength, fileinfo, - create_info, keys)) < 0) - { - my_free(screen_buff, MYF(0)); - DBUG_RETURN(1); - } - - key_buff_length= uint4korr(fileinfo+47); - keybuff=(uchar*) my_malloc(key_buff_length, MYF(0)); - key_info_length= pack_keys(keybuff, keys, key_info, data_offset); - (void) get_form_pos(file,fileinfo,&formnames); - if (!(filepos=make_new_entry(file,fileinfo,&formnames,""))) - goto err; - maxlength=(uint) next_io_size((ulong) (uint2korr(forminfo)+1000)); - int2store(forminfo+2,maxlength); - int4store(fileinfo+10,(ulong) (filepos+maxlength)); - fileinfo[26]= (uchar) test((create_info->max_rows == 1) && - (create_info->min_rows == 1) && (keys == 0)); - int2store(fileinfo+28,key_info_length); - /* This gives us the byte-position of the character at (character-position, not byte-position) TABLE_COMMENT_MAXLEN. @@ -222,33 +201,78 @@ bool mysql_create_frm(THD *thd, const char *file_name, string), the string is too long. For additional credit, realise that UTF-8 has 1-3 bytes before 6.0, - and 1-4 bytes in 6.0 (6.0 also has UTF-32). This means that the - inlined COMMENT supposedly does not exceed 60 character plus - terminator, vulgo, 181 bytes. + and 1-4 bytes in 6.0 (6.0 also has UTF-32). */ - tmp_len= system_charset_info->cset->charpos(system_charset_info, create_info->comment.str, create_info->comment.str + - create_info->comment.length, 60); + create_info->comment.length, + TABLE_COMMENT_MAXLEN); + if (tmp_len < create_info->comment.length) { + char *real_table_name= (char*) table; + List_iterator<Create_field> it(create_fields); + Create_field *field; + while ((field=it++)) + { + if (field->field && field->field->table && + (real_table_name= field->field->table->s->table_name.str)) + break; + } if ((thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES))) { - my_error(ER_TOO_LONG_TABLE_COMMENT, MYF(0), table, tmp_len); - goto err; + my_error(ER_TOO_LONG_TABLE_COMMENT, MYF(0), + real_table_name, (uint) TABLE_COMMENT_MAXLEN); + my_free(screen_buff,MYF(0)); + DBUG_RETURN(1); } - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TOO_LONG_TABLE_COMMENT, - ER(ER_TOO_LONG_TABLE_COMMENT), - table, tmp_len); + char warn_buff[MYSQL_ERRMSG_SIZE]; + my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_TOO_LONG_TABLE_COMMENT), + real_table_name, (uint) TABLE_COMMENT_MAXLEN); + /* do not push duplicate warnings */ + if (!check_duplicate_warning(current_thd, warn_buff, strlen(warn_buff))) + push_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_TOO_LONG_TABLE_COMMENT, warn_buff); create_info->comment.length= tmp_len; } + /* + If table comment is longer than TABLE_COMMENT_INLINE_MAXLEN bytes, + store the comment in an extra segment (up to TABLE_COMMENT_MAXLEN bytes). + Pre 6.0, the limit was 60 characters, with no extra segment-handling. + */ + if (create_info->comment.length > TABLE_COMMENT_INLINE_MAXLEN) + { + forminfo[46]=255; + create_info->extra_size+= 2 + create_info->comment.length; + } + else{ + strmake((char*) forminfo+47, create_info->comment.str ? + create_info->comment.str : "", create_info->comment.length); + forminfo[46]=(uchar) create_info->comment.length; + } + + if ((file=create_frm(thd, file_name, db, table, reclength, fileinfo, + create_info, keys, key_info)) < 0) + { + my_free(screen_buff, MYF(0)); + DBUG_RETURN(1); + } + + key_buff_length= uint4korr(fileinfo+47); + keybuff=(uchar*) my_malloc(key_buff_length, MYF(0)); + key_info_length= pack_keys(keybuff, keys, key_info, data_offset); + (void) get_form_pos(file,fileinfo,&formnames); + if (!(filepos=make_new_entry(file,fileinfo,&formnames,""))) + goto err; + maxlength=(uint) next_io_size((ulong) (uint2korr(forminfo)+1000)); + int2store(forminfo+2,maxlength); + int4store(fileinfo+10,(ulong) (filepos+maxlength)); + fileinfo[26]= (uchar) test((create_info->max_rows == 1) && + (create_info->min_rows == 1) && (keys == 0)); + int2store(fileinfo+28,key_info_length); - strmake((char*) forminfo+47, create_info->comment.str ? - create_info->comment.str : "", create_info->comment.length); - forminfo[46]=(uchar) create_info->comment.length; #ifdef WITH_PARTITION_STORAGE_ENGINE if (part_info) { @@ -309,6 +333,15 @@ bool mysql_create_frm(THD *thd, const char *file_name, goto err; } } + if (forminfo[46] == (uchar)255) + { + uchar comment_length_buff[2]; + int2store(comment_length_buff,create_info->comment.length); + if (mysql_file_write(file, comment_length_buff, 2, MYF(MY_NABP)) || + mysql_file_write(file, (uchar*) create_info->comment.str, + create_info->comment.length, MYF(MY_NABP))) + goto err; + } mysql_file_seek(file, filepos, MY_SEEK_SET, MYF(0)); if (mysql_file_write(file, forminfo, 288, MYF_RW) || @@ -561,6 +594,16 @@ static uint pack_keys(uchar *keybuff, uint key_count, KEY *keyinfo, pos=tmp; } *(pos++)=0; + for (key=keyinfo,end=keyinfo+key_count ; key != end ; key++) + { + if (key->flags & HA_USES_COMMENT) + { + int2store(pos, key->comment.length); + uchar *tmp= (uchar*)strnmov((char*) pos+2,key->comment.str, + key->comment.length); + pos= tmp; + } + } if (key_count > 127 || key_parts > 127) { @@ -614,19 +657,23 @@ static bool pack_header(uchar *forminfo, enum legacy_db_type table_type, field->comment.str, field->comment.str + field->comment.length, - 255); + COLUMN_COMMENT_MAXLEN); if (tmp_len < field->comment.length) { if ((current_thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES))) { - my_error(ER_TOO_LONG_FIELD_COMMENT, MYF(0), field->field_name, tmp_len); + my_error(ER_TOO_LONG_FIELD_COMMENT, MYF(0), + field->field_name, (uint) COLUMN_COMMENT_MAXLEN); DBUG_RETURN(1); } - push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_TOO_LONG_FIELD_COMMENT, - ER(ER_TOO_LONG_FIELD_COMMENT), - field->field_name, tmp_len); + char warn_buff[MYSQL_ERRMSG_SIZE]; + my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_TOO_LONG_FIELD_COMMENT), + field->field_name, (uint) COLUMN_COMMENT_MAXLEN); + /* do not push duplicate warnings */ + if (!check_duplicate_warning(current_thd, warn_buff, strlen(warn_buff))) + push_warning(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_TOO_LONG_FIELD_COMMENT, warn_buff); field->comment.length= tmp_len; } diff --git a/sql/unireg.h b/sql/unireg.h index def4fb12b39..9932be7ae74 100644 --- a/sql/unireg.h +++ b/sql/unireg.h @@ -46,6 +46,9 @@ #define ER(X) CURRENT_THD_ERRMSGS[(X) - ER_ERROR_FIRST] #define ER_DEFAULT(X) DEFAULT_ERRMSGS[(X) - ER_ERROR_FIRST] #define ER_SAFE(X) (((X) >= ER_ERROR_FIRST && (X) <= ER_ERROR_LAST) ? ER(X) : "Invalid error code") +#define ER_THD(thd,X) ((thd)->variables.lc_messages->errmsgs->errmsgs[(X) - \ + ER_ERROR_FIRST]) +#define ER_THD_OR_DEFAULT(thd,X) ((thd) ? ER_THD(thd, X) : ER_DEFAULT(X)) #define ERRMAPP 1 /* Errormap f|r my_error */ |