diff options
Diffstat (limited to 'sql')
94 files changed, 2740 insertions, 2190 deletions
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 0860adde652..1bec2d7b082 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -283,7 +283,11 @@ IF(WIN32 AND MYSQLD_EXECUTABLE) COMPONENT DataFiles PATTERN "initdb.dep" EXCLUDE PATTERN "bootstrap.sql" EXCLUDE) ELSE() # Not windows or cross compiling, just install an empty directory - INSTALL(FILES ${DUMMY_FILE} DESTINATION data/mysql) + INSTALL(FILES ${DUMMY_FILE} DESTINATION data/mysql COMPONENT DataFiles) ENDIF() ENDIF() +ADD_CUSTOM_TARGET(show-dist-name + COMMAND ${CMAKE_COMMAND} -E echo "${CPACK_PACKAGE_FILE_NAME}" +) + diff --git a/sql/Makefile.am b/sql/Makefile.am deleted file mode 100644 index 16161798078..00000000000 --- a/sql/Makefile.am +++ /dev/null @@ -1,280 +0,0 @@ -# Copyright (C) 2000-2006 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 - -#called from the top level Makefile - -MYSQLDATAdir = $(localstatedir) -MYSQLSHAREdir = $(pkgdatadir) -MYSQLBASEdir= $(prefix) -MYSQLLIBdir= $(pkglibdir) -pkgplugindir = $(pkglibdir)/plugin -INCLUDES = @ZLIB_INCLUDES@ \ - -I$(top_builddir)/include -I$(top_srcdir)/include \ - -I$(top_srcdir)/regex -I$(srcdir) $(openssl_includes) -WRAPLIBS= @WRAPLIBS@ -SUBDIRS = share -libexec_PROGRAMS = mysqld -EXTRA_PROGRAMS = gen_lex_hash -bin_PROGRAMS = mysql_tzinfo_to_sql -DTRACEFILES = filesort.o \ - .libs/libndb_la-ha_ndbcluster.o \ - handler.o \ - mysqld.o \ - net_serv.o \ - scheduler.o \ - sp_head.o \ - sql_cache.o \ - sql_connect.o \ - sql_cursor.o \ - sql_delete.o \ - sql_truncate.o \ - sql_reload.o \ - sql_insert.o \ - datadict.o \ - sql_parse.o \ - sql_prepare.o \ - sql_select.o \ - sql_update.o - -DTRACEFILES_DEPEND = filesort.o \ - libndb_la-ha_ndbcluster.lo \ - handler.o \ - mysqld.o \ - net_serv.o \ - scheduler.o \ - sp_head.o \ - sql_cache.o \ - sql_connect.o \ - sql_cursor.o \ - sql_delete.o \ - sql_truncate.o \ - sql_reload.o \ - sql_insert.o \ - datadict.o \ - sql_parse.o \ - sql_prepare.o \ - sql_select.o \ - sql_update.o - - -noinst_LTLIBRARIES= libndb.la \ - udf_example.la - -SUPPORTING_LIBS = $(top_builddir)/vio/libvio.a \ - $(top_builddir)/mysys/libmysys.a \ - $(top_builddir)/dbug/libdbug.a \ - $(top_builddir)/regex/libregex.a \ - $(top_builddir)/strings/libmystrings.a -mysqld_DEPENDENCIES= @mysql_plugin_libs@ $(SUPPORTING_LIBS) libndb.la -LDADD = $(SUPPORTING_LIBS) @ZLIB_LIBS@ @NDB_SCI_LIBS@ -mysqld_LDADD = libndb.la \ - @MYSQLD_EXTRA_LDFLAGS@ \ - @mysql_plugin_libs@ \ - $(LDADD) $(CXXLDFLAGS) $(WRAPLIBS) @LIBDL@ \ - $(yassl_libs) $(openssl_libs) @MYSQLD_EXTRA_LIBS@ - -noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \ - item_strfunc.h item_timefunc.h \ - item_xmlfunc.h sql_plugin_services.h \ - item_create.h item_subselect.h item_row.h \ - sql_priv.h item_geofunc.h sql_bitmap.h \ - procedure.h sql_class.h sql_lex.h sql_list.h \ - sql_string.h unireg.h \ - sql_error.h field.h handler.h mysqld_suffix.h \ - sql_profile.h mysqld.h sql_help.h frm_crypt.h \ - ha_ndbcluster.h ha_ndbcluster_cond.h \ - ha_ndbcluster_binlog.h ha_ndbcluster_tables.h \ - ha_partition.h rpl_constants.h \ - debug_sync.h \ - opt_range.h protocol.h rpl_tblmap.h rpl_utility.h \ - rpl_reporting.h sql_locale.h sql_parse.h \ - log.h sql_show.h rpl_rli.h rpl_mi.h \ - sql_select.h structs.h table.h sql_udf.h hash_filo.h \ - lex.h lex_symbol.h sql_acl.h sql_crypt.h sql_base.h \ - sql_table.h key.h lock.h thr_malloc.h strfunc.h \ - sql_delete.h sql_insert.h sql_update.h sql_db.h \ - sql_connect.h sql_rename.h sql_time.h sql_tablespace.h \ - hostname.h sql_test.h records.h filesort.h \ - sql_derived.h sql_load.h sql_handler.h init.h \ - derror.h sql_union.h des_key_file.h sql_binlog.h \ - discover.h sql_manager.h sql_do.h \ - sql_repl.h slave.h rpl_filter.h rpl_injector.h \ - log_event.h rpl_record.h sql_const.h \ - log_event_old.h rpl_record_old.h \ - sql_sort.h sql_cache.h set_var.h sys_vars_shared.h \ - spatial.h gstream.h client_settings.h tzfile.h \ - tztime.h my_decimal.h keycaches.h \ - sp_head.h sp_pcontext.h sp_rcontext.h sp.h sp_cache.h \ - parse_file.h sql_view.h sql_trigger.h \ - sql_array.h sql_cursor.h events.h scheduler.h \ - event_db_repository.h event_queue.h \ - sql_plugin.h authors.h event_parse_data.h \ - event_data_objects.h event_scheduler.h \ - sql_partition.h partition_info.h partition_element.h \ - sql_audit.h sql_alter.h sql_partition_admin.h \ - contributors.h sql_servers.h sql_signal.h records.h \ - sql_prepare.h rpl_handler.h replication.h mdl.h \ - sql_plist.h transaction.h sys_vars.h sql_truncate.h \ - sql_admin.h sql_reload.h datadict.h - -mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ - item.cc item_sum.cc item_buff.cc item_func.cc \ - item_cmpfunc.cc item_strfunc.cc item_timefunc.cc \ - thr_malloc.cc item_create.cc item_subselect.cc \ - item_row.cc item_geofunc.cc item_xmlfunc.cc \ - field.cc strfunc.cc key.cc sql_class.cc sql_list.cc \ - net_serv.cc protocol.cc sql_state.c \ - lock.cc sql_string.cc sql_manager.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 \ - sql_reload.cc datadict.cc sql_profile.cc \ - sql_prepare.cc sql_error.cc sql_locale.cc \ - sql_update.cc sql_delete.cc uniques.cc sql_do.cc \ - procedure.cc sql_test.cc sql_admin.cc \ - sql_truncate.cc \ - log.cc init.cc derror.cc sql_acl.cc \ - unireg.cc des_key_file.cc \ - log_event.cc rpl_record.cc \ - log_event_old.cc rpl_record_old.cc \ - discover.cc sql_time.cc opt_range.cc opt_sum.cc \ - records.cc filesort.cc handler.cc \ - ha_partition.cc \ - debug_sync.cc \ - sql_db.cc sql_table.cc sql_rename.cc sql_crypt.cc \ - sql_load.cc mf_iocache.cc field_conv.cc sql_show.cc \ - sql_udf.cc sql_analyse.cc sql_analyse.h sql_cache.cc \ - slave.cc sql_repl.cc rpl_filter.cc rpl_tblmap.cc \ - rpl_utility.cc rpl_injector.cc rpl_rli.cc rpl_mi.cc \ - rpl_reporting.cc \ - sql_union.cc sql_derived.cc \ - sql_client.cc \ - repl_failsafe.h repl_failsafe.cc sql_view.cc \ - gstream.cc spatial.cc sql_help.cc sql_cursor.cc \ - tztime.cc my_decimal.cc\ - sp_head.cc sp_pcontext.cc sp_rcontext.cc sp.cc \ - sp_cache.cc parse_file.cc sql_trigger.cc \ - event_scheduler.cc event_data_objects.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 event_parse_data.cc sql_signal.cc \ - rpl_handler.cc mdl.cc transaction.cc sql_audit.cc \ - sql_alter.cc sql_partition_admin.cc sha2.cc - -nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c client_plugin.c - -libndb_la_CPPFLAGS= @ndbcluster_includes@ -libndb_la_SOURCES= ha_ndbcluster.cc \ - ha_ndbcluster_binlog.cc \ - ha_ndbcluster_cond.cc - -gen_lex_hash_SOURCES = gen_lex_hash.cc -gen_lex_hash_LDFLAGS = @NOINST_LDFLAGS@ - -mysql_tzinfo_to_sql_SOURCES = tztime.cc -mysql_tzinfo_to_sql_CXXFLAGS= -DTZINFO2SQL - -DEFS = -DMYSQL_SERVER \ - -DDEFAULT_MYSQL_HOME='"$(MYSQLBASEdir)"' \ - -DMYSQL_DATADIR='"$(MYSQLDATAdir)"' \ - -DSHAREDIR='"$(MYSQLSHAREdir)"' \ - -DPLUGINDIR='"$(pkgplugindir)"' \ - -DHAVE_EVENT_SCHEDULER \ - @DEFS@ - -BUILT_MAINT_SRC = sql_yacc.cc sql_yacc.h -BUILT_SOURCES = $(BUILT_MAINT_SRC) lex_hash.h link_sources -EXTRA_DIST = udf_example.c udf_example.def $(BUILT_MAINT_SRC) \ - nt_servc.cc nt_servc.h \ - message.mc message.h message.rc MSG00001.bin \ - CMakeLists.txt - -CLEANFILES = lex_hash.h sql_yacc.output link_sources -DISTCLEANFILES = $(EXTRA_PROGRAMS) -MAINTAINERCLEANFILES = $(BUILT_MAINT_SRC) -AM_YFLAGS = -d --verbose - -# These are listed in 'nodist_mysqld_SOURCES' -link_sources: - rm -f mini_client_errors.c - @LN_CP_F@ $(top_srcdir)/libmysql/errmsg.c mini_client_errors.c - rm -f pack.c - @LN_CP_F@ $(top_srcdir)/sql-common/pack.c pack.c - rm -f client.c - @LN_CP_F@ $(top_srcdir)/sql-common/client.c client.c - rm -f client_plugin.c - @LN_CP_F@ $(top_srcdir)/sql-common/client_plugin.c client_plugin.c - rm -f my_time.c - @LN_CP_F@ $(top_srcdir)/sql-common/my_time.c my_time.c - rm -f my_user.c - @LN_CP_F@ $(top_srcdir)/sql-common/my_user.c my_user.c - echo timestamp > link_sources - -# This generates lex_hash.h -# NOTE Built sources should depend on their sources not the tool -# this avoid the rebuild of the built files in a source dist -lex_hash.h: gen_lex_hash.cc lex.h - $(MAKE) $(AM_MAKEFLAGS) gen_lex_hash$(EXEEXT) - ./gen_lex_hash$(EXEEXT) > $@-t - $(MV) $@-t $@ - -# For testing of udf_example.so -udf_example_la_SOURCES= udf_example.c -udf_example_la_LDFLAGS= -module -rpath $(pkglibdir) - -# We might have some stuff not built in this build, but that we want to install -install-exec-hook: - $(mkinstalldirs) $(DESTDIR)$(libexecdir) $(DESTDIR)$(pkglibdir) - test ! -x mysqld-debug$(EXEEXT) || $(INSTALL_PROGRAM) mysqld-debug$(EXEEXT) $(DESTDIR)$(libexecdir) - test ! -f mysqld-debug.sym.gz || $(INSTALL_DATA) mysqld-debug.sym.gz $(DESTDIR)$(pkglibdir) - test ! -f mysqld.sym.gz || $(INSTALL_DATA) mysqld.sym.gz $(DESTDIR)$(pkglibdir) - -if HAVE_DTRACE_DASH_G -libndb_la_LIBADD = probes_libndb.o -libndb_la_DEPENDENCIES = dtrace_files dtrace_providers probes_libndb.o -mysqld_LDADD += probes_all.o -mysqld_DEPENDENCIES += dtrace_files dtrace_providers probes_all.o -CLEANFILES += dtrace_files dtrace_providers probes_all.o -DTRACEPROVIDER = probes_mysql.d -CLEANFILES += $(DTRACEPROVIDER) - -dtrace_files: - echo $(DTRACEFILES) > $@ -dtrace_providers: probes_mysql.d - echo $(DTRACEPROVIDER) > $@ -probes_mysql.d: - -$(RM) -f probes_mysql.d - $(CP) $(top_srcdir)/include/probes_mysql.d.base probes_mysql.d - -DTRACEDIRS = . ../mysys $(patsubst %,$(top_builddir)/storage/%,@mysql_se_dirs@) - -probes_all.o: probes_mysql.d $(DTRACEFILES_DEPEND) - providers=`(for i in $(DTRACEDIRS); do cat $$i/dtrace_providers 2>/dev/null; done) | tr " " "\n" | sort | uniq | sed -e '/^$$/d' -e 's/^/-s /'`; \ - objects=`for i in $(DTRACEDIRS); do f=\`cat $$i/dtrace_files 2>/dev/null\`; for j in $$f; do test -f $$i/$$j && echo "$$i/$$j "; done; done`; \ - $(DTRACE) $(DTRACEFLAGS) -G $$providers $$objects -o $@ - -# Can't depend directly on .libs/*.o, because there is no generated rule for -# that in the Makefile; it is a byproduct of *.lo -probes_libndb.o: probes_mysql.d libndb_la-ha_ndbcluster.lo - if test -f .libs/libndb_la-ha_ndbcluster.o ; then \ - $(DTRACE) $(DTRACEFLAGS) -G -s probes_mysql.d .libs/libndb_la-ha_ndbcluster.o -o $@; \ - fi; \ - if test -f libndb_la-ha_ndbcluster.o ; then \ - $(DTRACE) $(DTRACEFLAGS) -G -s probes_mysql.d libndb_la-ha_ndbcluster.o -o $@; \ - fi - -endif diff --git a/sql/debug_sync.cc b/sql/debug_sync.cc index 74e5b2c70f3..4f353597d6d 100644 --- a/sql/debug_sync.cc +++ b/sql/debug_sync.cc @@ -1691,7 +1691,7 @@ static void debug_sync_execute(THD *thd, st_debug_sync_action *action) if (action->execute) { - const char *old_proc_info; + const char *UNINIT_VAR(old_proc_info); action->execute--; diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index 52c509621ac..ef14a061677 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -290,7 +290,6 @@ Event_basic::load_time_zone(THD *thd, const LEX_STRING tz_name) */ Event_queue_element::Event_queue_element(): - status_changed(FALSE), last_executed_changed(FALSE), on_completion(Event_parse_data::ON_COMPLETION_DROP), status(Event_parse_data::ENABLED), expression(0), dropped(FALSE), execution_count(0) @@ -539,7 +538,6 @@ Event_queue_element::load_from_row(THD *thd, TABLE *table) TIME_NO_ZERO_DATE); last_executed= my_tz_OFFSET0->TIME_to_gmt_sec(&time,¬_used); } - last_executed_changed= FALSE; if ((ptr= get_field(&mem_root, table->field[ET_FIELD_STATUS])) == NullS) DBUG_RETURN(TRUE); @@ -935,7 +933,6 @@ Event_queue_element::compute_next_execution_time() DBUG_PRINT("info",("One-time event will be dropped: %d.", dropped)); status= Event_parse_data::DISABLED; - status_changed= TRUE; } goto ret; } @@ -955,7 +952,6 @@ Event_queue_element::compute_next_execution_time() dropped= TRUE; DBUG_PRINT("info", ("Dropped: %d", dropped)); status= Event_parse_data::DISABLED; - status_changed= TRUE; goto ret; } @@ -1018,7 +1014,6 @@ Event_queue_element::compute_next_execution_time() if (on_completion == Event_parse_data::ON_COMPLETION_DROP) dropped= TRUE; status= Event_parse_data::DISABLED; - status_changed= TRUE; } else { @@ -1108,7 +1103,6 @@ Event_queue_element::compute_next_execution_time() execute_at= 0; execute_at_null= TRUE; status= Event_parse_data::DISABLED; - status_changed= TRUE; if (on_completion == Event_parse_data::ON_COMPLETION_DROP) dropped= TRUE; } @@ -1144,50 +1138,11 @@ void Event_queue_element::mark_last_executed(THD *thd) { last_executed= (my_time_t) thd->query_start(); - last_executed_changed= TRUE; execution_count++; } -/* - Saves status and last_executed_at to the disk if changed. - - SYNOPSIS - Event_queue_element::update_timing_fields() - thd - thread context - - RETURN VALUE - FALSE OK - TRUE Error while opening mysql.event for writing or during - write on disk -*/ - -bool -Event_queue_element::update_timing_fields(THD *thd) -{ - Event_db_repository *db_repository= Events::get_db_repository(); - int ret; - - DBUG_ENTER("Event_queue_element::update_timing_fields"); - - DBUG_PRINT("enter", ("name: %*s", (int) name.length, name.str)); - - /* No need to update if nothing has changed */ - if (!(status_changed || last_executed_changed)) - DBUG_RETURN(0); - - ret= db_repository->update_timing_fields_for_event(thd, - dbname, name, - last_executed_changed, - last_executed, - status_changed, - (ulonglong) status); - last_executed_changed= status_changed= FALSE; - DBUG_RETURN(ret); -} - - static void append_datetime(String *buf, Time_zone *time_zone, my_time_t secs, @@ -1526,7 +1481,7 @@ end: thd->end_statement(); thd->cleanup_after_query(); /* Avoid races with SHOW PROCESSLIST */ - thd->set_query(NULL, 0); + thd->reset_query(); DBUG_PRINT("info", ("EXECUTED %s.%s ret: %d", dbname.str, name.str, ret)); diff --git a/sql/event_data_objects.h b/sql/event_data_objects.h index 9d17213bcb8..46740812d31 100644 --- a/sql/event_data_objects.h +++ b/sql/event_data_objects.h @@ -82,10 +82,6 @@ protected: class Event_queue_element : public Event_basic { -protected: - bool status_changed; - bool last_executed_changed; - public: int on_completion; int status; @@ -117,9 +113,6 @@ public: void mark_last_executed(THD *thd); - - bool - update_timing_fields(THD *thd); }; diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index db508e4ea41..053558aa0c3 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -622,6 +622,12 @@ Event_db_repository::create_event(THD *thd, Event_parse_data *parse_data, TABLE *table= NULL; sp_head *sp= thd->lex->sphead; ulong saved_mode= thd->variables.sql_mode; + /* + Take a savepoint to release only the lock on mysql.event + table at the end but keep the global read lock and + possible other locks taken by the caller. + */ + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("Event_db_repository::create_event"); @@ -699,8 +705,8 @@ Event_db_repository::create_event(THD *thd, Event_parse_data *parse_data, ret= 0; end: - if (table) - close_mysql_tables(thd); + close_thread_tables(thd); + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); thd->variables.sql_mode= saved_mode; DBUG_RETURN(test(ret)); @@ -734,6 +740,12 @@ Event_db_repository::update_event(THD *thd, Event_parse_data *parse_data, TABLE *table= NULL; sp_head *sp= thd->lex->sphead; ulong saved_mode= thd->variables.sql_mode; + /* + Take a savepoint to release only the lock on mysql.event + table at the end but keep the global read lock and + possible other locks taken by the caller. + */ + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); int ret= 1; DBUG_ENTER("Event_db_repository::update_event"); @@ -811,8 +823,8 @@ Event_db_repository::update_event(THD *thd, Event_parse_data *parse_data, ret= 0; end: - if (table) - close_mysql_tables(thd); + close_thread_tables(thd); + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); thd->variables.sql_mode= saved_mode; DBUG_RETURN(test(ret)); @@ -838,6 +850,12 @@ Event_db_repository::drop_event(THD *thd, LEX_STRING db, LEX_STRING name, bool drop_if_exists) { TABLE *table= NULL; + /* + Take a savepoint to release only the lock on mysql.event + table at the end but keep the global read lock and + possible other locks taken by the caller. + */ + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); int ret= 1; DBUG_ENTER("Event_db_repository::drop_event"); @@ -866,8 +884,8 @@ Event_db_repository::drop_event(THD *thd, LEX_STRING db, LEX_STRING name, ret= 0; end: - if (table) - close_mysql_tables(thd); + close_thread_tables(thd); + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); DBUG_RETURN(test(ret)); } @@ -940,7 +958,7 @@ Event_db_repository::drop_schema_events(THD *thd, LEX_STRING schema) TABLE *table= NULL; READ_RECORD read_record_info; enum enum_events_table_field field= ET_FIELD_DB; - MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint(); + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("Event_db_repository::drop_schema_events"); DBUG_PRINT("enter", ("field=%d schema=%s", field, schema.str)); @@ -1042,15 +1060,14 @@ Event_db_repository:: update_timing_fields_for_event(THD *thd, LEX_STRING event_db_name, LEX_STRING event_name, - bool update_last_executed, my_time_t last_executed, - bool update_status, ulonglong status) { TABLE *table= NULL; Field **fields; int ret= 1; bool save_binlog_row_based; + MYSQL_TIME time; DBUG_ENTER("Event_db_repository::update_timing_fields_for_event"); @@ -1075,20 +1092,12 @@ update_timing_fields_for_event(THD *thd, /* Don't update create on row update. */ table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET; - if (update_last_executed) - { - MYSQL_TIME time; - my_tz_OFFSET0->gmt_sec_to_TIME(&time, last_executed); + my_tz_OFFSET0->gmt_sec_to_TIME(&time, last_executed); + fields[ET_FIELD_LAST_EXECUTED]->set_notnull(); + fields[ET_FIELD_LAST_EXECUTED]->store_time(&time, MYSQL_TIMESTAMP_DATETIME); - fields[ET_FIELD_LAST_EXECUTED]->set_notnull(); - fields[ET_FIELD_LAST_EXECUTED]->store_time(&time, - MYSQL_TIMESTAMP_DATETIME); - } - if (update_status) - { - fields[ET_FIELD_STATUS]->set_notnull(); - fields[ET_FIELD_STATUS]->store(status, TRUE); - } + fields[ET_FIELD_STATUS]->set_notnull(); + fields[ET_FIELD_STATUS]->store(status, TRUE); if ((ret= table->file->ha_update_row(table->record[1], table->record[0]))) { diff --git a/sql/event_db_repository.h b/sql/event_db_repository.h index ea7f3bbac0e..58484d17f06 100644 --- a/sql/event_db_repository.h +++ b/sql/event_db_repository.h @@ -101,9 +101,7 @@ public: update_timing_fields_for_event(THD *thd, LEX_STRING event_db_name, LEX_STRING event_name, - bool update_last_executed, my_time_t last_executed, - bool update_status, ulonglong status); public: static bool diff --git a/sql/event_queue.cc b/sql/event_queue.cc index f0310c676d1..bdc8fa258a8 100644 --- a/sql/event_queue.cc +++ b/sql/event_queue.cc @@ -17,6 +17,8 @@ #include "unireg.h" #include "event_queue.h" #include "event_data_objects.h" +#include "event_db_repository.h" +#include "events.h" #include "sql_audit.h" #include "tztime.h" // my_tz_find, my_tz_OFFSET0, struct Time_zone #include "log.h" // sql_print_error @@ -444,7 +446,6 @@ Event_queue::recalculate_activation_times(THD *thd) for (i= 0; i < queue.elements; i++) { ((Event_queue_element*)queue_element(&queue, i))->compute_next_execution_time(); - ((Event_queue_element*)queue_element(&queue, i))->update_timing_fields(thd); } queue_fix(&queue); /* @@ -567,6 +568,8 @@ Event_queue::get_top_for_execution_if_time(THD *thd, { bool ret= FALSE; *event_name= NULL; + my_time_t UNINIT_VAR(last_executed); + int UNINIT_VAR(status); DBUG_ENTER("Event_queue::get_top_for_execution_if_time"); LOCK_QUEUE_DATA(); @@ -632,8 +635,14 @@ Event_queue::get_top_for_execution_if_time(THD *thd, top->execution_count++; (*event_name)->dropped= top->dropped; + /* + Save new values of last_executed timestamp and event status on stack + in order to be able to update event description in system table once + QUEUE_DATA lock is released. + */ + last_executed= top->last_executed; + status= top->status; - top->update_timing_fields(thd); if (top->status == Event_parse_data::DISABLED) { DBUG_PRINT("info", ("removing from the queue")); @@ -656,9 +665,16 @@ end: ret, (long) *event_name)); if (*event_name) + { DBUG_PRINT("info", ("db: %s name: %s", (*event_name)->dbname.str, (*event_name)->name.str)); + Event_db_repository *db_repository= Events::get_db_repository(); + (void) db_repository->update_timing_fields_for_event(thd, + (*event_name)->dbname, (*event_name)->name, + last_executed, (ulonglong) status); + } + DBUG_RETURN(ret); } @@ -741,11 +757,13 @@ Event_queue::cond_wait(THD *thd, struct timespec *abstime, const char* msg, thd->enter_cond(&COND_queue_state, &LOCK_event_queue, msg); - DBUG_PRINT("info", ("mysql_cond_%swait", abstime? "timed":"")); - if (!abstime) - mysql_cond_wait(&COND_queue_state, &LOCK_event_queue); - else - mysql_cond_timedwait(&COND_queue_state, &LOCK_event_queue, abstime); + if (!thd->killed) + { + if (!abstime) + mysql_cond_wait(&COND_queue_state, &LOCK_event_queue); + else + mysql_cond_timedwait(&COND_queue_state, &LOCK_event_queue, abstime); + } mutex_last_locked_in_func= func; mutex_last_locked_at_line= line; diff --git a/sql/events.cc b/sql/events.cc index e7e47801586..23a0dc9eb52 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -30,6 +30,7 @@ #include "event_scheduler.h" #include "sp_head.h" // for Stored_program_creation_ctx #include "set_var.h" +#include "lock.h" // lock_object_name /** @addtogroup Event_Scheduler @@ -77,7 +78,6 @@ Event_queue *Events::event_queue; Event_scheduler *Events::scheduler; Event_db_repository *Events::db_repository; ulong Events::opt_event_scheduler= Events::EVENTS_OFF; -mysql_mutex_t Events::LOCK_event_metadata; bool Events::check_system_tables_error= FALSE; @@ -340,7 +340,9 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, 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); + if (lock_object_name(thd, MDL_key::EVENT, + parse_data->dbname.str, parse_data->name.str)) + DBUG_RETURN(TRUE); /* On error conditions my_error() is called so no need to handle here */ if (!(ret= db_repository->create_event(thd, parse_data, if_not_exists))) @@ -388,7 +390,6 @@ Events::create_event(THD *thd, Event_parse_data *parse_data, } } } - mysql_mutex_unlock(&LOCK_event_metadata); /* Restore the state of binlog format */ DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); if (save_binlog_row_based) @@ -472,7 +473,9 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, 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); + if (lock_object_name(thd, MDL_key::EVENT, + parse_data->dbname.str, parse_data->name.str)) + DBUG_RETURN(TRUE); /* On error conditions my_error() is called so no need to handle here */ if (!(ret= db_repository->update_event(thd, parse_data, @@ -502,7 +505,6 @@ Events::update_event(THD *thd, Event_parse_data *parse_data, ret= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } } - mysql_mutex_unlock(&LOCK_event_metadata); /* Restore the state of binlog format */ DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); if (save_binlog_row_based) @@ -556,7 +558,9 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists) 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); + if (lock_object_name(thd, MDL_key::EVENT, + dbname.str, name.str)) + DBUG_RETURN(TRUE); /* On error conditions my_error() is called so no need to handle here */ if (!(ret= db_repository->drop_event(thd, dbname, name, if_exists))) { @@ -566,7 +570,6 @@ Events::drop_event(THD *thd, LEX_STRING dbname, LEX_STRING name, bool if_exists) DBUG_ASSERT(thd->query() && thd->query_length()); ret= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); } - mysql_mutex_unlock(&LOCK_event_metadata); /* Restore the state of binlog format */ DBUG_ASSERT(!thd->is_current_stmt_binlog_format_row()); if (save_binlog_row_based) @@ -595,15 +598,12 @@ Events::drop_schema_events(THD *thd, char *db) DBUG_PRINT("enter", ("dropping events from %s", db)); /* - sic: no check if the scheduler is disabled or system tables + Sic: no check if the scheduler is disabled or system tables are damaged, as intended. */ - - mysql_mutex_lock(&LOCK_event_metadata); if (event_queue) event_queue->drop_schema_events(thd, db_lex); db_repository->drop_schema_events(thd, db_lex); - mysql_mutex_unlock(&LOCK_event_metadata); DBUG_VOID_RETURN; } @@ -915,12 +915,11 @@ Events::deinit() } #ifdef HAVE_PSI_INTERFACE -PSI_mutex_key key_LOCK_event_metadata, key_LOCK_event_queue, +PSI_mutex_key key_LOCK_event_queue, key_event_scheduler_LOCK_scheduler_state; static PSI_mutex_info all_events_mutexes[]= { - { &key_LOCK_event_metadata, "LOCK_event_metadata", PSI_FLAG_GLOBAL}, { &key_LOCK_event_queue, "LOCK_event_queue", PSI_FLAG_GLOBAL}, { &key_event_scheduler_LOCK_scheduler_state, "Event_scheduler::LOCK_scheduler_state", PSI_FLAG_GLOBAL} }; @@ -974,23 +973,6 @@ Events::init_mutexes() #ifdef HAVE_PSI_INTERFACE init_events_psi_keys(); #endif - - mysql_mutex_init(key_LOCK_event_metadata, - &LOCK_event_metadata, MY_MUTEX_INIT_FAST); -} - - -/* - Destroys Events mutexes - - SYNOPSIS - Events::destroy_mutexes() -*/ - -void -Events::destroy_mutexes() -{ - mysql_mutex_destroy(&LOCK_event_metadata); } diff --git a/sql/events.h b/sql/events.h index f3ebc6da4ad..a337b29049a 100644 --- a/sql/events.h +++ b/sql/events.h @@ -26,8 +26,7 @@ */ #ifdef HAVE_PSI_INTERFACE -extern PSI_mutex_key key_LOCK_event_metadata, - key_event_scheduler_LOCK_scheduler_state; +extern PSI_mutex_key key_event_scheduler_LOCK_scheduler_state; extern PSI_cond_key key_event_scheduler_COND_state; extern PSI_thread_key key_thread_event_scheduler, key_thread_event_worker; #endif /* HAVE_PSI_INTERFACE */ @@ -79,7 +78,6 @@ public: enum enum_opt_event_scheduler { EVENTS_OFF, EVENTS_ON, EVENTS_DISABLED }; /* Protected using LOCK_global_system_variables only. */ static ulong opt_event_scheduler; - static mysql_mutex_t LOCK_event_metadata; static bool check_if_system_tables_error(); static bool start(); static bool stop(); diff --git a/sql/field.cc b/sql/field.cc index be7441f6bfd..e5cae9ea8e3 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -4189,7 +4189,7 @@ String *Field_float::val_str(String *val_buffer, String *val_ptr __attribute__((unused))) { ASSERT_COLUMN_MARKED_FOR_READ; - DBUG_ASSERT(field_length <= MAX_FIELD_CHARLENGTH); + DBUG_ASSERT(!zerofill || field_length <= MAX_FIELD_CHARLENGTH); float nr; #ifdef WORDS_BIGENDIAN if (table->s->db_low_byte_first) @@ -4512,7 +4512,7 @@ String *Field_double::val_str(String *val_buffer, String *val_ptr __attribute__((unused))) { ASSERT_COLUMN_MARKED_FOR_READ; - DBUG_ASSERT(field_length <= MAX_FIELD_CHARLENGTH); + DBUG_ASSERT(!zerofill || field_length <= MAX_FIELD_CHARLENGTH); double nr; #ifdef WORDS_BIGENDIAN if (table->s->db_low_byte_first) @@ -6327,10 +6327,13 @@ int Field_str::store(double nr) ASSERT_COLUMN_MARKED_FOR_WRITE; char buff[DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE]; uint local_char_length= field_length / charset()->mbmaxlen; - size_t length; - my_bool error; + size_t length= 0; + my_bool error= (local_char_length == 0); + + // my_gcvt() requires width > 0, and we may have a CHAR(0) column. + if (!error) + length= my_gcvt(nr, MY_GCVT_ARG_DOUBLE, local_char_length, buff, &error); - length= my_gcvt(nr, MY_GCVT_ARG_DOUBLE, local_char_length, buff, &error); if (error) { if (table->in_use->abort_on_warning) @@ -7725,12 +7728,6 @@ void Field_blob::sql_type(String &res) const uchar *Field_blob::pack(uchar *to, const uchar *from, uint max_length, bool low_byte_first) { - DBUG_ENTER("Field_blob::pack"); - DBUG_PRINT("enter", ("to: 0x%lx; from: 0x%lx;" - " max_length: %u; low_byte_first: %d", - (ulong) to, (ulong) from, - max_length, low_byte_first)); - DBUG_DUMP("record", from, table->s->reclength); uchar *save= ptr; ptr= (uchar*) from; uint32 length=get_length(); // Length of from string @@ -7751,8 +7748,7 @@ uchar *Field_blob::pack(uchar *to, const uchar *from, memcpy(to+packlength, from,length); } ptr=save; // Restore org row pointer - DBUG_DUMP("packed", to, packlength + length); - DBUG_RETURN(to+packlength+length); + return to+packlength+length; } @@ -8396,6 +8392,54 @@ uint Field_enum::is_equal(Create_field *new_field) } +uchar *Field_enum::pack(uchar *to, const uchar *from, + uint max_length, bool low_byte_first) +{ + DBUG_ENTER("Field_enum::pack"); + DBUG_PRINT("debug", ("packlength: %d", packlength)); + DBUG_DUMP("from", from, packlength); + + switch (packlength) + { + case 1: + *to = *from; + DBUG_RETURN(to + 1); + case 2: DBUG_RETURN(pack_int16(to, from, low_byte_first)); + case 3: DBUG_RETURN(pack_int24(to, from, low_byte_first)); + case 4: DBUG_RETURN(pack_int32(to, from, low_byte_first)); + case 8: DBUG_RETURN(pack_int64(to, from, low_byte_first)); + default: + DBUG_ASSERT(0); + } + MY_ASSERT_UNREACHABLE(); + DBUG_RETURN(NULL); +} + +const uchar *Field_enum::unpack(uchar *to, const uchar *from, + uint param_data, bool low_byte_first) +{ + DBUG_ENTER("Field_enum::unpack"); + DBUG_PRINT("debug", ("packlength: %d", packlength)); + DBUG_DUMP("from", from, packlength); + + switch (packlength) + { + case 1: + *to = *from; + DBUG_RETURN(from + 1); + + case 2: DBUG_RETURN(unpack_int16(to, from, low_byte_first)); + case 3: DBUG_RETURN(unpack_int24(to, from, low_byte_first)); + case 4: DBUG_RETURN(unpack_int32(to, from, low_byte_first)); + case 8: DBUG_RETURN(unpack_int64(to, from, low_byte_first)); + default: + DBUG_ASSERT(0); + } + MY_ASSERT_UNREACHABLE(); + DBUG_RETURN(NULL); +} + + /** @return returns 1 if the fields are equally defined diff --git a/sql/field.h b/sql/field.h index 7b250c34fe4..e3db95260c3 100644 --- a/sql/field.h +++ b/sql/field.h @@ -554,6 +554,48 @@ private: { return 0; } protected: + static void handle_int16(uchar *to, const uchar *from, + bool low_byte_first_from, bool low_byte_first_to) + { + int16 val; +#ifdef WORDS_BIGENDIAN + if (low_byte_first_from) + val = sint2korr(from); + else +#endif + shortget(val, from); + +#ifdef WORDS_BIGENDIAN + if (low_byte_first_to) + int2store(to, val); + else +#endif + shortstore(to, val); + } + + static void handle_int24(uchar *to, const uchar *from, + bool low_byte_first_from, bool low_byte_first_to) + { + int32 val; +#ifdef WORDS_BIGENDIAN + if (low_byte_first_from) + val = sint3korr(from); + else +#endif + val= (from[0] << 16) + (from[1] << 8) + from[2]; + +#ifdef WORDS_BIGENDIAN + if (low_byte_first_to) + int2store(to, val); + else +#endif + { + to[0]= 0xFF & (val >> 16); + to[1]= 0xFF & (val >> 8); + to[2]= 0xFF & val; + } + } + /* Helper function to pack()/unpack() int32 values */ @@ -598,6 +640,32 @@ protected: longlongstore(to, val); } + uchar *pack_int16(uchar *to, const uchar *from, bool low_byte_first_to) + { + handle_int16(to, from, table->s->db_low_byte_first, low_byte_first_to); + return to + sizeof(int16); + } + + const uchar *unpack_int16(uchar* to, const uchar *from, + bool low_byte_first_from) + { + handle_int16(to, from, low_byte_first_from, table->s->db_low_byte_first); + return from + sizeof(int16); + } + + uchar *pack_int24(uchar *to, const uchar *from, bool low_byte_first_to) + { + handle_int24(to, from, table->s->db_low_byte_first, low_byte_first_to); + return to + 3; + } + + const uchar *unpack_int24(uchar* to, const uchar *from, + bool low_byte_first_from) + { + handle_int24(to, from, low_byte_first_from, table->s->db_low_byte_first); + return from + 3; + } + uchar *pack_int32(uchar *to, const uchar *from, bool low_byte_first_to) { handle_int32(to, from, table->s->db_low_byte_first, low_byte_first_to); @@ -678,6 +746,17 @@ public: uchar null_bit_arg, utype unireg_check_arg, const char *field_name_arg, CHARSET_INFO *charset); Item_result result_type () const { return STRING_RESULT; } + /* + match_collation_to_optimize_range() is to distinguish in + range optimizer (see opt_range.cc) between real string types: + CHAR, VARCHAR, TEXT + and the other string-alike types with result_type() == STRING_RESULT: + DATE, TIME, DATETIME, TIMESTAMP + We need it to decide whether to test if collation of the operation + matches collation of the field (needed only for real string types). + QQ: shouldn't DATE/TIME types have their own XXX_RESULT types eventually? + */ + virtual bool match_collation_to_optimize_range() const=0; uint decimals() const { return NOT_FIXED_DEC; } int store(double nr); int store(longlong nr, bool unsigned_val)=0; @@ -918,41 +997,13 @@ public: virtual uchar *pack(uchar* to, const uchar *from, uint max_length, bool low_byte_first) { - int16 val; -#ifdef WORDS_BIGENDIAN - if (table->s->db_low_byte_first) - val = sint2korr(from); - else -#endif - shortget(val, from); - -#ifdef WORDS_BIGENDIAN - if (low_byte_first) - int2store(to, val); - else -#endif - shortstore(to, val); - return to + sizeof(val); + return pack_int16(to, from, low_byte_first); } virtual const uchar *unpack(uchar* to, const uchar *from, uint param_data, bool low_byte_first) { - int16 val; -#ifdef WORDS_BIGENDIAN - if (low_byte_first) - val = sint2korr(from); - else -#endif - shortget(val, from); - -#ifdef WORDS_BIGENDIAN - if (table->s->db_low_byte_first) - int2store(to, val); - else -#endif - shortstore(to, val); - return from + sizeof(val); + return unpack_int16(to, from, low_byte_first); } }; @@ -1187,6 +1238,7 @@ public: unireg_check_arg, field_name_arg, cs) {} enum_field_types type() const { return MYSQL_TYPE_NULL;} + bool match_collation_to_optimize_range() const { return FALSE; } int store(const char *to, uint length, CHARSET_INFO *cs) { null[0]=1; return 0; } int store(double nr) { null[0]=1; return 0; } @@ -1216,6 +1268,7 @@ public: Field_timestamp(bool maybe_null_arg, const char *field_name_arg, CHARSET_INFO *cs); enum_field_types type() const { return MYSQL_TYPE_TIMESTAMP;} + bool match_collation_to_optimize_range() const { return FALSE; } enum ha_base_keytype key_type() const { return HA_KEYTYPE_ULONG_INT; } enum Item_result cmp_type () const { return INT_RESULT; } enum Derivation derivation(void) const { return DERIVATION_NUMERIC; } @@ -1320,6 +1373,7 @@ public: :Field_str((uchar*) 0, MAX_DATE_WIDTH, maybe_null_arg ? (uchar*) "": 0,0, NONE, field_name_arg, cs) { flags|= BINARY_FLAG; } enum_field_types type() const { return MYSQL_TYPE_DATE;} + bool match_collation_to_optimize_range() const { return FALSE; } enum ha_base_keytype key_type() const { return HA_KEYTYPE_ULONG_INT; } enum Item_result cmp_type () const { return INT_RESULT; } enum Derivation derivation(void) const { return DERIVATION_NUMERIC; } @@ -1369,6 +1423,7 @@ public: NONE, field_name_arg, cs) { flags|= BINARY_FLAG; } enum_field_types type() const { return MYSQL_TYPE_DATE;} enum_field_types real_type() const { return MYSQL_TYPE_NEWDATE; } + bool match_collation_to_optimize_range() const { return FALSE; } enum ha_base_keytype key_type() const { return HA_KEYTYPE_UINT24; } enum Item_result cmp_type () const { return INT_RESULT; } enum Derivation derivation(void) const { return DERIVATION_NUMERIC; } @@ -1408,6 +1463,7 @@ public: :Field_str((uchar*) 0,8, maybe_null_arg ? (uchar*) "": 0,0, NONE, field_name_arg, cs) { flags|= BINARY_FLAG; } enum_field_types type() const { return MYSQL_TYPE_TIME;} + bool match_collation_to_optimize_range() const { return FALSE; } enum ha_base_keytype key_type() const { return HA_KEYTYPE_INT24; } enum Item_result cmp_type () const { return INT_RESULT; } enum Derivation derivation(void) const { return DERIVATION_NUMERIC; } @@ -1447,6 +1503,7 @@ public: :Field_str((uchar*) 0, MAX_DATETIME_WIDTH, maybe_null_arg ? (uchar*) "": 0,0, NONE, field_name_arg, cs) { flags|= BINARY_FLAG; } enum_field_types type() const { return MYSQL_TYPE_DATETIME;} + bool match_collation_to_optimize_range() const { return FALSE; } #ifdef HAVE_LONG_LONG enum ha_base_keytype key_type() const { return HA_KEYTYPE_ULONGLONG; } #endif @@ -1515,6 +1572,7 @@ public: orig_table->s->frm_version < FRM_VER_TRUE_VARCHAR ? MYSQL_TYPE_VAR_STRING : MYSQL_TYPE_STRING); } + bool match_collation_to_optimize_range() const { return TRUE; } enum ha_base_keytype key_type() const { return binary() ? HA_KEYTYPE_BINARY : HA_KEYTYPE_TEXT; } bool zero_pack() const { return 0; } @@ -1595,6 +1653,7 @@ public: } enum_field_types type() const { return MYSQL_TYPE_VARCHAR; } + bool match_collation_to_optimize_range() const { return TRUE; } enum ha_base_keytype key_type() const; uint row_pack_length() { return field_length; } bool zero_pack() const { return 0; } @@ -1690,6 +1749,7 @@ public: :Field_longstr((uchar*) 0, 0, (uchar*) "", 0, NONE, "temp", system_charset_info), packlength(packlength_arg) {} enum_field_types type() const { return MYSQL_TYPE_BLOB;} + bool match_collation_to_optimize_range() const { return TRUE; } enum ha_base_keytype key_type() const { return binary() ? HA_KEYTYPE_VARBINARY2 : HA_KEYTYPE_VARTEXT2; } int store(const char *to,uint length,CHARSET_INFO *charset); @@ -1839,6 +1899,7 @@ public: { geom_type= geom_type_arg; } enum ha_base_keytype key_type() const { return HA_KEYTYPE_VARBINARY2; } enum_field_types type() const { return MYSQL_TYPE_GEOMETRY; } + bool match_collation_to_optimize_range() const { return FALSE; } void sql_type(String &str) const; int store(const char *to, uint length, CHARSET_INFO *charset); int store(double nr); @@ -1870,6 +1931,7 @@ public: } Field *new_field(MEM_ROOT *root, TABLE *new_table, bool keep_type); enum_field_types type() const { return MYSQL_TYPE_STRING; } + bool match_collation_to_optimize_range() const { return FALSE; } enum Item_result cmp_type () const { return INT_RESULT; } enum Item_result cast_to_int_type () const { return INT_RESULT; } enum ha_base_keytype key_type() const; @@ -1895,6 +1957,12 @@ public: bool has_charset(void) const { return TRUE; } /* enum and set are sorted as integers */ CHARSET_INFO *sort_charset(void) const { return &my_charset_bin; } + + virtual uchar *pack(uchar *to, const uchar *from, + uint max_length, bool low_byte_first); + virtual const uchar *unpack(uchar *to, const uchar *from, + uint param_data, bool low_byte_first); + private: int do_save_field_metadata(uchar *first_byte); uint is_equal(Create_field *new_field); diff --git a/sql/field_conv.cc b/sql/field_conv.cc index ea6ff82e0aa..20c647ccc00 100644 --- a/sql/field_conv.cc +++ b/sql/field_conv.cc @@ -787,11 +787,8 @@ int field_conv(Field *to,Field *from) ((Field_varstring*)from)->length_bytes == ((Field_varstring*)to)->length_bytes)) { // Identical fields -#ifdef HAVE_purify - /* This may happen if one does 'UPDATE ... SET x=x' */ - if (to->ptr != from->ptr) -#endif - memcpy(to->ptr,from->ptr,to->pack_length()); + // to->ptr==from->ptr may happen if one does 'UPDATE ... SET x=x' + memmove(to->ptr, from->ptr, to->pack_length()); return 0; } } diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc index bc0f2258a0a..20745db9ce9 100644 --- a/sql/ha_ndbcluster.cc +++ b/sql/ha_ndbcluster.cc @@ -6435,8 +6435,8 @@ void ha_ndbcluster::get_auto_increment(ulonglong offset, ulonglong increment, for (;;) { Ndb_tuple_id_range_guard g(m_share); - if (m_skip_auto_increment && - ndb->readAutoIncrementValue(m_table, g.range, auto_value) || + if ((m_skip_auto_increment && + ndb->readAutoIncrementValue(m_table, g.range, auto_value)) || ndb->getAutoIncrementValue(m_table, g.range, auto_value, cache_size, increment, offset)) { if (--retries && @@ -7382,38 +7382,35 @@ int ndbcluster_find_files(handlerton *hton, THD *thd, } /* + Delete old files. + 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. + while holding shared meta-data lock on other tables. This might lead to a + deadlock but such a deadlock should be detected by MDL deadlock detector. + XXX: the scenario described above is not covered with any test. */ - if (!global_read_lock) - { - // Delete old files - List_iterator_fast<char> it3(delete_list); - while ((file_name_str= it3++)) - { - DBUG_PRINT("info", ("Remove table %s/%s", db, file_name_str)); - // Delete the table and all related files - TABLE_LIST table_list; - table_list.init_one_table(db, strlen(db), file_name_str, - strlen(file_name_str), file_name_str, - TL_WRITE); - table_list.mdl_request.set_type(MDL_EXCLUSIVE); - (void)mysql_rm_table_part2(thd, &table_list, - FALSE, /* if_exists */ - FALSE, /* drop_temporary */ - FALSE, /* drop_view */ - TRUE /* dont_log_query*/); - trans_commit_implicit(thd); /* Safety, should be unnecessary. */ - thd->mdl_context.release_transactional_locks(); - /* Clear error message that is returned when table is deleted */ - thd->clear_error(); - } + List_iterator_fast<char> it3(delete_list); + while ((file_name_str= it3++)) + { + DBUG_PRINT("info", ("Remove table %s/%s", db, file_name_str)); + /* Delete the table and all related files. */ + TABLE_LIST table_list; + table_list.init_one_table(db, strlen(db), file_name_str, + strlen(file_name_str), file_name_str, + TL_WRITE); + table_list.mdl_request.set_type(MDL_EXCLUSIVE); + (void)mysql_rm_table_part2(thd, &table_list, + FALSE, /* if_exists */ + FALSE, /* drop_temporary */ + FALSE, /* drop_view */ + TRUE /* dont_log_query*/); + trans_commit_implicit(thd); /* Safety, should be unnecessary. */ + thd->mdl_context.release_transactional_locks(); + /* Clear error message that is returned when table is deleted */ + thd->clear_error(); } /* Lock mutex before creating .FRM files. */ @@ -10271,8 +10268,8 @@ bool ha_ndbcluster::check_if_incompatible_data(HA_CREATE_INFO *create_info, { Field *field= table->field[i]; const NDBCOL *col= tab->getColumn(i); - if (col->getStorageType() == NDB_STORAGETYPE_MEMORY && create_info->storage_media != HA_SM_MEMORY || - col->getStorageType() == NDB_STORAGETYPE_DISK && create_info->storage_media != HA_SM_DISK) + if ((col->getStorageType() == NDB_STORAGETYPE_MEMORY && create_info->storage_media != HA_SM_MEMORY) || + (col->getStorageType() == NDB_STORAGETYPE_DISK && create_info->storage_media != HA_SM_DISK)) { DBUG_PRINT("info", ("Column storage media is changed")); DBUG_RETURN(COMPATIBLE_DATA_NO); diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index 3d8d5ae9eb8..f4e240e5cc1 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -3093,7 +3093,9 @@ int ha_partition::write_row(uchar * buf) bool have_auto_increment= table->next_number_field && buf == table->record[0]; my_bitmap_map *old_map; THD *thd= ha_thd(); - timestamp_auto_set_type orig_timestamp_type= table->timestamp_field_type; + timestamp_auto_set_type saved_timestamp_type= table->timestamp_field_type; + ulong saved_sql_mode= thd->variables.sql_mode; + bool saved_auto_inc_field_not_null= table->auto_increment_field_not_null; #ifdef NOT_NEEDED uchar *rec0= m_rec0; #endif @@ -3129,6 +3131,22 @@ int ha_partition::write_row(uchar * buf) */ if (error) goto exit; + + /* + Don't allow generation of auto_increment value the partitions handler. + If a partitions handler would change the value, then it might not + match the partition any longer. + This can occur if 'SET INSERT_ID = 0; INSERT (NULL)', + So allow this by adding 'MODE_NO_AUTO_VALUE_ON_ZERO' to sql_mode. + The partitions handler::next_insert_id must always be 0. Otherwise + we need to forward release_auto_increment, or reset it for all + partitions. + */ + if (table->next_number_field->val_int() == 0) + { + table->auto_increment_field_not_null= TRUE; + thd->variables.sql_mode|= MODE_NO_AUTO_VALUE_ON_ZERO; + } } old_map= dbug_tmp_use_all_columns(table, table->read_set); @@ -3162,7 +3180,9 @@ int ha_partition::write_row(uchar * buf) set_auto_increment_if_higher(table->next_number_field); reenable_binlog(thd); exit: - table->timestamp_field_type= orig_timestamp_type; + thd->variables.sql_mode= saved_sql_mode; + table->auto_increment_field_not_null= saved_auto_inc_field_not_null; + table->timestamp_field_type= saved_timestamp_type; DBUG_RETURN(error); } @@ -3229,11 +3249,24 @@ int ha_partition::update_row(const uchar *old_data, uchar *new_data) } else { + Field *saved_next_number_field= table->next_number_field; + /* + Don't allow generation of auto_increment value for update. + table->next_number_field is never set on UPDATE. + But is set for INSERT ... ON DUPLICATE KEY UPDATE, + and since update_row() does not generate or update an auto_inc value, + we cannot have next_number_field set when moving a row + to another partition with write_row(), since that could + generate/update the auto_inc value. + This gives the same behavior for partitioned vs non partitioned tables. + */ + table->next_number_field= NULL; DBUG_PRINT("info", ("Update from partition %d to partition %d", old_part_id, new_part_id)); tmp_disable_binlog(thd); /* Do not replicate the low-level changes. */ error= m_file[new_part_id]->ha_write_row(new_data); reenable_binlog(thd); + table->next_number_field= saved_next_number_field; if (error) goto exit; @@ -4392,8 +4425,12 @@ int ha_partition::index_read_idx_map(uchar *buf, uint index, get_partition_set(table, buf, index, &m_start_key, &m_part_spec); - /* How can it be more than one partition with the current use? */ - DBUG_ASSERT(m_part_spec.start_part == m_part_spec.end_part); + /* + We have either found exactly 1 partition + (in which case start_part == end_part) + or no matching partitions (start_part > end_part) + */ + DBUG_ASSERT(m_part_spec.start_part >= m_part_spec.end_part); for (part= m_part_spec.start_part; part <= m_part_spec.end_part; part++) { @@ -4628,6 +4665,7 @@ int ha_partition::partition_scan_set_up(uchar * buf, bool idx_read_flag) key not found. */ DBUG_PRINT("info", ("scan with no partition to scan")); + table->status= STATUS_NOT_FOUND; DBUG_RETURN(HA_ERR_END_OF_FILE); } if (m_part_spec.start_part == m_part_spec.end_part) @@ -4652,6 +4690,7 @@ int ha_partition::partition_scan_set_up(uchar * buf, bool idx_read_flag) if (start_part == MY_BIT_NONE) { DBUG_PRINT("info", ("scan with no partition to scan")); + table->status= STATUS_NOT_FOUND; DBUG_RETURN(HA_ERR_END_OF_FILE); } if (start_part > m_part_spec.start_part) @@ -6375,9 +6414,42 @@ bool ha_partition::get_error_message(int error, String *buf) */ uint ha_partition::alter_table_flags(uint flags) { + uint flags_to_return, flags_to_check; DBUG_ENTER("ha_partition::alter_table_flags"); - DBUG_RETURN(ht->alter_table_flags(flags) | - m_file[0]->alter_table_flags(flags)); + + flags_to_return= ht->alter_table_flags(flags); + flags_to_return|= m_file[0]->alter_table_flags(flags); + + /* + If one partition fails we must be able to revert the change for the other, + already altered, partitions. So both ADD and DROP can only be supported in + pairs. + */ + flags_to_check= HA_ONLINE_ADD_INDEX_NO_WRITES; + flags_to_check|= HA_ONLINE_DROP_INDEX_NO_WRITES; + if ((flags_to_return & flags_to_check) != flags_to_check) + flags_to_return&= ~flags_to_check; + flags_to_check= HA_ONLINE_ADD_UNIQUE_INDEX_NO_WRITES; + flags_to_check|= HA_ONLINE_DROP_UNIQUE_INDEX_NO_WRITES; + if ((flags_to_return & flags_to_check) != flags_to_check) + flags_to_return&= ~flags_to_check; + flags_to_check= HA_ONLINE_ADD_PK_INDEX_NO_WRITES; + flags_to_check|= HA_ONLINE_DROP_PK_INDEX_NO_WRITES; + if ((flags_to_return & flags_to_check) != flags_to_check) + flags_to_return&= ~flags_to_check; + flags_to_check= HA_ONLINE_ADD_INDEX; + flags_to_check|= HA_ONLINE_DROP_INDEX; + if ((flags_to_return & flags_to_check) != flags_to_check) + flags_to_return&= ~flags_to_check; + flags_to_check= HA_ONLINE_ADD_UNIQUE_INDEX; + flags_to_check|= HA_ONLINE_DROP_UNIQUE_INDEX; + if ((flags_to_return & flags_to_check) != flags_to_check) + flags_to_return&= ~flags_to_check; + flags_to_check= HA_ONLINE_ADD_PK_INDEX; + flags_to_check|= HA_ONLINE_DROP_PK_INDEX; + if ((flags_to_return & flags_to_check) != flags_to_check) + flags_to_return&= ~flags_to_check; + DBUG_RETURN(flags_to_return); } @@ -6412,6 +6484,7 @@ int ha_partition::add_index(TABLE *table_arg, KEY *key_info, uint num_of_keys) handler **file; int ret= 0; + DBUG_ENTER("ha_partition::add_index"); /* There has already been a check in fix_partition_func in mysql_alter_table before this call, which checks for unique/primary key violations of the @@ -6419,8 +6492,28 @@ int ha_partition::add_index(TABLE *table_arg, KEY *key_info, uint num_of_keys) */ for (file= m_file; *file; file++) if ((ret= (*file)->add_index(table_arg, key_info, num_of_keys))) - break; - return ret; + goto err; + DBUG_RETURN(ret); +err: + if (file > m_file) + { + uint *key_numbers= (uint*) ha_thd()->alloc(sizeof(uint) * num_of_keys); + uint old_num_of_keys= table_arg->s->keys; + uint i; + /* The newly created keys have the last id's */ + for (i= 0; i < num_of_keys; i++) + key_numbers[i]= i + old_num_of_keys; + if (!table_arg->key_info) + table_arg->key_info= key_info; + while (--file >= m_file) + { + (void) (*file)->prepare_drop_index(table_arg, key_numbers, num_of_keys); + (void) (*file)->final_drop_index(table_arg); + } + if (table_arg->key_info == key_info) + table_arg->key_info= NULL; + } + DBUG_RETURN(ret); } diff --git a/sql/handler.cc b/sql/handler.cc index 1542dd99ec6..38b57c16ee0 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -29,8 +29,6 @@ #include "sql_cache.h" // query_cache, query_cache_* #include "key.h" // key_copy, key_unpack, key_cmp_if_same, key_cmp #include "sql_table.h" // build_table_filename -#include "lock.h" // wait_if_global_read_lock, - // start_waiting_global_read_lock #include "sql_parse.h" // check_stack_overrun #include "sql_acl.h" // SUPER_ACL #include "sql_base.h" // free_io_cache @@ -41,6 +39,7 @@ #include "transaction.h" #include <errno.h> #include "probes_mysql.h" +#include "debug_sync.h" // DEBUG_SYNC #ifdef WITH_PARTITION_STORAGE_ENGINE #include "ha_partition.h" @@ -1155,8 +1154,9 @@ int ha_commit_trans(THD *thd, bool all) { uint rw_ha_count; bool rw_trans; + MDL_request mdl_request; - DBUG_EXECUTE_IF("crash_commit_before", DBUG_ABORT();); + DBUG_EXECUTE_IF("crash_commit_before", DBUG_SUICIDE();); /* Close all cursors that can not survive COMMIT */ if (is_real_trans) /* not a statement commit */ @@ -1166,11 +1166,27 @@ int ha_commit_trans(THD *thd, bool all) /* rw_trans is TRUE when we in a transaction changing data */ rw_trans= is_real_trans && (rw_ha_count > 0); - if (rw_trans && - thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, FALSE)) + if (rw_trans) { - ha_rollback_trans(thd, all); - DBUG_RETURN(1); + /* + Acquire a metadata lock which will ensure that COMMIT is blocked + by an active FLUSH TABLES WITH READ LOCK (and vice versa: + COMMIT in progress blocks FTWRL). + + We allow the owner of FTWRL to COMMIT; we assume that it knows + what it does. + */ + mdl_request.init(MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE, + MDL_EXPLICIT); + + if (thd->mdl_context.acquire_lock(&mdl_request, + thd->variables.lock_wait_timeout)) + { + ha_rollback_trans(thd, all); + DBUG_RETURN(1); + } + + DEBUG_SYNC(thd, "ha_commit_trans_after_acquire_commit_lock"); } if (rw_trans && @@ -1208,7 +1224,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", DBUG_ABORT();); + DBUG_EXECUTE_IF("crash_commit_after_prepare", DBUG_SUICIDE();); if (error || (is_real_trans && xid && (error= !(cookie= tc_log->log_xid(thd, xid))))) { @@ -1216,17 +1232,25 @@ int ha_commit_trans(THD *thd, bool all) error= 1; goto end; } - DBUG_EXECUTE_IF("crash_commit_after_log", DBUG_ABORT();); + DBUG_EXECUTE_IF("crash_commit_after_log", DBUG_SUICIDE();); } error=ha_commit_one_phase(thd, all) ? (cookie ? 2 : 1) : 0; - DBUG_EXECUTE_IF("crash_commit_before_unlog", DBUG_ABORT();); + DBUG_EXECUTE_IF("crash_commit_before_unlog", DBUG_SUICIDE();); if (cookie) tc_log->unlog(cookie, xid); - DBUG_EXECUTE_IF("crash_commit_after", DBUG_ABORT();); + DBUG_EXECUTE_IF("crash_commit_after", DBUG_SUICIDE();); RUN_HOOK(transaction, after_commit, (thd, FALSE)); end: - if (rw_trans) - thd->global_read_lock.start_waiting_global_read_lock(thd); + if (rw_trans && mdl_request.ticket) + { + /* + We do not always immediately release transactional locks + after ha_commit_trans() (see uses of ha_enable_transaction()), + thus we release the commit blocker lock as soon as it's + not needed. + */ + thd->mdl_context.release_lock(mdl_request.ticket); + } } /* Free resources and perform other cleanup even for 'empty' transactions. */ else if (is_real_trans) @@ -4160,7 +4184,7 @@ int handler::read_multi_range_first(KEY_MULTI_RANGE **found_range_p, */ int handler::read_multi_range_next(KEY_MULTI_RANGE **found_range_p) { - int result; + int UNINIT_VAR(result); DBUG_ENTER("handler::read_multi_range_next"); /* We should not be called after the last call returned EOF. */ diff --git a/sql/handler.h b/sql/handler.h index 325df003215..0e03ea17dde 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -174,6 +174,8 @@ /* These bits are set if different kinds of indexes can be created off-line without re-create of the table (but with a table lock). + Partitioning needs both ADD and DROP to be supported by its underlying + handlers, due to error handling, see bug#57778. */ #define HA_ONLINE_ADD_INDEX_NO_WRITES (1L << 0) /*add index w/lock*/ #define HA_ONLINE_DROP_INDEX_NO_WRITES (1L << 1) /*drop index w/lock*/ diff --git a/sql/item.cc b/sql/item.cc index b166f3e645f..8957e6e7133 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -226,8 +226,6 @@ bool Item::val_bool() */ String *Item::val_str_ascii(String *str) { - DBUG_ASSERT(fixed == 1); - if (!(collation.collation->state & MY_CS_NONASCII)) return val_str(str); @@ -1853,11 +1851,12 @@ bool agg_item_set_converter(DTCollation &coll, const char *fname, *arg= conv; else thd->change_item_tree(arg, conv); - /* - We do not check conv->fixed, because Item_func_conv_charset which can - be return by safe_charset_converter can't be fixed at creation - */ - conv->fix_fields(thd, arg); + + if (conv->fix_fields(thd, arg)) + { + res= TRUE; + break; // we cannot return here, we need to restore "arena". + } } if (arena) thd->restore_active_arena(arena, &backup); @@ -3458,19 +3457,16 @@ Item_param::set_value(THD *thd, sp_rcontext *ctx, Item **it) str_value.charset()); collation.set(str_value.charset(), DERIVATION_COERCIBLE); decimals= 0; - param_type= MYSQL_TYPE_STRING; break; } case REAL_RESULT: set_double(arg->val_real()); - param_type= MYSQL_TYPE_DOUBLE; break; case INT_RESULT: set_int(arg->val_int(), arg->max_length); - param_type= MYSQL_TYPE_LONG; break; case DECIMAL_RESULT: @@ -3482,8 +3478,6 @@ Item_param::set_value(THD *thd, sp_rcontext *ctx, Item **it) return TRUE; set_decimal(dv); - param_type= MYSQL_TYPE_NEWDECIMAL; - break; } @@ -3515,6 +3509,7 @@ void Item_param::set_out_param_info(Send_field *info) { m_out_param_info= info; + param_type= m_out_param_info->type; } @@ -3560,6 +3555,7 @@ void Item_param::make_field(Send_field *field) field->org_table_name= m_out_param_info->org_table_name; field->col_name= m_out_param_info->col_name; field->org_col_name= m_out_param_info->org_col_name; + field->length= m_out_param_info->length; field->charsetnr= m_out_param_info->charsetnr; field->flags= m_out_param_info->flags; @@ -5531,17 +5527,43 @@ static uint nr_of_decimals(const char *str, const char *end) break; } decimal_point= str; - for (; my_isdigit(system_charset_info, *str) ; str++) + for ( ; str < end && my_isdigit(system_charset_info, *str) ; str++) ; - if (*str == 'e' || *str == 'E') + if (str < end && (*str == 'e' || *str == 'E')) return NOT_FIXED_DEC; + /* + QQ: + The number of decimal digist in fact should be (str - decimal_point - 1). + But it seems the result of nr_of_decimals() is never used! + + In case of 'e' and 'E' nr_of_decimals returns NOT_FIXED_DEC. + In case if there is no 'e' or 'E' parser code in sql_yacc.yy + never calls Item_float::Item_float() - it creates Item_decimal instead. + + The only piece of code where we call Item_float::Item_float(str, len) + without having 'e' or 'E' is item_xmlfunc.cc, but this Item_float + never appears in metadata itself. Changing the code to return + (str - decimal_point - 1) does not make any changes in the test results. + + This should be addressed somehow. + Looks like a reminder from before real DECIMAL times. + */ return (uint) (str - decimal_point); } /** - This function is only called during parsing. We will signal an error if - value is not a true double value (overflow) + This function is only called during parsing: + - when parsing SQL query from sql_yacc.yy + - when parsing XPath query from item_xmlfunc.cc + We will signal an error if value is not a true double value (overflow): + eng: Illegal %s '%-.192s' value found during parsing + + Note: the string is NOT null terminated when called from item_xmlfunc.cc, + so this->name will contain some SQL query tail behind the "length" bytes. + This is Ok for now, as this Item is never seen in SHOW, + or EXPLAIN, or anywhere else in metadata. + Item->name should be fixed to use LEX_STRING eventually. */ Item_float::Item_float(const char *str_arg, uint length) @@ -5552,12 +5574,9 @@ Item_float::Item_float(const char *str_arg, uint length) &error); if (error) { - /* - Note that we depend on that str_arg is null terminated, which is true - when we are in the parser - */ - DBUG_ASSERT(str_arg[length] == 0); - my_error(ER_ILLEGAL_VALUE_FOR_TYPE, MYF(0), "double", (char*) str_arg); + char tmp[NAME_LEN + 1]; + my_snprintf(tmp, sizeof(tmp), "%.*s", length, str_arg); + my_error(ER_ILLEGAL_VALUE_FOR_TYPE, MYF(0), "double", tmp); } presentation= name=(char*) str_arg; decimals=(uint8) nr_of_decimals(str_arg, str_arg+length); @@ -7493,9 +7512,19 @@ void Item_cache_datetime::store(Item *item, longlong val_arg) } +void Item_cache_datetime::store(Item *item) +{ + Item_cache::store(item); + str_value_cached= FALSE; +} + String *Item_cache_datetime::val_str(String *str) { DBUG_ASSERT(fixed == 1); + + if ((value_cached || str_value_cached) && null_value) + return NULL; + if (!str_value_cached) { /* @@ -7509,6 +7538,8 @@ String *Item_cache_datetime::val_str(String *str) if (value_cached) { MYSQL_TIME ltime; + /* Return NULL in case of OOM/conversion error. */ + null_value= TRUE; if (str_value.alloc(MAX_DATE_STRING_REP_LENGTH)) return NULL; if (cached_field_type == MYSQL_TYPE_TIME) @@ -7531,13 +7562,14 @@ String *Item_cache_datetime::val_str(String *str) { int was_cut; longlong res; - res= number_to_datetime(val_int(), <ime, TIME_FUZZY_DATE, &was_cut); + res= number_to_datetime(int_value, <ime, TIME_FUZZY_DATE, &was_cut); if (res == -1) return NULL; } str_value.length(my_TIME_to_str(<ime, const_cast<char*>(str_value.ptr()))); str_value_cached= TRUE; + null_value= FALSE; } else if (!cache_value()) return NULL; @@ -7558,7 +7590,7 @@ my_decimal *Item_cache_datetime::val_decimal(my_decimal *decimal_val) double Item_cache_datetime::val_real() { DBUG_ASSERT(fixed == 1); - if (!value_cached && !cache_value_int()) + if ((!value_cached && !cache_value_int()) || null_value) return 0.0; return (double) int_value; } @@ -7566,7 +7598,7 @@ double Item_cache_datetime::val_real() longlong Item_cache_datetime::val_int() { DBUG_ASSERT(fixed == 1); - if (!value_cached && !cache_value_int()) + if ((!value_cached && !cache_value_int()) || null_value) return 0; return int_value; } diff --git a/sql/item.h b/sql/item.h index 8e8199ecac8..2249cc9dd4a 100644 --- a/sql/item.h +++ b/sql/item.h @@ -2572,7 +2572,7 @@ public: DBUG_ASSERT(fixed); return (*ref)->get_time(ltime); } - virtual bool basic_const_item() const { return (*ref)->basic_const_item(); } + virtual bool basic_const_item() const { return ref && (*ref)->basic_const_item(); } bool is_outer_field() const { DBUG_ASSERT(fixed); @@ -3451,8 +3451,8 @@ public: cmp_context= STRING_RESULT; } - virtual void store(Item *item) { Item_cache::store(item); } void store(Item *item, longlong val_arg); + void store(Item *item); double val_real(); longlong val_int(); String* val_str(String *str); diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index b04ec105468..f89eabaa868 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -4720,6 +4720,7 @@ bool Item_func_like::fix_fields(THD *thd, Item **ref) String *escape_str= escape_item->val_str(&cmp.value1); if (escape_str) { + const char *escape_str_ptr= escape_str->ptr(); if (escape_used_in_parsing && ( (((thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES) && escape_str->numchars() != 1) || @@ -4734,9 +4735,9 @@ bool Item_func_like::fix_fields(THD *thd, Item **ref) CHARSET_INFO *cs= escape_str->charset(); my_wc_t wc; int rc= cs->cset->mb_wc(cs, &wc, - (const uchar*) escape_str->ptr(), - (const uchar*) escape_str->ptr() + - escape_str->length()); + (const uchar*) escape_str_ptr, + (const uchar*) escape_str_ptr + + escape_str->length()); escape= (int) (rc > 0 ? wc : '\\'); } else @@ -4753,13 +4754,13 @@ bool Item_func_like::fix_fields(THD *thd, Item **ref) { char ch; uint errors; - uint32 cnvlen= copy_and_convert(&ch, 1, cs, escape_str->ptr(), + uint32 cnvlen= copy_and_convert(&ch, 1, cs, escape_str_ptr, escape_str->length(), escape_str->charset(), &errors); escape= cnvlen ? ch : '\\'; } else - escape= *(escape_str->ptr()); + escape= escape_str_ptr ? *escape_str_ptr : '\\'; } } else diff --git a/sql/item_func.cc b/sql/item_func.cc index 7906ed71bc7..92b80e274f4 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -178,7 +178,14 @@ Item_func::fix_fields(THD *thd, Item **ref) used_tables_cache= not_null_tables_cache= 0; const_item_cache=1; - if (check_stack_overrun(thd, STACK_MIN_SIZE, buff)) + /* + Use stack limit of STACK_MIN_SIZE * 2 since + on some platforms a recursive call to fix_fields + requires more than STACK_MIN_SIZE bytes (e.g. for + MIPS, it takes about 22kB to make one recursive + call to Item_func::fix_fields()) + */ + if (check_stack_overrun(thd, STACK_MIN_SIZE * 2, buff)) return TRUE; // Fatal error if flag is set! if (arg_count) { // Print purify happy @@ -3691,48 +3698,92 @@ longlong Item_master_pos_wait::val_int() } +/** + Enables a session to wait on a condition until a timeout or a network + disconnect occurs. + + @remark The connection is polled every m_interrupt_interval nanoseconds. +*/ + +class Interruptible_wait +{ + THD *m_thd; + struct timespec m_abs_timeout; + static const ulonglong m_interrupt_interval; + + public: + Interruptible_wait(THD *thd) + : m_thd(thd) {} + + ~Interruptible_wait() {} + + public: + /** + Set the absolute timeout. + + @param timeout The amount of time in nanoseconds to wait + */ + void set_timeout(ulonglong timeout) + { + /* + Calculate the absolute system time at the start so it can + be controlled in slices. It relies on the fact that once + the absolute time passes, the timed wait call will fail + automatically with a timeout error. + */ + set_timespec_nsec(m_abs_timeout, timeout); + } + + /** The timed wait. */ + int wait(mysql_cond_t *, mysql_mutex_t *); +}; + + +/** Time to wait before polling the connection status. */ +const ulonglong Interruptible_wait::m_interrupt_interval= 5 * ULL(1000000000); + /** - Wait for a given condition to be signaled within the specified timeout. + Wait for a given condition to be signaled. + + @param cond The condition variable to wait on. + @param mutex The associated mutex. - @param cond the condition variable to wait on - @param lock the associated mutex - @param abstime the amount of time in seconds to wait + @remark The absolute timeout is preserved across calls. @retval return value from mysql_cond_timedwait */ -#define INTERRUPT_INTERVAL (5 * ULL(1000000000)) - -static int interruptible_wait(THD *thd, mysql_cond_t *cond, - mysql_mutex_t *lock, double time) +int Interruptible_wait::wait(mysql_cond_t *cond, mysql_mutex_t *mutex) { int error; - struct timespec abstime; - ulonglong slice, timeout= (ulonglong) (time * 1000000000.0); + struct timespec timeout; - do + while (1) { /* Wait for a fixed interval. */ - if (timeout > INTERRUPT_INTERVAL) - slice= INTERRUPT_INTERVAL; - else - slice= timeout; + set_timespec_nsec(timeout, m_interrupt_interval); - timeout-= slice; - set_timespec_nsec(abstime, slice); - error= mysql_cond_timedwait(cond, lock, &abstime); + /* But only if not past the absolute timeout. */ + if (cmp_timespec(timeout, m_abs_timeout) > 0) + timeout= m_abs_timeout; + + error= mysql_cond_timedwait(cond, mutex, &timeout); if (error == ETIMEDOUT || error == ETIME) { /* Return error if timed out or connection is broken. */ - if (!timeout || !thd->is_connected()) + if (!cmp_timespec(timeout, m_abs_timeout) || !m_thd->is_connected()) break; } - } while (error && timeout); + /* Otherwise, propagate status to the caller. */ + else + break; + } return error; } + /** Get a user level lock. If the thread has an old lock this is first released. @@ -3748,10 +3799,11 @@ longlong Item_func_get_lock::val_int() { DBUG_ASSERT(fixed == 1); String *res=args[0]->val_str(&value); - double timeout= args[1]->val_real(); + ulonglong timeout= args[1]->val_int(); THD *thd=current_thd; User_level_lock *ull; int error; + Interruptible_wait timed_cond(thd); DBUG_ENTER("Item_func_get_lock::val_int"); /* @@ -3812,11 +3864,13 @@ longlong Item_func_get_lock::val_int() thd->mysys_var->current_mutex= &LOCK_user_locks; thd->mysys_var->current_cond= &ull->cond; + timed_cond.set_timeout(timeout * ULL(1000000000)); + error= 0; while (ull->locked && !thd->killed) { DBUG_PRINT("info", ("waiting on lock")); - error= interruptible_wait(thd, &ull->cond, &LOCK_user_locks, timeout); + error= timed_cond.wait(&ull->cond, &LOCK_user_locks); if (error == ETIMEDOUT || error == ETIME) { DBUG_PRINT("info", ("lock wait timeout")); @@ -4011,6 +4065,7 @@ void Item_func_benchmark::print(String *str, enum_query_type query_type) longlong Item_func_sleep::val_int() { THD *thd= current_thd; + Interruptible_wait timed_cond(thd); mysql_cond_t cond; double timeout; int error; @@ -4030,6 +4085,8 @@ longlong Item_func_sleep::val_int() if (timeout < 0.00001) return 0; + timed_cond.set_timeout((ulonglong) (timeout * 1000000000.0)); + mysql_cond_init(key_item_func_sleep_cond, &cond, NULL); mysql_mutex_lock(&LOCK_user_locks); @@ -4040,7 +4097,7 @@ longlong Item_func_sleep::val_int() error= 0; while (!thd->killed) { - error= interruptible_wait(thd, &cond, &LOCK_user_locks, timeout); + error= timed_cond.wait(&cond, &LOCK_user_locks); if (error == ETIMEDOUT || error == ETIME) break; error= 0; @@ -4274,7 +4331,7 @@ update_hash(user_var_entry *entry, bool set_null, void *ptr, uint length, length--; // Fix length change above entry->value[length]= 0; // Store end \0 } - memcpy(entry->value,ptr,length); + memmove(entry->value, ptr, length); if (type == DECIMAL_RESULT) ((my_decimal*)entry->value)->fix_buffer_pointer(); entry->length= length; @@ -5677,7 +5734,17 @@ void Item_func_match::init_search(bool no_order) /* Check if init_search() has been called before */ if (ft_handler) + { + /* + We should reset ft_handler as it is cleaned up + on destruction of FT_SELECT object + (necessary in case of re-execution of subquery). + TODO: FT_SELECT should not clean up ft_handler. + */ + if (join_key) + table->file->ft_handler= ft_handler; DBUG_VOID_RETURN; + } if (key == NO_SUCH_KEY) { diff --git a/sql/item_func.h b/sql/item_func.h index 15730516c71..63662d97019 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -1595,7 +1595,7 @@ public: join_key(0), ft_handler(0), table(0), master(0), concat_ws(0) { } void cleanup() { - DBUG_ENTER("Item_func_match"); + DBUG_ENTER("Item_func_match::cleanup"); Item_real_func::cleanup(); if (!master && ft_handler) ft_handler->please->close_search(ft_handler); diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 89c1e785c71..c71d8226f77 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -1080,9 +1080,15 @@ String *Item_func_replace::val_str(String *str) search=res2->ptr(); search_end=search+from_length; redo: + DBUG_ASSERT(res->ptr() || !offset); ptr=res->ptr()+offset; strend=res->ptr()+res->length(); - end=strend-from_length+1; + /* + In some cases val_str() can return empty string + with ptr() == NULL and length() == 0. + Let's check strend to avoid overflow. + */ + end= strend ? strend - from_length + 1 : NULL; while (ptr < end) { if (*ptr == *search) @@ -1184,6 +1190,20 @@ String *Item_func_insert::val_str(String *str) if ((length < 0) || (length > res->length())) length= res->length(); + /* + There is one exception not handled (intentionaly) by the character set + aggregation code. If one string is strong side and is binary, and + another one is weak side and is a multi-byte character string, + then we need to operate on the second string in terms on bytes when + calling ::numchars() and ::charpos(), rather than in terms of characters. + Lets substitute its character set to binary. + */ + if (collation.collation == &my_charset_bin) + { + res->set_charset(&my_charset_bin); + res2->set_charset(&my_charset_bin); + } + /* start and length are now sufficiently valid to pass to charpos function */ start= res->charpos((int) start); length= res->charpos((int) length, (uint32) start); @@ -2725,6 +2745,20 @@ String *Item_func_rpad::val_str(String *str) /* Set here so that rest of code sees out-of-bound value as such. */ if ((ulonglong) count > INT_MAX32) count= INT_MAX32; + /* + There is one exception not handled (intentionaly) by the character set + aggregation code. If one string is strong side and is binary, and + another one is weak side and is a multi-byte character string, + then we need to operate on the second string in terms on bytes when + calling ::numchars() and ::charpos(), rather than in terms of characters. + Lets substitute its character set to binary. + */ + if (collation.collation == &my_charset_bin) + { + res->set_charset(&my_charset_bin); + rpad->set_charset(&my_charset_bin); + } + if (count <= (res_char_length= res->numchars())) { // String to pad is big enough res->length(res->charpos((int) count)); // Shorten result if longer @@ -2814,6 +2848,20 @@ String *Item_func_lpad::val_str(String *str) if ((ulonglong) count > INT_MAX32) count= INT_MAX32; + /* + There is one exception not handled (intentionaly) by the character set + aggregation code. If one string is strong side and is binary, and + another one is weak side and is a multi-byte character string, + then we need to operate on the second string in terms on bytes when + calling ::numchars() and ::charpos(), rather than in terms of characters. + Lets substitute its character set to binary. + */ + if (collation.collation == &my_charset_bin) + { + res->set_charset(&my_charset_bin); + pad->set_charset(&my_charset_bin); + } + res_char_length= res->numchars(); if (count <= res_char_length) diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 5cf585e1a56..c1b1f47e5e9 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -1910,21 +1910,26 @@ int subselect_single_select_engine::exec() DBUG_RETURN(join->error ? join->error : 1); } if (!select_lex->uncacheable && thd->lex->describe && - !(join->select_options & SELECT_DESCRIBE) && - join->need_tmp) + !(join->select_options & SELECT_DESCRIBE)) { item->update_used_tables(); if (item->const_item()) { /* + It's necessary to keep original JOIN table because + create_sort_index() function may overwrite original + JOIN_TAB::type and wrong optimization method can be + selected on re-execution. + */ + select_lex->uncacheable|= UNCACHEABLE_EXPLAIN; + select_lex->master_unit()->uncacheable|= UNCACHEABLE_EXPLAIN; + /* Force join->join_tmp creation, because this subquery will be replaced by a simple select from the materialization temp table by optimize() called by EXPLAIN and we need to preserve the initial query structure so we can display it. */ - select_lex->uncacheable|= UNCACHEABLE_EXPLAIN; - select_lex->master_unit()->uncacheable|= UNCACHEABLE_EXPLAIN; - if (join->init_save_join_tab()) + if (join->need_tmp && join->init_save_join_tab()) DBUG_RETURN(1); /* purecov: inspected */ } } diff --git a/sql/item_sum.cc b/sql/item_sum.cc index b6e5d6d3182..8cd8e1bb222 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -3395,8 +3395,6 @@ String* Item_func_group_concat::val_str(String* str) void Item_func_group_concat::print(String *str, enum_query_type query_type) { - /* orig_args is not filled with valid values until fix_fields() */ - Item **pargs= fixed ? orig_args : args; str->append(STRING_WITH_LEN("group_concat(")); if (distinct) str->append(STRING_WITH_LEN("distinct ")); @@ -3404,7 +3402,7 @@ void Item_func_group_concat::print(String *str, enum_query_type query_type) { if (i) str->append(','); - pargs[i]->print(str, query_type); + orig_args[i]->print(str, query_type); } if (arg_count_order) { @@ -3413,7 +3411,7 @@ void Item_func_group_concat::print(String *str, enum_query_type query_type) { if (i) str->append(','); - pargs[i + arg_count_field]->print(str, query_type); + orig_args[i + arg_count_field]->print(str, query_type); if (order[i]->asc) str->append(STRING_WITH_LEN(" ASC")); else diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 49336b04e16..73b8b6047e0 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -2526,14 +2526,14 @@ String *Item_char_typecast::val_str(String *str) { // Convert character set if differ uint dummy_errors; - if (!(res= args[0]->val_str(&tmp_value)) || - str->copy(res->ptr(), res->length(), from_cs, - cast_cs, &dummy_errors)) + if (!(res= args[0]->val_str(str)) || + tmp_value.copy(res->ptr(), res->length(), from_cs, + cast_cs, &dummy_errors)) { null_value= 1; return 0; } - res= str; + res= &tmp_value; } res->set_charset(cast_cs); @@ -2568,9 +2568,9 @@ String *Item_char_typecast::val_str(String *str) { if (res->alloced_length() < (uint) cast_length) { - str->alloc(cast_length); - str->copy(*res); - res= str; + str_value.alloc(cast_length); + str_value.copy(*res); + res= &str_value; } bzero((char*) res->ptr() + res->length(), (uint) cast_length - res->length()); @@ -3465,7 +3465,7 @@ bool Item_func_str_to_date::get_date(MYSQL_TIME *ltime, uint fuzzy_date) return 0; null_date: - if (fuzzy_date & TIME_NO_ZERO_DATE) + if (val && (fuzzy_date & TIME_NO_ZERO_DATE)) { char buff[128]; strmake(buff, val->ptr(), min(val->length(), sizeof(buff)-1)); diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h index 004eb83cbeb..72a5aa0c296 100644 --- a/sql/item_timefunc.h +++ b/sql/item_timefunc.h @@ -361,6 +361,7 @@ public: const char *func_name() const { return "time_to_sec"; } void fix_length_and_dec() { + maybe_null= TRUE; decimals=0; max_length=10*MY_CHARSET_BIN_MB_MAXLEN; } diff --git a/sql/item_xmlfunc.cc b/sql/item_xmlfunc.cc index f124c37f0eb..49d18b1bb0f 100644 --- a/sql/item_xmlfunc.cc +++ b/sql/item_xmlfunc.cc @@ -2798,12 +2798,12 @@ String *Item_func_xml_extractvalue::val_str(String *str) null_value= 0; if (!nodeset_func || !(res= args[0]->val_str(str)) || - !parse_xml(res, &pxml)) + !parse_xml(res, &pxml) || + !(res= nodeset_func->val_str(&tmp_value))) { null_value= 1; return 0; } - res= nodeset_func->val_str(&tmp_value); return res; } diff --git a/sql/key.cc b/sql/key.cc index 582334620ad..288afd034a9 100644 --- a/sql/key.cc +++ b/sql/key.cc @@ -361,12 +361,10 @@ void key_unpack(String *to,TABLE *table,uint idx) if (field->binary() && field->type() == MYSQL_TYPE_STRING && tmp.length()) { const char *tmp_end= tmp.ptr() + tmp.length(); - while (tmp_end > tmp.ptr() && !*--tmp_end); + while (tmp_end > tmp.ptr() && !*--tmp_end) ; tmp.length(tmp_end - tmp.ptr() + 1); } - if (cs->mbmaxlen > 1 && - table->field[key_part->fieldnr - 1]->field_length != - key_part->length) + if (cs->mbmaxlen > 1 && (key_part->key_part_flag & HA_PART_KEY_SEG)) { /* Prefix key, multi-byte charset. diff --git a/sql/lock.cc b/sql/lock.cc index 0181a544824..2e141e1c9fb 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -777,8 +777,11 @@ bool lock_schema_name(THD *thd, const char *db) return TRUE; } - global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); - mdl_request.init(MDL_key::SCHEMA, db, "", MDL_EXCLUSIVE); + if (thd->global_read_lock.can_acquire_protection()) + return TRUE; + global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE, + MDL_STATEMENT); + mdl_request.init(MDL_key::SCHEMA, db, "", MDL_EXCLUSIVE, MDL_TRANSACTION); mdl_requests.push_front(&mdl_request); mdl_requests.push_front(&global_request); @@ -793,13 +796,13 @@ bool lock_schema_name(THD *thd, const char *db) /** - Obtain an exclusive metadata lock on the stored routine name. + Obtain an exclusive metadata lock on an object 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. + @param mdl_type Object type (currently functions, procedures + and events can be name-locked). + @param db The schema the object belongs to. + @param name Object name in the schema. This function assumes that no metadata locks were acquired before calling it. Additionally, it cannot be called while @@ -815,12 +818,9 @@ bool lock_schema_name(THD *thd, const char *db) or this connection was killed. */ -bool lock_routine_name(THD *thd, bool is_function, +bool lock_object_name(THD *thd, MDL_key::enum_mdl_namespace mdl_type, const char *db, const char *name) { - 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 schema_request; @@ -836,9 +836,13 @@ bool lock_routine_name(THD *thd, bool is_function, DBUG_ASSERT(name); DEBUG_SYNC(thd, "before_wait_locked_pname"); - global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); - schema_request.init(MDL_key::SCHEMA, db, "", MDL_INTENTION_EXCLUSIVE); - mdl_request.init(mdl_type, db, name, MDL_EXCLUSIVE); + if (thd->global_read_lock.can_acquire_protection()) + return TRUE; + global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE, + MDL_STATEMENT); + schema_request.init(MDL_key::SCHEMA, db, "", MDL_INTENTION_EXCLUSIVE, + MDL_TRANSACTION); + mdl_request.init(mdl_type, db, name, MDL_EXCLUSIVE, MDL_TRANSACTION); mdl_requests.push_front(&mdl_request); mdl_requests.push_front(&schema_request); @@ -888,45 +892,24 @@ static void print_lock_error(int error, const char *table) /**************************************************************************** Handling of global read locks + Global read lock is implemented using metadata lock infrastructure. + Taking the global read lock is TWO steps (2nd step is optional; without it, COMMIT of existing transactions will be allowed): lock_global_read_lock() THEN make_global_read_lock_block_commit(). - The global locks are handled through the global variables: - global_read_lock - count of threads which have the global read lock (i.e. have completed at - least the first step above) - global_read_lock_blocks_commit - count of threads which have the global read lock and block - commits (i.e. are in or have completed the second step above) - waiting_for_read_lock - count of threads which want to take a global read lock but cannot - protect_against_global_read_lock - count of threads which have set protection against global read lock. - - access to them is protected with a mutex LOCK_global_read_lock - - (XXX: one should never take LOCK_open if LOCK_global_read_lock is - taken, otherwise a deadlock may occur. Other mutexes could be a - problem too - grep the code for global_read_lock if you want to use - any other mutex here) Also one must not hold LOCK_open when calling - wait_if_global_read_lock(). When the thread with the global read lock - tries to close its tables, it needs to take LOCK_open in - close_thread_table(). - How blocking of threads by global read lock is achieved: that's - advisory. Any piece of code which should be blocked by global read lock must - be designed like this: - - call to wait_if_global_read_lock(). When this returns 0, no global read - lock is owned; if argument abort_on_refresh was 0, none can be obtained. - - job - - if abort_on_refresh was 0, call to start_waiting_global_read_lock() to - allow other threads to get the global read lock. I.e. removal of the - protection. - (Note: it's a bit like an implementation of rwlock). - - [ I am sorry to mention some SQL syntaxes below I know I shouldn't but found - no better descriptive way ] + semi-automatic. We assume that any statement which should be blocked + by global read lock will either open and acquires write-lock on tables + or acquires metadata locks on objects it is going to modify. For any + such statement global IX metadata lock is automatically acquired for + its duration (in case of LOCK TABLES until end of LOCK TABLES mode). + And lock_global_read_lock() simply acquires global S metadata lock + and thus prohibits execution of statements which modify data (unless + they modify only temporary tables). If deadlock happens it is detected + by MDL subsystem and resolved in the standard fashion (by backing-off + metadata locks acquired so far and restarting open tables process + if possible). Why does FLUSH TABLES WITH READ LOCK need to block COMMIT: because it's used to read a non-moving SHOW MASTER STATUS, and a COMMIT writes to the binary @@ -960,11 +943,6 @@ static void print_lock_error(int error, const char *table) ****************************************************************************/ -volatile uint global_read_lock=0; -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; - /** Take global read lock, wait if there is protection against lock. @@ -985,85 +963,17 @@ bool Global_read_lock::lock_global_read_lock(THD *thd) if (!m_state) { MDL_request mdl_request; - const char *old_message; - 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, - new_message); - DBUG_PRINT("info", - ("waiting_for: %d protect_against: %d", - waiting_for_read_lock, protect_against_global_read_lock)); - - waiting_for_read_lock++; - while (protect_against_global_read_lock && !thd->killed) - mysql_cond_wait(&COND_global_read_lock, &LOCK_global_read_lock); - waiting_for_read_lock--; - if (thd->killed) - { - thd->exit_cond(old_message); - DBUG_RETURN(1); - } - 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); + mdl_request.init(MDL_key::GLOBAL, "", "", MDL_SHARED, MDL_EXPLICIT); 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; + m_state= GRL_ACQUIRED; } /* We DON'T set global_read_lock_blocks_commit now, it will be set after @@ -1089,166 +999,22 @@ bool Global_read_lock::lock_global_read_lock(THD *thd) void Global_read_lock::unlock_global_read_lock(THD *thd) { - uint tmp; DBUG_ENTER("unlock_global_read_lock"); - DBUG_PRINT("info", - ("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 (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 */ - if (!tmp) + if (m_mdl_blocks_commits_lock) { - DBUG_PRINT("signal", ("Broadcasting COND_global_read_lock")); - mysql_cond_broadcast(&COND_global_read_lock); + thd->mdl_context.release_lock(m_mdl_blocks_commits_lock); + m_mdl_blocks_commits_lock= NULL; } + thd->mdl_context.release_lock(m_mdl_global_shared_lock); + m_mdl_global_shared_lock= NULL; 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 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. - */ - mysql_mutex_assert_not_owner(&LOCK_open); - - mysql_mutex_lock(&LOCK_global_read_lock); - if ((need_exit_cond= must_wait)) - { - if (m_state) // This thread had the read locks - { - if (is_not_commit) - my_message(ER_CANT_UPDATE_WITH_READLOCK, - ER(ER_CANT_UPDATE_WITH_READLOCK), MYF(0)); - mysql_mutex_unlock(&LOCK_global_read_lock); - /* - We allow FLUSHer to COMMIT; we assume FLUSHer knows what it does. - This allowance is needed to not break existing versions of innobackup - which do a BEGIN; INSERT; FLUSH TABLES WITH READ LOCK; COMMIT. - */ - DBUG_RETURN(is_not_commit); - } - old_message=thd->enter_cond(&COND_global_read_lock, &LOCK_global_read_lock, - "Waiting for release of readlock"); - while (must_wait && ! thd->killed && - (!abort_on_refresh || !thd->open_tables || - thd->open_tables->s->version == refresh_version)) - { - DBUG_PRINT("signal", ("Waiting for COND_global_read_lock")); - mysql_cond_wait(&COND_global_read_lock, &LOCK_global_read_lock); - DBUG_PRINT("signal", ("Got COND_global_read_lock")); - } - if (thd->killed) - 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 - */ - if (unlikely(need_exit_cond)) - thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock - else - mysql_mutex_unlock(&LOCK_global_read_lock); - DBUG_RETURN(result); -} - - -/** - 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"); - /* - 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); - tmp= (!--protect_against_global_read_lock && - (waiting_for_read_lock || global_read_lock_blocks_commit)); - mysql_mutex_unlock(&LOCK_global_read_lock); - if (tmp) - mysql_cond_broadcast(&COND_global_read_lock); - DBUG_VOID_RETURN; -} - /** Make global read lock also block commits. @@ -1267,8 +1033,7 @@ void Global_read_lock::start_waiting_global_read_lock(THD *thd) bool Global_read_lock::make_global_read_lock_block_commit(THD *thd) { - bool error; - const char *old_message; + MDL_request mdl_request; DBUG_ENTER("make_global_read_lock_block_commit"); /* If we didn't succeed lock_global_read_lock(), or if we already suceeded @@ -1276,42 +1041,32 @@ bool Global_read_lock::make_global_read_lock_block_commit(THD *thd) */ if (m_state != GRL_ACQUIRED) DBUG_RETURN(0); - mysql_mutex_lock(&LOCK_global_read_lock); - /* increment this BEFORE waiting on cond (otherwise race cond) */ - global_read_lock_blocks_commit++; - /* For testing we set up some blocking, to see if we can be killed */ - DBUG_EXECUTE_IF("make_global_read_lock_block_commit_loop", - protect_against_global_read_lock++;); - old_message= thd->enter_cond(&COND_global_read_lock, &LOCK_global_read_lock, - "Waiting for all running commits to finish"); - while (protect_against_global_read_lock && !thd->killed) - mysql_cond_wait(&COND_global_read_lock, &LOCK_global_read_lock); - DBUG_EXECUTE_IF("make_global_read_lock_block_commit_loop", - protect_against_global_read_lock--;); - if ((error= test(thd->killed))) - global_read_lock_blocks_commit--; // undo what we did - else - m_state= GRL_ACQUIRED_AND_BLOCKS_COMMIT; - thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock - DBUG_RETURN(error); + + mdl_request.init(MDL_key::COMMIT, "", "", MDL_SHARED, MDL_EXPLICIT); + + if (thd->mdl_context.acquire_lock(&mdl_request, + thd->variables.lock_wait_timeout)) + DBUG_RETURN(TRUE); + + m_mdl_blocks_commits_lock= mdl_request.ticket; + m_state= GRL_ACQUIRED_AND_BLOCKS_COMMIT; + + DBUG_RETURN(FALSE); } /** - Broadcast COND_global_read_lock. - - TODO/FIXME: Dmitry thinks that we broadcast on COND_global_read_lock - when old instance of table is closed to avoid races - between incrementing refresh_version and - wait_if_global_read_lock(thd, TRUE, FALSE) call. - Once global read lock implementation starts using MDL - infrastructure this will became unnecessary and should - be removed. + Set explicit duration for metadata locks which are used to implement GRL. + + @param thd Reference to thread. */ -void broadcast_refresh(void) +void Global_read_lock::set_explicit_lock_duration(THD *thd) { - mysql_cond_broadcast(&COND_global_read_lock); + if (m_mdl_global_shared_lock) + thd->mdl_context.set_lock_duration(m_mdl_global_shared_lock, MDL_EXPLICIT); + if (m_mdl_blocks_commits_lock) + thd->mdl_context.set_lock_duration(m_mdl_blocks_commits_lock, MDL_EXPLICIT); } /** diff --git a/sql/lock.h b/sql/lock.h index c097c8d269e..6f779595af8 100644 --- a/sql/lock.h +++ b/sql/lock.h @@ -2,6 +2,7 @@ #define LOCK_INCLUDED #include "thr_lock.h" /* thr_lock_type */ +#include "mdl.h" // Forward declarations struct TABLE; @@ -18,11 +19,10 @@ void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table); void mysql_lock_abort(THD *thd, TABLE *table, bool upgrade_lock); bool mysql_lock_abort_for_thread(THD *thd, TABLE *table); MYSQL_LOCK *mysql_lock_merge(MYSQL_LOCK *a,MYSQL_LOCK *b); -void broadcast_refresh(void); /* Lock based on name */ bool lock_schema_name(THD *thd, const char *db); /* Lock based on stored routine name */ -bool lock_routine_name(THD *thd, bool is_function, const char *db, - const char *name); +bool lock_object_name(THD *thd, MDL_key::enum_mdl_namespace mdl_type, + const char *db, const char *name); #endif /* LOCK_INCLUDED */ diff --git a/sql/log.cc b/sql/log.cc index 0e15c3b8e79..bfc5018b556 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -1362,7 +1362,7 @@ void LOGGER::deactivate_log_handler(THD *thd, uint log_type) file_log= file_log_handler->get_mysql_log(); break; default: - assert(0); // Impossible + MY_ASSERT_UNREACHABLE(); } if (!(*tmp_opt)) @@ -2177,7 +2177,11 @@ bool MYSQL_LOG::init_and_set_log_file_name(const char *log_name, 1 error */ -bool MYSQL_LOG::open(const char *log_name, enum_log_type log_type_arg, +bool MYSQL_LOG::open( +#ifdef HAVE_PSI_INTERFACE + PSI_file_key log_file_key, +#endif + const char *log_name, enum_log_type log_type_arg, const char *new_name, enum cache_type io_cache_type_arg) { char buff[FN_REFLEN]; @@ -2205,7 +2209,12 @@ bool MYSQL_LOG::open(const char *log_name, enum_log_type log_type_arg, db[0]= 0; - if ((file= mysql_file_open(key_file_MYSQL_LOG, +#ifdef HAVE_PSI_INTERFACE + /* Keep the key for reopen */ + m_log_file_key= log_file_key; +#endif + + if ((file= mysql_file_open(log_file_key, log_file_name, open_flags, MYF(MY_WME | ME_WAITTANG))) < 0 || init_io_cache(&log_file, file, IO_SIZE, io_cache_type, @@ -2389,7 +2398,11 @@ void MYSQL_QUERY_LOG::reopen_file() Note that at this point, log_state != LOG_CLOSED (important for is_open()). */ - open(save_name, log_type, 0, io_cache_type); + open( +#ifdef HAVE_PSI_INTERFACE + m_log_file_key, +#endif + save_name, log_type, 0, io_cache_type); my_free(save_name); mysql_mutex_unlock(&LOCK_log); @@ -2849,14 +2862,14 @@ 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", DBUG_ABORT();); + DBUG_EXECUTE_IF("crash_create_non_critical_before_update_index", DBUG_SUICIDE();); #endif write_error= 0; /* open the main log file */ - if (MYSQL_LOG::open(log_name, log_type_arg, new_name, - io_cache_type_arg)) + if (MYSQL_LOG::open(key_file_binlog, + log_name, log_type_arg, new_name, io_cache_type_arg)) { #ifdef HAVE_REPLICATION close_purge_index_file(); @@ -2946,7 +2959,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", DBUG_ABORT();); + DBUG_EXECUTE_IF("crash_create_critical_before_update_index", DBUG_SUICIDE();); #endif DBUG_ASSERT(my_b_inited(&index_file) != 0); @@ -2965,7 +2978,7 @@ bool MYSQL_BIN_LOG::open(const char *log_name, goto err; #ifdef HAVE_REPLICATION - DBUG_EXECUTE_IF("crash_create_after_update_index", DBUG_ABORT();); + DBUG_EXECUTE_IF("crash_create_after_update_index", DBUG_SUICIDE();); #endif } } @@ -3428,7 +3441,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", DBUG_ABORT();); + DBUG_EXECUTE_IF("crash_before_purge_logs", DBUG_SUICIDE();); mysql_mutex_lock(&rli->log_space_lock); rli->relay_log.purge_logs(to_purge_if_included, included, @@ -3556,7 +3569,7 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log, break; } - DBUG_EXECUTE_IF("crash_purge_before_update_index", DBUG_ABORT();); + DBUG_EXECUTE_IF("crash_purge_before_update_index", DBUG_SUICIDE();); if ((error= sync_purge_index_file())) { @@ -3571,7 +3584,7 @@ int MYSQL_BIN_LOG::purge_logs(const char *to_log, goto err; } - DBUG_EXECUTE_IF("crash_purge_critical_after_update_index", DBUG_ABORT();); + DBUG_EXECUTE_IF("crash_purge_critical_after_update_index", DBUG_SUICIDE();); err: /* Read each entry from purge_index_file and delete the file. */ @@ -3581,7 +3594,7 @@ err: " that would be purged."); close_purge_index_file(); - DBUG_EXECUTE_IF("crash_purge_non_critical_after_update_index", DBUG_ABORT();); + DBUG_EXECUTE_IF("crash_purge_non_critical_after_update_index", DBUG_SUICIDE();); if (need_mutex) mysql_mutex_unlock(&LOCK_index); @@ -5177,7 +5190,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")); - DBUG_ABORT(); + DBUG_SUICIDE(); }); if ((write_error= write_cache(cache, false, false))) @@ -5192,7 +5205,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", DBUG_ABORT();); + DBUG_EXECUTE_IF("half_binlogged_transaction", DBUG_SUICIDE();); if (cache->error) // Error on read { sql_print_error(ER(ER_ERROR_ON_READ), cache->file_name, errno); diff --git a/sql/log.h b/sql/log.h index 89b3594cd1e..d824d3afa26 100644 --- a/sql/log.h +++ b/sql/log.h @@ -196,7 +196,11 @@ public: MYSQL_LOG(); void init_pthread_objects(); void cleanup(); - bool open(const char *log_name, + bool open( +#ifdef HAVE_PSI_INTERFACE + PSI_file_key log_file_key, +#endif + const char *log_name, enum_log_type log_type, const char *new_name, enum cache_type io_cache_type_arg); @@ -223,6 +227,10 @@ public: volatile enum_log_state log_state; enum cache_type io_cache_type; friend class Log_event; +#ifdef HAVE_PSI_INTERFACE + /** Instrumentation key to use for file io in @c log_file */ + PSI_file_key m_log_file_key; +#endif }; class MYSQL_QUERY_LOG: public MYSQL_LOG @@ -241,14 +249,22 @@ public: bool open_slow_log(const char *log_name) { char buf[FN_REFLEN]; - return open(generate_name(log_name, "-slow.log", 0, buf), LOG_NORMAL, 0, - WRITE_CACHE); + return open( +#ifdef HAVE_PSI_INTERFACE + key_file_slow_log, +#endif + generate_name(log_name, "-slow.log", 0, buf), + LOG_NORMAL, 0, WRITE_CACHE); } bool open_query_log(const char *log_name) { char buf[FN_REFLEN]; - return open(generate_name(log_name, ".log", 0, buf), LOG_NORMAL, 0, - WRITE_CACHE); + return open( +#ifdef HAVE_PSI_INTERFACE + key_file_query_log, +#endif + generate_name(log_name, ".log", 0, buf), + LOG_NORMAL, 0, WRITE_CACHE); } private: diff --git a/sql/log_event.cc b/sql/log_event.cc index bfcac10e138..537865197da 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -1244,7 +1244,7 @@ Log_event* Log_event::read_log_event(const char* buf, uint event_len, break; #ifdef HAVE_REPLICATION case SLAVE_EVENT: /* can never happen (unused event) */ - ev = new Slave_log_event(buf, event_len); + ev = new Slave_log_event(buf, event_len, description_event); break; #endif /* HAVE_REPLICATION */ case CREATE_FILE_EVENT: @@ -1332,8 +1332,10 @@ Log_event* Log_event::read_log_event(const char* buf, uint event_len, (because constructor is "void") ; so instead we leave the pointer we wanted to allocate (e.g. 'query') to 0 and we test it in is_valid(). Same for Format_description_log_event, member 'post_header_len'. + + SLAVE_EVENT is never used, so it should not be read ever. */ - if (!ev || !ev->is_valid()) + if (!ev || !ev->is_valid() || (event_type == SLAVE_EVENT)) { DBUG_PRINT("error",("Found invalid event in binary log")); @@ -2330,7 +2332,7 @@ bool Query_log_event::write(IO_CACHE* file) start+= 4; } - if (thd && thd->is_current_user_used()) + if (thd && thd->need_binlog_invoker()) { LEX_STRING user; LEX_STRING host; @@ -3239,7 +3241,8 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli, if (is_trans_keyword() || rpl_filter->db_ok(thd->db)) { thd->set_time((time_t)when); - thd->set_query_and_id((char*)query_arg, q_len_arg, next_query_id()); + thd->set_query_and_id((char*)query_arg, q_len_arg, + thd->charset(), next_query_id()); thd->variables.pseudo_thread_id= thread_id; // for temp tables DBUG_PRINT("query",("%s", thd->query())); @@ -3291,6 +3294,18 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli, goto compare_errors; } thd->update_charset(); // for the charset change to take effect + /* + Reset thd->query_string.cs to the newly set value. + Note, there is a small flaw here. For a very short time frame + if the new charset is different from the old charset and + if another thread executes "SHOW PROCESSLIST" after + the above thd->set_query_and_id() and before this thd->set_query(), + and if the current query has some non-ASCII characters, + the another thread may see some '?' marks in the PROCESSLIST + result. This should be acceptable now. This is a reminder + to fix this if any refactoring happens here sometime. + */ + thd->set_query((char*) query_arg, q_len_arg, thd->charset()); } } if (time_zone_len) @@ -3351,6 +3366,8 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli, if (!parser_state.init(thd, thd->query(), thd->query_length())) { mysql_parse(thd, thd->query(), thd->query_length(), &parser_state); + /* Finalize server status flags after executing a statement. */ + thd->update_server_status(); log_slow_statement(thd); } @@ -3511,7 +3528,7 @@ end: */ thd->catalog= 0; thd->set_db(NULL, 0); /* will free the current database */ - thd->set_query(NULL, 0); + thd->reset_query(); DBUG_PRINT("info", ("end: query= 0")); /* As a disk space optimization, future masters will not log an event for @@ -4748,7 +4765,7 @@ int Load_log_event::do_apply_event(NET* net, Relay_log_info const *rli, new_db.str= (char *) rpl_filter->get_rewrite_db(db, &new_db.length); thd->set_db(new_db.str, new_db.length); DBUG_ASSERT(thd->query() == 0); - thd->set_query_inner(NULL, 0); // Should not be needed + thd->reset_query_inner(); // Should not be needed thd->is_slave_error= 0; clear_all_errors(thd, const_cast<Relay_log_info*>(rli)); @@ -4942,7 +4959,7 @@ error: const char *remember_db= thd->db; thd->catalog= 0; thd->set_db(NULL, 0); /* will free the current database */ - thd->set_query(NULL, 0); + thd->reset_query(); thd->stmt_da->can_overwrite_status= TRUE; thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); thd->stmt_da->can_overwrite_status= FALSE; @@ -4959,6 +4976,8 @@ error: */ if (! thd->in_multi_stmt_transaction_mode()) thd->mdl_context.release_transactional_locks(); + else + thd->mdl_context.release_statement_locks(); DBUG_EXECUTE_IF("LOAD_DATA_INFILE_has_fatal_error", thd->is_slave_error= 0; thd->is_fatal_error= 1;); @@ -6117,8 +6136,12 @@ void Slave_log_event::init_from_mem_pool(int data_size) /** This code is not used, so has not been updated to be format-tolerant. */ -Slave_log_event::Slave_log_event(const char* buf, uint event_len) - :Log_event(buf,0) /*unused event*/ ,mem_pool(0),master_host(0) +/* We are using description_event so that slave does not crash on Log_event + constructor */ +Slave_log_event::Slave_log_event(const char* buf, + uint event_len, + const Format_description_log_event* description_event) + :Log_event(buf,description_event),mem_pool(0),master_host(0) { if (event_len < LOG_EVENT_HEADER_LEN) return; @@ -8399,6 +8422,7 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli) m_field_metadata, m_field_metadata_size, m_null_bits, m_flags); table_list->m_tabledef_valid= TRUE; + table_list->open_type= OT_BASE_ONLY; /* We record in the slave's information that the table should be @@ -8503,7 +8527,7 @@ void Table_map_log_event::pack_info(Protocol *protocol) #ifdef MYSQL_CLIENT -void Table_map_log_event::print(FILE *file, PRINT_EVENT_INFO *print_event_info) +void Table_map_log_event::print(FILE *, PRINT_EVENT_INFO *print_event_info) { if (!print_event_info->short_form) { diff --git a/sql/log_event.h b/sql/log_event.h index 5d7250d8ebd..1841420ed86 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -1032,7 +1032,7 @@ public: return (void*) my_malloc((uint)size, MYF(MY_WME|MY_FAE)); } - static void operator delete(void *ptr, size_t size) + static void operator delete(void *ptr, size_t) { my_free(ptr); } @@ -1846,7 +1846,9 @@ public: void print(FILE* file, PRINT_EVENT_INFO* print_event_info); #endif - Slave_log_event(const char* buf, uint event_len); + Slave_log_event(const char* buf, + uint event_len, + const Format_description_log_event *description_event); ~Slave_log_event(); int get_data_size(); bool is_valid() const { return master_host != 0; } diff --git a/sql/mdl.cc b/sql/mdl.cc index d53ddcee0c8..3aed54ce12d 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -68,8 +68,6 @@ static void init_mdl_psi_keys(void) } #endif /* HAVE_PSI_INTERFACE */ -void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket); - /** Thread state names to be used in case when we have to wait on resource @@ -78,12 +76,14 @@ void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket); const char *MDL_key::m_namespace_to_wait_state_name[NAMESPACE_END]= { - "Waiting for global metadata lock", + "Waiting for global read lock", "Waiting for schema metadata lock", "Waiting for table metadata lock", "Waiting for stored function metadata lock", "Waiting for stored procedure metadata lock", - NULL + "Waiting for trigger metadata lock", + "Waiting for event metadata lock", + "Waiting for commit lock" }; static bool mdl_initialized= 0; @@ -100,7 +100,6 @@ 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: @@ -110,6 +109,10 @@ private: HASH m_locks; /* Protects access to m_locks hash. */ mysql_mutex_t m_mutex; + /** Pre-allocated MDL_lock object for GLOBAL namespace. */ + MDL_lock *m_global_lock; + /** Pre-allocated MDL_lock object for COMMIT namespace. */ + MDL_lock *m_commit_lock; }; @@ -354,18 +357,6 @@ public: 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); - } - } - void reschedule_waiters(); void remove_ticket(Ticket_list MDL_lock::*queue, MDL_ticket *ticket); @@ -373,6 +364,9 @@ public: bool visit_subgraph(MDL_ticket *waiting_ticket, MDL_wait_for_graph_visitor *gvisitor); + virtual bool needs_notification(const MDL_ticket *ticket) const = 0; + virtual void notify_conflicting_locks(MDL_context *ctx) = 0; + /** List of granted tickets for this lock. */ Ticket_list m_granted; /** Tickets for contexts waiting to acquire a lock. */ @@ -442,6 +436,11 @@ public: { return m_waiting_incompatible; } + virtual bool needs_notification(const MDL_ticket *ticket) const + { + return (ticket->get_type() == MDL_SHARED); + } + virtual void notify_conflicting_locks(MDL_context *ctx); private: static const bitmap_t m_granted_incompatible[MDL_TYPE_END]; @@ -469,6 +468,11 @@ public: { return m_waiting_incompatible; } + virtual bool needs_notification(const MDL_ticket *ticket) const + { + return ticket->is_upgradable_or_exclusive(); + } + virtual void notify_conflicting_locks(MDL_context *ctx); private: static const bitmap_t m_granted_incompatible[MDL_TYPE_END]; @@ -536,9 +540,14 @@ void mdl_destroy() void MDL_map::init() { + MDL_key global_lock_key(MDL_key::GLOBAL, "", ""); + MDL_key commit_lock_key(MDL_key::COMMIT, "", ""); + mysql_mutex_init(key_MDL_map_mutex, &m_mutex, NULL); my_hash_init(&m_locks, &my_charset_bin, 16 /* FIXME */, 0, 0, mdl_locks_key, 0, 0); + m_global_lock= MDL_lock::create(&global_lock_key); + m_commit_lock= MDL_lock::create(&commit_lock_key); } @@ -552,6 +561,8 @@ void MDL_map::destroy() DBUG_ASSERT(!m_locks.records); mysql_mutex_destroy(&m_mutex); my_hash_free(&m_locks); + MDL_lock::destroy(m_global_lock); + MDL_lock::destroy(m_commit_lock); } @@ -569,43 +580,28 @@ 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()))) + if (mdl_key->mdl_namespace() == MDL_key::GLOBAL || + mdl_key->mdl_namespace() == MDL_key::COMMIT) { - 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; + /* + Avoid locking m_mutex when lock for GLOBAL or COMMIT namespace is + requested. Return pointer to pre-allocated MDL_lock instance instead. + Such an optimization allows to save one mutex lock/unlock for any + statement changing data. - return lock; -} + It works since these namespaces contain only one element so keys + for them look like '<namespace-id>\0\0'. + */ + DBUG_ASSERT(mdl_key->length() == 3); + lock= (mdl_key->mdl_namespace() == MDL_key::GLOBAL) ? m_global_lock : + m_commit_lock; -/** - Find MDL_lock object corresponding to the key. + mysql_prlock_wrlock(&lock->m_rwlock); - @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. -*/ + return lock; + } -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()); @@ -616,8 +612,13 @@ retry: mdl_key->ptr(), mdl_key->length()))) { - mysql_mutex_unlock(&m_mutex); - return NULL; + 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)) @@ -684,6 +685,17 @@ void MDL_map::remove(MDL_lock *lock) { uint ref_usage, ref_release; + if (lock->key.mdl_namespace() == MDL_key::GLOBAL || + lock->key.mdl_namespace() == MDL_key::COMMIT) + { + /* + Never destroy pre-allocated MDL_lock objects for GLOBAL and + COMMIT namespaces. + */ + mysql_prlock_unlock(&lock->m_rwlock); + return; + } + /* Destroy the MDL_lock object, but ensure that anyone that is holding a reference to the object is not remaining, if so he @@ -719,8 +731,7 @@ void MDL_map::remove(MDL_lock *lock) */ MDL_context::MDL_context() - :m_trans_sentinel(NULL), - m_thd(NULL), + : m_thd(NULL), m_needs_thr_lock_abort(FALSE), m_waiting_for(NULL) { @@ -742,7 +753,9 @@ MDL_context::MDL_context() void MDL_context::destroy() { - DBUG_ASSERT(m_tickets.is_empty()); + DBUG_ASSERT(m_tickets[MDL_STATEMENT].is_empty() && + m_tickets[MDL_TRANSACTION].is_empty() && + m_tickets[MDL_EXPLICIT].is_empty()); mysql_prlock_destroy(&m_LOCK_waiting_for); } @@ -771,10 +784,12 @@ void MDL_context::destroy() 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) + enum_mdl_type mdl_type_arg, + enum_mdl_duration mdl_duration_arg) { key.mdl_key_init(mdl_namespace, db_arg, name_arg); type= mdl_type_arg; + duration= mdl_duration_arg; ticket= NULL; } @@ -789,49 +804,17 @@ void MDL_request::init(MDL_key::enum_mdl_namespace mdl_namespace, */ void MDL_request::init(const MDL_key *key_arg, - enum enum_mdl_type mdl_type_arg) + enum_mdl_type mdl_type_arg, + enum_mdl_duration mdl_duration_arg) { key.mdl_key_init(key_arg); type= mdl_type_arg; + duration= mdl_duration_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; -} - - -/** Auxiliary functions needed for creation/destruction of MDL_lock objects. @note Also chooses an MDL_lock descendant appropriate for object namespace. @@ -846,6 +829,7 @@ inline MDL_lock *MDL_lock::create(const MDL_key *mdl_key) { case MDL_key::GLOBAL: case MDL_key::SCHEMA: + case MDL_key::COMMIT: return new MDL_scoped_lock(mdl_key); default: return new MDL_object_lock(mdl_key); @@ -867,9 +851,17 @@ void MDL_lock::destroy(MDL_lock *lock) on memory allocation by reusing released objects. */ -MDL_ticket *MDL_ticket::create(MDL_context *ctx_arg, enum_mdl_type type_arg) +MDL_ticket *MDL_ticket::create(MDL_context *ctx_arg, enum_mdl_type type_arg +#ifndef DBUG_OFF + , enum_mdl_duration duration_arg +#endif + ) { - return new MDL_ticket(ctx_arg, type_arg); + return new MDL_ticket(ctx_arg, type_arg +#ifndef DBUG_OFF + , duration_arg +#endif + ); } @@ -1438,13 +1430,11 @@ bool MDL_ticket::is_incompatible_when_waiting(enum_mdl_type type) const /** 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. + Start searching from list of locks for the same duration as lock + being requested. If not look at lists for other durations. @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. + @param[out] result_duration Duration of lock which was found. @note Tickets which correspond to lock types "stronger" than one being requested are also considered compatible. @@ -1454,24 +1444,28 @@ bool MDL_ticket::is_incompatible_when_waiting(enum_mdl_type type) const MDL_ticket * MDL_context::find_ticket(MDL_request *mdl_request, - bool *is_transactional) + enum_mdl_duration *result_duration) { MDL_ticket *ticket; - Ticket_iterator it(m_tickets); - - *is_transactional= TRUE; + int i; - while ((ticket= it++)) + for (i= 0; i < MDL_DURATION_END; i++) { - if (ticket == m_trans_sentinel) - *is_transactional= FALSE; + enum_mdl_duration duration= (enum_mdl_duration)((mdl_request->duration+i) % + MDL_DURATION_END); + Ticket_iterator it(m_tickets[duration]); - if (mdl_request->key.is_equal(&ticket->m_lock->key) && - ticket->has_stronger_or_equal_type(mdl_request->type)) - break; + while ((ticket= it++)) + { + if (mdl_request->key.is_equal(&ticket->m_lock->key) && + ticket->has_stronger_or_equal_type(mdl_request->type)) + { + *result_duration= duration; + return ticket; + } + } } - - return ticket; + return NULL; } @@ -1549,7 +1543,7 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, MDL_lock *lock; MDL_key *key= &mdl_request->key; MDL_ticket *ticket; - bool is_transactional; + enum_mdl_duration found_duration; DBUG_ASSERT(mdl_request->type != MDL_EXCLUSIVE || is_lock_owner(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE)); @@ -1563,7 +1557,7 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, 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))) + if ((ticket= find_ticket(mdl_request, &found_duration))) { DBUG_ASSERT(ticket->m_lock); DBUG_ASSERT(ticket->has_stronger_or_equal_type(mdl_request->type)); @@ -1585,7 +1579,9 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, a different alias. */ mdl_request->ticket= ticket; - if (!is_transactional && clone_ticket(mdl_request)) + if ((found_duration != mdl_request->duration || + mdl_request->duration == MDL_EXPLICIT) && + clone_ticket(mdl_request)) { /* Clone failed. */ mdl_request->ticket= NULL; @@ -1594,7 +1590,11 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, return FALSE; } - if (!(ticket= MDL_ticket::create(this, mdl_request->type))) + if (!(ticket= MDL_ticket::create(this, mdl_request->type +#ifndef DBUG_OFF + , mdl_request->duration +#endif + ))) return TRUE; /* The below call implicitly locks MDL_lock::m_rwlock on success. */ @@ -1612,7 +1612,7 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, mysql_prlock_unlock(&lock->m_rwlock); - m_tickets.push_front(ticket); + m_tickets[mdl_request->duration].push_front(ticket); mdl_request->ticket= ticket; } @@ -1647,7 +1647,11 @@ MDL_context::clone_ticket(MDL_request *mdl_request) we effectively downgrade the cloned lock to the level of the request. */ - if (!(ticket= MDL_ticket::create(this, mdl_request->type))) + if (!(ticket= MDL_ticket::create(this, mdl_request->type +#ifndef DBUG_OFF + , mdl_request->duration +#endif + ))) return TRUE; /* clone() is not supposed to be used to get a stronger lock. */ @@ -1660,36 +1664,74 @@ MDL_context::clone_ticket(MDL_request *mdl_request) ticket->m_lock->m_granted.add_ticket(ticket); mysql_prlock_unlock(&ticket->m_lock->m_rwlock); - m_tickets.push_front(ticket); + m_tickets[mdl_request->duration].push_front(ticket); return FALSE; } /** - Notify a thread holding a shared metadata lock which - conflicts with a pending exclusive lock. + Notify threads holding a shared metadata locks on object which + conflict with a pending X, SNW or SNRW lock. - @param thd Current thread context - @param conflicting_ticket Conflicting metadata lock + @param ctx MDL_context for current thread. */ -void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket) +void MDL_object_lock::notify_conflicting_locks(MDL_context *ctx) { - /* Only try to abort locks on which we back off. */ - if (conflicting_ticket->get_type() < MDL_SHARED_NO_WRITE) + Ticket_iterator it(m_granted); + MDL_ticket *conflicting_ticket; + + while ((conflicting_ticket= it++)) { - MDL_context *conflicting_ctx= conflicting_ticket->get_ctx(); - THD *conflicting_thd= conflicting_ctx->get_thd(); - DBUG_ASSERT(thd != conflicting_thd); /* Self-deadlock */ + /* Only try to abort locks on which we back off. */ + if (conflicting_ticket->get_ctx() != ctx && + conflicting_ticket->get_type() < MDL_SHARED_NO_WRITE) - /* - 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()); + { + MDL_context *conflicting_ctx= conflicting_ticket->get_ctx(); + + /* + 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(ctx->get_thd(), + conflicting_ctx->get_thd(), + conflicting_ctx->get_needs_thr_lock_abort()); + } + } +} + + +/** + Notify threads holding scoped IX locks which conflict with a pending S lock. + + @param ctx MDL_context for current thread. +*/ + +void MDL_scoped_lock::notify_conflicting_locks(MDL_context *ctx) +{ + Ticket_iterator it(m_granted); + MDL_ticket *conflicting_ticket; + + while ((conflicting_ticket= it++)) + { + if (conflicting_ticket->get_ctx() != ctx && + conflicting_ticket->get_type() == MDL_INTENTION_EXCLUSIVE) + + { + MDL_context *conflicting_ctx= conflicting_ticket->get_ctx(); + + /* + Thread which holds global IX lock can be a handler thread for + insert delayed. We need to kill such threads in order to get + global shared lock. We do this my calling code outside of MDL. + */ + mysql_notify_thread_having_shared_lock(ctx->get_thd(), + conflicting_ctx->get_thd(), + conflicting_ctx->get_needs_thr_lock_abort()); + } } } @@ -1747,8 +1789,8 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) */ m_wait.reset_status(); - if (ticket->is_upgradable_or_exclusive()) - lock->notify_shared_locks(this); + if (lock->needs_notification(ticket)) + lock->notify_conflicting_locks(this); mysql_prlock_unlock(&lock->m_rwlock); @@ -1759,7 +1801,7 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) find_deadlock(); - if (ticket->is_upgradable_or_exclusive()) + if (lock->needs_notification(ticket)) { struct timespec abs_shortwait; set_timespec(abs_shortwait, 1); @@ -1775,7 +1817,7 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) break; mysql_prlock_wrlock(&lock->m_rwlock); - lock->notify_shared_locks(this); + lock->notify_conflicting_locks(this); mysql_prlock_unlock(&lock->m_rwlock); set_timespec(abs_shortwait, 1); } @@ -1818,7 +1860,7 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) */ DBUG_ASSERT(wait_status == MDL_wait::GRANTED); - m_tickets.push_front(ticket); + m_tickets[mdl_request->duration].push_front(ticket); mdl_request->ticket= ticket; @@ -1859,7 +1901,7 @@ bool MDL_context::acquire_locks(MDL_request_list *mdl_requests, { MDL_request_list::Iterator it(*mdl_requests); MDL_request **sort_buf, **p_req; - MDL_ticket *mdl_svp= mdl_savepoint(); + MDL_savepoint mdl_svp= mdl_savepoint(); ssize_t req_count= static_cast<ssize_t>(mdl_requests->elements()); if (req_count == 0) @@ -1928,7 +1970,7 @@ 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(); + MDL_savepoint mdl_svp= mdl_savepoint(); bool is_new_ticket; DBUG_ENTER("MDL_ticket::upgrade_shared_lock_to_exclusive"); @@ -1945,7 +1987,8 @@ MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket, 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); + mdl_xlock_request.init(&mdl_ticket->m_lock->key, MDL_EXCLUSIVE, + MDL_TRANSACTION); if (acquire_lock(&mdl_xlock_request, lock_wait_timeout)) DBUG_RETURN(TRUE); @@ -1969,7 +2012,7 @@ MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket, if (is_new_ticket) { - m_tickets.remove(mdl_xlock_request.ticket); + m_tickets[MDL_TRANSACTION].remove(mdl_xlock_request.ticket); MDL_ticket::destroy(mdl_xlock_request.ticket); } @@ -2230,10 +2273,12 @@ void MDL_context::find_deadlock() /** Release lock. - @param ticket Ticket for lock to be released. + @param duration Lock duration. + @param ticket Ticket for lock to be released. + */ -void MDL_context::release_lock(MDL_ticket *ticket) +void MDL_context::release_lock(enum_mdl_duration duration, MDL_ticket *ticket) { MDL_lock *lock= ticket->m_lock; DBUG_ENTER("MDL_context::release_lock"); @@ -2243,12 +2288,9 @@ void MDL_context::release_lock(MDL_ticket *ticket) 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); + m_tickets[duration].remove(ticket); MDL_ticket::destroy(ticket); DBUG_VOID_RETURN; @@ -2256,50 +2298,56 @@ void MDL_context::release_lock(MDL_ticket *ticket) /** + Release lock with explicit duration. + + @param ticket Ticket for lock to be released. + +*/ + +void MDL_context::release_lock(MDL_ticket *ticket) +{ + DBUG_ASSERT(ticket->m_duration == MDL_EXPLICIT); + + release_lock(MDL_EXPLICIT, ticket); +} + + +/** 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: + Statement and transactional locks are added to the beginning of + the corresponding lists, 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 + - release all locks in the end of a statment or 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) +void MDL_context::release_locks_stored_before(enum_mdl_duration duration, + MDL_ticket *sentinel) { MDL_ticket *ticket; - Ticket_iterator it(m_tickets); + Ticket_iterator it(m_tickets[duration]); DBUG_ENTER("MDL_context::release_locks_stored_before"); - if (m_tickets.is_empty()) + if (m_tickets[duration].is_empty()) DBUG_VOID_RETURN; while ((ticket= it++) && ticket != sentinel) { DBUG_PRINT("info", ("found lock to release ticket=%p", ticket)); - release_lock(ticket); + release_lock(duration, 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. + Release all explicit 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. @@ -2312,17 +2360,13 @@ void MDL_context::release_all_locks_for_name(MDL_ticket *name) /* Remove matching lock tickets from the context. */ MDL_ticket *ticket; - Ticket_iterator it_ticket(m_tickets); + Ticket_iterator it_ticket(m_tickets[MDL_EXPLICIT]); 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); + release_lock(MDL_EXPLICIT, ticket); } } @@ -2377,9 +2421,10 @@ MDL_context::is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace, 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); + enum_mdl_duration not_unused; + /* We don't care about exact duration of lock here. */ + mdl_request.init(mdl_namespace, db, name, mdl_type, MDL_TRANSACTION); + MDL_ticket *ticket= find_ticket(&mdl_request, ¬_unused); DBUG_ASSERT(ticket == NULL || ticket->m_lock); @@ -2408,18 +2453,15 @@ bool MDL_ticket::has_pending_conflicting_lock() const @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) +void MDL_context::rollback_to_savepoint(const MDL_savepoint &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); + release_locks_stored_before(MDL_STATEMENT, mdl_savepoint.m_stmt_ticket); + release_locks_stored_before(MDL_TRANSACTION, mdl_savepoint.m_trans_ticket); DBUG_VOID_RETURN; } @@ -2437,65 +2479,150 @@ void MDL_context::rollback_to_savepoint(MDL_ticket *mdl_savepoint) void MDL_context::release_transactional_locks() { DBUG_ENTER("MDL_context::release_transactional_locks"); - release_locks_stored_before(m_trans_sentinel); + release_locks_stored_before(MDL_STATEMENT, NULL); + release_locks_stored_before(MDL_TRANSACTION, NULL); + DBUG_VOID_RETURN; +} + + +void MDL_context::release_statement_locks() +{ + DBUG_ENTER("MDL_context::release_transactional_locks"); + release_locks_stored_before(MDL_STATEMENT, NULL); 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. + + @retval TRUE The ticket is older than the savepoint or + is an LT, HA or GLR ticket. Thus it belongs + to the savepoint or has explicit duration. + @retval FALSE The ticket is newer than the savepoint. + and is not an LT, HA or GLR ticket. */ -bool MDL_context::has_lock(MDL_ticket *mdl_savepoint, +bool MDL_context::has_lock(const MDL_savepoint &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; + MDL_context::Ticket_iterator s_it(m_tickets[MDL_STATEMENT]); + MDL_context::Ticket_iterator t_it(m_tickets[MDL_TRANSACTION]); - while ((ticket= it++) && ticket != m_trans_sentinel) + while ((ticket= s_it++) && ticket != mdl_savepoint.m_stmt_ticket) { - /* - 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; + return FALSE; } - /* Reached m_trans_sentinel. The ticket must be LT, HA or GRL ticket. */ - return FALSE; + + while ((ticket= t_it++) && ticket != mdl_savepoint.m_trans_ticket) + { + if (ticket == mdl_ticket) + return FALSE; + } + return TRUE; +} + + +/** + Change lock duration for transactional lock. + + @param ticket Ticket representing lock. + @param duration Lock duration to be set. + + @note This method only supports changing duration of + transactional lock to some other duration. +*/ + +void MDL_context::set_lock_duration(MDL_ticket *mdl_ticket, + enum_mdl_duration duration) +{ + DBUG_ASSERT(mdl_ticket->m_duration == MDL_TRANSACTION && + duration != MDL_TRANSACTION); + + m_tickets[MDL_TRANSACTION].remove(mdl_ticket); + m_tickets[duration].push_front(mdl_ticket); +#ifndef DBUG_OFF + mdl_ticket->m_duration= duration; +#endif } /** - 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. + Set explicit duration for all locks in the context. */ -void MDL_context::move_ticket_after_trans_sentinel(MDL_ticket *mdl_ticket) +void MDL_context::set_explicit_duration_for_all_locks() { - m_tickets.remove(mdl_ticket); - if (m_trans_sentinel == NULL) + int i; + MDL_ticket *ticket; + + /* + In the most common case when this function is called list + of transactional locks is bigger than list of locks with + explicit duration. So we start by swapping these two lists + and then move elements from new list of transactional + locks and list of statement locks to list of locks with + explicit duration. + */ + + m_tickets[MDL_EXPLICIT].swap(m_tickets[MDL_TRANSACTION]); + + for (i= 0; i < MDL_EXPLICIT; i++) { - m_trans_sentinel= mdl_ticket; - m_tickets.push_back(mdl_ticket); + Ticket_iterator it_ticket(m_tickets[i]); + + while ((ticket= it_ticket++)) + { + m_tickets[i].remove(ticket); + m_tickets[MDL_EXPLICIT].push_front(ticket); + } } - else - m_tickets.insert_after(m_trans_sentinel, mdl_ticket); + +#ifndef DBUG_OFF + Ticket_iterator exp_it(m_tickets[MDL_EXPLICIT]); + + while ((ticket= exp_it++)) + ticket->m_duration= MDL_EXPLICIT; +#endif +} + + +/** + Set transactional duration for all locks in the context. +*/ + +void MDL_context::set_transaction_duration_for_all_locks() +{ + MDL_ticket *ticket; + + /* + In the most common case when this function is called list + of explicit locks is bigger than two other lists (in fact, + list of statement locks is always empty). So we start by + swapping list of explicit and transactional locks and then + move contents of new list of explicit locks to list of + locks with transactional duration. + */ + + DBUG_ASSERT(m_tickets[MDL_STATEMENT].is_empty()); + + m_tickets[MDL_TRANSACTION].swap(m_tickets[MDL_EXPLICIT]); + + Ticket_iterator it_ticket(m_tickets[MDL_EXPLICIT]); + + while ((ticket= it_ticket++)) + { + m_tickets[MDL_EXPLICIT].remove(ticket); + m_tickets[MDL_TRANSACTION].push_front(ticket); + } + +#ifndef DBUG_OFF + Ticket_iterator trans_it(m_tickets[MDL_TRANSACTION]); + + while ((ticket= trans_it++)) + ticket->m_duration= MDL_TRANSACTION; +#endif } diff --git a/sql/mdl.h b/sql/mdl.h index 7938d833eac..4a9397fe215 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -150,6 +150,15 @@ enum enum_mdl_type { MDL_TYPE_END}; +/** Duration of metadata lock. */ + +enum enum_mdl_duration { MDL_STATEMENT= 0, + MDL_TRANSACTION, + MDL_EXPLICIT, + /* This should be the last ! */ + MDL_DURATION_END }; + + /** Maximal length of key for metadata locking subsystem. */ #define MAX_MDLKEY_LENGTH (1 + NAME_LEN + 1 + NAME_LEN + 1) @@ -167,13 +176,16 @@ class MDL_key { public: /** - Object namespaces + Object namespaces. + Sic: when adding a new member to this enum make sure to + update m_namespace_to_wait_state_name array in mdl.cc! 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. + - EVENT is for event scheduler events 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. @@ -184,6 +196,8 @@ public: FUNCTION, PROCEDURE, TRIGGER, + EVENT, + COMMIT, /* This should be the last ! */ NAMESPACE_END }; @@ -238,7 +252,7 @@ public: 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); + return memcmp(m_ptr, rhs->m_ptr, min(m_length, rhs->m_length)); } MDL_key(const MDL_key *rhs) @@ -305,6 +319,8 @@ class MDL_request public: /** Type of metadata lock. */ enum enum_mdl_type type; + /** Duration for requested lock. */ + enum enum_mdl_duration duration; /** Pointers for participating in the list of lock requests for this context. @@ -327,17 +343,16 @@ 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); + enum_mdl_type mdl_type_arg, + enum_mdl_duration mdl_duration_arg); + void init(const MDL_key *key_arg, enum_mdl_type mdl_type_arg, + enum_mdl_duration mdl_duration_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; } - 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 @@ -363,6 +378,7 @@ public: MDL_request(const MDL_request *rhs) :type(rhs->type), + duration(rhs->duration), ticket(NULL), key(&rhs->key) {} @@ -484,17 +500,35 @@ public: private: friend class MDL_context; - MDL_ticket(MDL_context *ctx_arg, enum_mdl_type type_arg) + MDL_ticket(MDL_context *ctx_arg, enum_mdl_type type_arg +#ifndef DBUG_OFF + , enum_mdl_duration duration_arg +#endif + ) : m_type(type_arg), +#ifndef DBUG_OFF + m_duration(duration_arg), +#endif m_ctx(ctx_arg), m_lock(NULL) {} - static MDL_ticket *create(MDL_context *ctx_arg, enum_mdl_type type_arg); + static MDL_ticket *create(MDL_context *ctx_arg, enum_mdl_type type_arg +#ifndef DBUG_OFF + , enum_mdl_duration duration_arg +#endif + ); static void destroy(MDL_ticket *ticket); private: /** Type of metadata lock. Externally accessible. */ enum enum_mdl_type m_type; +#ifndef DBUG_OFF + /** + Duration of lock represented by this ticket. + Context private. Debug-only. + */ + enum_mdl_duration m_duration; +#endif /** Context of the owner of the metadata lock ticket. Externally accessible. */ @@ -512,6 +546,39 @@ private: /** + Savepoint for MDL context. + + Doesn't include metadata locks with explicit duration as + they are not released during rollback to savepoint. +*/ + +class MDL_savepoint +{ +public: + MDL_savepoint() {}; + +private: + MDL_savepoint(MDL_ticket *stmt_ticket, MDL_ticket *trans_ticket) + : m_stmt_ticket(stmt_ticket), m_trans_ticket(trans_ticket) + {} + + friend class MDL_context; + +private: + /** + Pointer to last lock with statement duration which was taken + before creation of savepoint. + */ + MDL_ticket *m_stmt_ticket; + /** + Pointer to last lock with transaction duration which was taken + before creation of savepoint. + */ + MDL_ticket *m_trans_ticket; +}; + + +/** A reliable way to wait on an MDL lock. */ @@ -559,9 +626,7 @@ public: typedef I_P_List<MDL_ticket, I_P_List_adapter<MDL_ticket, &MDL_ticket::next_in_context, - &MDL_ticket::prev_in_context>, - I_P_List_null_counter, - I_P_List_fast_push_back<MDL_ticket> > + &MDL_ticket::prev_in_context> > Ticket_list; typedef Ticket_list::Iterator Ticket_iterator; @@ -584,37 +649,28 @@ public: const char *db, const char *name, enum_mdl_type mdl_type); - bool has_lock(MDL_ticket *mdl_savepoint, MDL_ticket *mdl_ticket); + bool has_lock(const MDL_savepoint &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(); + return !(m_tickets[MDL_STATEMENT].is_empty() && + m_tickets[MDL_TRANSACTION].is_empty() && + m_tickets[MDL_EXPLICIT].is_empty()); } - void set_trans_sentinel() + MDL_savepoint mdl_savepoint() { - m_trans_sentinel= m_tickets.front(); + return MDL_savepoint(m_tickets[MDL_STATEMENT].front(), + m_tickets[MDL_TRANSACTION].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 set_explicit_duration_for_all_locks(); + void set_transaction_duration_for_all_locks(); + void set_lock_duration(MDL_ticket *mdl_ticket, enum_mdl_duration duration); + void release_statement_locks(); void release_transactional_locks(); - void rollback_to_savepoint(MDL_ticket *mdl_savepoint); + void rollback_to_savepoint(const MDL_savepoint &mdl_savepoint); inline THD *get_thd() const { return m_thd; } @@ -655,46 +711,43 @@ public: MDL_wait m_wait; 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, + Lists of all MDL tickets acquired by this connection. + + Lists of MDL tickets: + --------------------- + The entire set of locks acquired by a connection can be separated + in three subsets according to their: locks released at the end of + statement, at the end of transaction and locks are released + explicitly. + + Statement and transactional locks are locks with automatic scope. + They are accumulated in the course of a transaction, and released + either at the end of uppermost statement (for statement locks) or + on COMMIT, ROLLBACK or ROLLBACK TO SAVEPOINT (for transactional + locks). They must not be (and never are) released manually, i.e. with release_lock() call. - Non-transactional locks are taken for locks that span + Locks with explicit duration 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. + locks implementing "global read 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. + Statement/transactional locks are always prepended to the + beginning of the appropriate 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: + Locks with explicit duration stored are not stored in any + particular order, 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 @@ -709,14 +762,9 @@ private: However, one can open a few HANDLERs after entering the read only mode. * LOCK TABLES locks include intention exclusive locks on - involved schemas. - */ - Ticket_list m_tickets; - /** - Separates transactional and non-transactional locks - in m_tickets list, @sa m_tickets. + involved schemas and global intention exclusive lock. */ - MDL_ticket *m_trans_sentinel; + Ticket_list m_tickets[MDL_DURATION_END]; THD *m_thd; /** TRUE - if for this context we will break protocol and try to @@ -747,8 +795,9 @@ private: MDL_wait_for_subgraph *m_waiting_for; private: MDL_ticket *find_ticket(MDL_request *mdl_req, - bool *is_transactional); - void release_locks_stored_before(MDL_ticket *sentinel); + enum_mdl_duration *duration); + void release_locks_stored_before(enum_mdl_duration duration, MDL_ticket *sentinel); + void release_lock(enum_mdl_duration duration, MDL_ticket *ticket); bool try_acquire_lock_impl(MDL_request *mdl_request, MDL_ticket **out_ticket); diff --git a/sql/my_decimal.h b/sql/my_decimal.h index abf4b178422..e5b1573608a 100644 --- a/sql/my_decimal.h +++ b/sql/my_decimal.h @@ -328,7 +328,7 @@ int my_decimal2int(uint mask, const my_decimal *d, my_bool unsigned_flag, inline -int my_decimal2double(uint mask, const my_decimal *d, double *result) +int my_decimal2double(uint, const my_decimal *d, double *result) { /* No need to call check_result as this will always succeed */ return decimal2double((decimal_t*) d, result); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 99754e8b7f6..09c17d600a8 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -324,7 +324,8 @@ static PSI_rwlock_key key_rwlock_openssl; /* the default log output is log tables */ static bool lower_case_table_names_used= 0; static bool volatile select_thread_in_use, signal_thread_in_use; -static bool volatile ready_to_exit; +/* See Bug#56666 and Bug#56760 */; +volatile bool ready_to_exit; static my_bool opt_debugging= 0, opt_external_locking= 0, opt_console= 0; static my_bool opt_short_log_format= 0; static uint kill_cached_threads, wake_thread; @@ -409,7 +410,8 @@ handlerton *heap_hton; handlerton *myisam_hton; handlerton *partition_hton; -my_bool opt_readonly= 0, use_temp_pool, relay_log_purge; +my_bool read_only= 0, opt_readonly= 0; +my_bool use_temp_pool, relay_log_purge; my_bool relay_log_recovery; my_bool opt_sync_frm, opt_allow_suspicious_udfs; my_bool opt_secure_auth= 0; @@ -604,8 +606,7 @@ pthread_key(MEM_ROOT**,THR_MALLOC); pthread_key(THD*, THR_THD); mysql_mutex_t LOCK_thread_count; mysql_mutex_t - LOCK_status, LOCK_global_read_lock, - LOCK_error_log, LOCK_uuid_generator, + LOCK_status, LOCK_error_log, LOCK_uuid_generator, LOCK_delayed_insert, LOCK_delayed_status, LOCK_delayed_create, LOCK_crypt, LOCK_global_system_variables, @@ -625,7 +626,6 @@ mysql_mutex_t LOCK_des_key_file; mysql_rwlock_t LOCK_grant, LOCK_sys_init_connect, LOCK_sys_init_slave; mysql_rwlock_t LOCK_system_variables_hash; mysql_cond_t COND_thread_count; -mysql_cond_t COND_global_read_lock; pthread_t signal_thread; pthread_attr_t connection_attrib; mysql_mutex_t LOCK_server_started; @@ -1542,7 +1542,6 @@ static void clean_up_mutexes() mysql_mutex_destroy(&LOCK_crypt); mysql_mutex_destroy(&LOCK_user_conn); mysql_mutex_destroy(&LOCK_connection_count); - Events::destroy_mutexes(); #ifdef HAVE_OPENSSL mysql_mutex_destroy(&LOCK_des_key_file); #ifndef HAVE_YASSL @@ -1560,12 +1559,10 @@ static void clean_up_mutexes() mysql_rwlock_destroy(&LOCK_sys_init_slave); mysql_mutex_destroy(&LOCK_global_system_variables); mysql_rwlock_destroy(&LOCK_system_variables_hash); - mysql_mutex_destroy(&LOCK_global_read_lock); mysql_mutex_destroy(&LOCK_uuid_generator); mysql_mutex_destroy(&LOCK_prepared_stmt_count); mysql_mutex_destroy(&LOCK_error_messages); mysql_cond_destroy(&COND_thread_count); - mysql_cond_destroy(&COND_global_read_lock); mysql_cond_destroy(&COND_thread_cache); mysql_cond_destroy(&COND_flush_thread_cache); mysql_cond_destroy(&COND_manager); @@ -3524,8 +3521,6 @@ static int init_thread_environment() &LOCK_global_system_variables, MY_MUTEX_INIT_FAST); mysql_rwlock_init(key_rwlock_LOCK_system_variables_hash, &LOCK_system_variables_hash); - mysql_mutex_init(key_LOCK_global_read_lock, - &LOCK_global_read_lock, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_LOCK_prepared_stmt_count, &LOCK_prepared_stmt_count, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_LOCK_error_messages, @@ -3553,7 +3548,6 @@ static int init_thread_environment() mysql_rwlock_init(key_rwlock_LOCK_sys_init_slave, &LOCK_sys_init_slave); mysql_rwlock_init(key_rwlock_LOCK_grant, &LOCK_grant); mysql_cond_init(key_COND_thread_count, &COND_thread_count, NULL); - mysql_cond_init(key_COND_global_read_lock, &COND_global_read_lock, NULL); mysql_cond_init(key_COND_thread_cache, &COND_thread_cache, NULL); mysql_cond_init(key_COND_flush_thread_cache, &COND_flush_thread_cache, NULL); mysql_cond_init(key_COND_manager, &COND_manager, NULL); @@ -3778,12 +3772,12 @@ static int init_server_components() unireg_abort(1); } - /* initialize delegates for extension observers */ + /* + initialize delegates for extension observers, errors have already + been reported in the function + */ if (delegates_init()) - { - sql_print_error("Initialize extension delegates failed"); unireg_abort(1); - } /* need to configure logging before initializing storage engines */ if (opt_log_slave_updates && !opt_bin_log) @@ -4949,7 +4943,7 @@ void create_thread_to_handle_connection(THD *thd) statistic_increment(aborted_connects,&LOCK_status); /* Can't use my_error() since store_globals has not been called. */ my_snprintf(error_message_buff, sizeof(error_message_buff), - ER(ER_CANT_CREATE_THREAD), error); + ER_THD(thd, ER_CANT_CREATE_THREAD), error); net_send_error(thd, ER_CANT_CREATE_THREAD, error_message_buff, NULL); mysql_mutex_lock(&LOCK_thread_count); close_connection(thd,0,0); @@ -5662,6 +5656,12 @@ struct my_option my_long_options[]= {"ansi", 'a', "Use ANSI SQL syntax instead of MySQL syntax. This mode " "will also set transaction isolation level 'serializable'.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + /* + Because Sys_var_bit does not support command-line options, we need to + explicitely add one for --autocommit + */ + {"autocommit", OPT_AUTOCOMMIT, "Set default value for autocommit (0 or 1)", + NULL, NULL, 0, GET_BOOL, OPT_ARG, 0, 0, 0, 0, 0, NULL}, {"bind-address", OPT_BIND_ADDRESS, "IP address to bind to.", &my_bind_addr_str, &my_bind_addr_str, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, @@ -7114,6 +7114,13 @@ mysqld_get_one_option(int optid, if (argument == NULL) /* no argument */ log_error_file_ptr= const_cast<char*>(""); break; + case OPT_AUTOCOMMIT: + const ulonglong turn_bit_on= (argument && (atoi(argument) == 0)) ? + OPTION_NOT_AUTOCOMMIT : OPTION_AUTOCOMMIT; + global_system_variables.option_bits= + (global_system_variables.option_bits & + ~(OPTION_NOT_AUTOCOMMIT | OPTION_AUTOCOMMIT)) | turn_bit_on; + break; } return 0; } @@ -7336,6 +7343,8 @@ static int get_options(int *argc_ptr, char ***argv_ptr) test(global_system_variables.optimizer_switch & OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN); + opt_readonly= read_only; + return 0; } @@ -7656,7 +7665,7 @@ PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_prep_xids, key_delayed_insert_mutex, key_hash_filo_lock, key_LOCK_active_mi, key_LOCK_connection_count, key_LOCK_crypt, key_LOCK_delayed_create, key_LOCK_delayed_insert, key_LOCK_delayed_status, key_LOCK_error_log, - key_LOCK_gdl, key_LOCK_global_read_lock, key_LOCK_global_system_variables, + key_LOCK_gdl, key_LOCK_global_system_variables, key_LOCK_manager, key_LOCK_prepared_stmt_count, key_LOCK_rpl_status, key_LOCK_server_started, key_LOCK_status, @@ -7694,7 +7703,6 @@ static PSI_mutex_info all_server_mutexes[]= { &key_LOCK_delayed_status, "LOCK_delayed_status", PSI_FLAG_GLOBAL}, { &key_LOCK_error_log, "LOCK_error_log", PSI_FLAG_GLOBAL}, { &key_LOCK_gdl, "LOCK_gdl", PSI_FLAG_GLOBAL}, - { &key_LOCK_global_read_lock, "LOCK_global_read_lock", PSI_FLAG_GLOBAL}, { &key_LOCK_global_system_variables, "LOCK_global_system_variables", PSI_FLAG_GLOBAL}, { &key_LOCK_manager, "LOCK_manager", PSI_FLAG_GLOBAL}, { &key_LOCK_prepared_stmt_count, "LOCK_prepared_stmt_count", PSI_FLAG_GLOBAL}, @@ -7743,7 +7751,7 @@ PSI_cond_key key_PAGE_cond, key_COND_active, key_COND_pool; #endif /* HAVE_MMAP */ PSI_cond_key key_BINLOG_COND_prep_xids, key_BINLOG_update_cond, - key_COND_cache_status_changed, key_COND_global_read_lock, key_COND_manager, + key_COND_cache_status_changed, key_COND_manager, key_COND_rpl_status, key_COND_server_started, key_delayed_insert_cond, key_delayed_insert_cond_client, key_item_func_sleep_cond, key_master_info_data_cond, @@ -7766,7 +7774,6 @@ static PSI_cond_info all_server_conds[]= { &key_BINLOG_COND_prep_xids, "MYSQL_BIN_LOG::COND_prep_xids", 0}, { &key_BINLOG_update_cond, "MYSQL_BIN_LOG::update_cond", 0}, { &key_COND_cache_status_changed, "Query_cache::COND_cache_status_changed", 0}, - { &key_COND_global_read_lock, "COND_global_read_lock", PSI_FLAG_GLOBAL}, { &key_COND_manager, "COND_manager", PSI_FLAG_GLOBAL}, { &key_COND_rpl_status, "COND_rpl_status", PSI_FLAG_GLOBAL}, { &key_COND_server_started, "COND_server_started", PSI_FLAG_GLOBAL}, @@ -7825,9 +7832,10 @@ PSI_file_key key_file_binlog, key_file_binlog_index, key_file_casetest, key_file_dbopt, key_file_des_key_file, key_file_ERRMSG, key_select_to_file, key_file_fileparser, key_file_frm, key_file_global_ddl_log, key_file_load, key_file_loadfile, key_file_log_event_data, key_file_log_event_info, - key_file_master_info, key_file_misc, key_file_MYSQL_LOG, key_file_partition, + key_file_master_info, key_file_misc, key_file_partition, key_file_pid, key_file_relay_log_info, key_file_send_file, key_file_tclog, key_file_trg, key_file_trn, key_file_init; +PSI_file_key key_file_query_log, key_file_slow_log; static PSI_file_info all_server_files[]= { @@ -7850,11 +7858,12 @@ static PSI_file_info all_server_files[]= { &key_file_log_event_info, "log_event_info", 0}, { &key_file_master_info, "master_info", 0}, { &key_file_misc, "misc", 0}, - { &key_file_MYSQL_LOG, "MYSQL_LOG", 0}, { &key_file_partition, "partition", 0}, { &key_file_pid, "pid", 0}, + { &key_file_query_log, "query_log", 0}, { &key_file_relay_log_info, "relay_log_info", 0}, { &key_file_send_file, "send_file", 0}, + { &key_file_slow_log, "slow_log", 0}, { &key_file_tclog, "tclog", 0}, { &key_file_trg, "trigger_name", 0}, { &key_file_trn, "trigger", 0}, diff --git a/sql/mysqld.h b/sql/mysqld.h index 6e81c240f7d..7fe4dc7258a 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -100,14 +100,15 @@ extern bool opt_ignore_builtin_innodb; extern my_bool opt_character_set_client_handshake; extern bool volatile abort_loop; extern bool in_bootstrap; -extern uint volatile thread_count, global_read_lock; +extern uint volatile thread_count; extern uint connection_count; extern my_bool opt_safe_user_create; extern my_bool opt_safe_show_db, opt_local_infile, opt_myisam_use_mmap; extern my_bool opt_slave_compressed_protocol, use_temp_pool; extern ulong slave_exec_mode_options; extern ulonglong slave_type_conversions_options; -extern my_bool opt_readonly, lower_case_file_system; +extern my_bool read_only, opt_readonly; +extern my_bool lower_case_file_system; extern my_bool opt_enable_named_pipe, opt_sync_frm, opt_allow_suspicious_udfs; extern my_bool opt_secure_auth; extern char* opt_secure_file_priv; @@ -227,7 +228,7 @@ extern PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_prep_xids, key_delayed_insert_mutex, key_hash_filo_lock, key_LOCK_active_mi, key_LOCK_connection_count, key_LOCK_crypt, key_LOCK_delayed_create, key_LOCK_delayed_insert, key_LOCK_delayed_status, key_LOCK_error_log, - key_LOCK_gdl, key_LOCK_global_read_lock, key_LOCK_global_system_variables, + key_LOCK_gdl, key_LOCK_global_system_variables, key_LOCK_logger, key_LOCK_manager, key_LOCK_prepared_stmt_count, key_LOCK_rpl_status, key_LOCK_server_started, key_LOCK_status, @@ -248,7 +249,7 @@ extern PSI_cond_key key_PAGE_cond, key_COND_active, key_COND_pool; #endif /* HAVE_MMAP */ extern PSI_cond_key key_BINLOG_COND_prep_xids, key_BINLOG_update_cond, - key_COND_cache_status_changed, key_COND_global_read_lock, key_COND_manager, + key_COND_cache_status_changed, key_COND_manager, key_COND_rpl_status, key_COND_server_started, key_delayed_insert_cond, key_delayed_insert_cond_client, key_item_func_sleep_cond, key_master_info_data_cond, @@ -270,9 +271,10 @@ extern PSI_file_key key_file_binlog, key_file_binlog_index, key_file_casetest, key_file_dbopt, key_file_des_key_file, key_file_ERRMSG, key_select_to_file, key_file_fileparser, key_file_frm, key_file_global_ddl_log, key_file_load, key_file_loadfile, key_file_log_event_data, key_file_log_event_info, - key_file_master_info, key_file_misc, key_file_MYSQL_LOG, key_file_partition, + key_file_master_info, key_file_misc, key_file_partition, key_file_pid, key_file_relay_log_info, key_file_send_file, key_file_tclog, key_file_trg, key_file_trn, key_file_init; +extern PSI_file_key key_file_query_log, key_file_slow_log; void init_server_psi_keys(); #endif /* HAVE_PSI_INTERFACE */ @@ -320,7 +322,7 @@ extern mysql_mutex_t LOCK_user_locks, LOCK_status, LOCK_error_log, LOCK_delayed_insert, LOCK_uuid_generator, LOCK_delayed_status, LOCK_delayed_create, LOCK_crypt, LOCK_timezone, - LOCK_slave_list, LOCK_active_mi, LOCK_manager, LOCK_global_read_lock, + LOCK_slave_list, LOCK_active_mi, LOCK_manager, LOCK_global_system_variables, LOCK_user_conn, LOCK_prepared_stmt_count, LOCK_error_messages, LOCK_connection_count; extern MYSQL_PLUGIN_IMPORT mysql_mutex_t LOCK_thread_count; @@ -333,7 +335,6 @@ extern mysql_rwlock_t LOCK_grant, LOCK_sys_init_connect, LOCK_sys_init_slave; extern mysql_rwlock_t LOCK_system_variables_hash; extern mysql_cond_t COND_thread_count; extern mysql_cond_t COND_manager; -extern mysql_cond_t COND_global_read_lock; extern int32 thread_running; extern my_atomic_rwlock_t thread_running_lock; @@ -391,7 +392,8 @@ enum options_mysqld OPT_UPDATE_LOG, OPT_WANT_CORE, OPT_ENGINE_CONDITION_PUSHDOWN, - OPT_LOG_ERROR + OPT_LOG_ERROR, + OPT_AUTOCOMMIT }; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index ffff9e3f6c8..2ac860d25e3 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -5796,6 +5796,7 @@ get_mm_leaf(RANGE_OPT_PARAM *param, COND *conf_func, Field *field, */ if (field->result_type() == STRING_RESULT && + ((Field_str*) field)->match_collation_to_optimize_range() && value->result_type() == STRING_RESULT && key_part->image_type == Field::itRAW && ((Field_str*)field)->charset() != conf_func->compare_collation() && diff --git a/sql/opt_range.h b/sql/opt_range.h index 33b3d561ad8..6da4d1c2776 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -279,6 +279,7 @@ public: virtual bool reverse_sorted() = 0; virtual bool unique_key_range() { return false; } + virtual bool clustered_pk_range() { return false; } enum { QS_TYPE_RANGE = 0, @@ -553,6 +554,8 @@ public: THD *thd; int read_keys_and_merge(); + bool clustered_pk_range() { return test(pk_quick_select); } + /* used to get rows collected in Unique */ READ_RECORD read_record; }; diff --git a/sql/protocol.cc b/sql/protocol.cc index dd3a5d92a87..03b151e4346 100644 --- a/sql/protocol.cc +++ b/sql/protocol.cc @@ -505,11 +505,11 @@ void Protocol::end_statement() thd->stmt_da->get_sqlstate()); break; case Diagnostics_area::DA_EOF: - error= send_eof(thd->stmt_da->server_status(), + error= send_eof(thd->server_status, thd->stmt_da->statement_warn_count()); break; case Diagnostics_area::DA_OK: - error= send_ok(thd->stmt_da->server_status(), + error= send_ok(thd->server_status, thd->stmt_da->statement_warn_count(), thd->stmt_da->affected_rows(), thd->stmt_da->last_insert_id(), diff --git a/sql/records.cc b/sql/records.cc index ccacdc33b36..d713c34dffe 100644 --- a/sql/records.cc +++ b/sql/records.cc @@ -206,6 +206,15 @@ void init_read_record(READ_RECORD *info,THD *thd, TABLE *table, if (select && my_b_inited(&select->file)) tempfile= &select->file; + else if (select && select->quick && select->quick->clustered_pk_range()) + { + /* + In case of QUICK_INDEX_MERGE_SELECT with clustered pk range we have to + use its own access method(i.e QUICK_INDEX_MERGE_SELECT::get_next()) as + sort file does not contain rowids which satisfy clustered pk range. + */ + tempfile= 0; + } else tempfile= table->sort.io_cache; if (tempfile && my_b_inited(tempfile)) // Test if ref-records was used diff --git a/sql/rpl_handler.cc b/sql/rpl_handler.cc index 5f16544d855..4355942e47f 100644 --- a/sql/rpl_handler.cc +++ b/sql/rpl_handler.cc @@ -105,12 +105,20 @@ int delegates_init() transaction_delegate= new (place_trans_mem) Trans_delegate; if (!transaction_delegate->is_inited()) + { + sql_print_error("Initialization of transaction delegates failed. " + "Please report a bug."); return 1; + } binlog_storage_delegate= new (place_storage_mem) Binlog_storage_delegate; if (!binlog_storage_delegate->is_inited()) + { + sql_print_error("Initialization binlog storage delegates failed. " + "Please report a bug."); return 1; + } #ifdef HAVE_REPLICATION void *place_transmit_mem= transmit_mem.data; @@ -119,16 +127,29 @@ int delegates_init() binlog_transmit_delegate= new (place_transmit_mem) Binlog_transmit_delegate; if (!binlog_transmit_delegate->is_inited()) + { + sql_print_error("Initialization of binlog transmit delegates failed. " + "Please report a bug."); return 1; + } binlog_relay_io_delegate= new (place_relay_io_mem) Binlog_relay_IO_delegate; if (!binlog_relay_io_delegate->is_inited()) + { + sql_print_error("Initialization binlog relay IO delegates failed. " + "Please report a bug."); return 1; + } #endif if (pthread_key_create(&RPL_TRANS_BINLOG_INFO, NULL)) + { + sql_print_error("Error while creating pthread specific data key for replication. " + "Please report a bug."); return 1; + } + return 0; } diff --git a/sql/rpl_record.cc b/sql/rpl_record.cc index 8219f70727e..dd16318303d 100644 --- a/sql/rpl_record.cc +++ b/sql/rpl_record.cc @@ -78,8 +78,6 @@ pack_row(TABLE *table, MY_BITMAP const* cols, unsigned int null_mask= 1U; for ( ; (field= *p_field) ; p_field++) { - DBUG_PRINT("debug", ("null_mask=%d; null_ptr=%p; row_data=%p; null_byte_count=%d", - null_mask, null_ptr, row_data, null_byte_count)); if (bitmap_is_set(cols, p_field - table->field)) { my_ptrdiff_t offset; @@ -110,6 +108,7 @@ pack_row(TABLE *table, MY_BITMAP const* cols, field->field_name, field->real_type(), (ulong) old_pack_ptr, (ulong) pack_ptr, (int) (pack_ptr - old_pack_ptr))); + DBUG_DUMP("packed_data", old_pack_ptr, pack_ptr - old_pack_ptr); } null_mask <<= 1; @@ -380,8 +379,11 @@ unpack_row(Relay_log_info const *rli, } DBUG_ASSERT(null_mask & 0xFF); // One of the 8 LSB should be set - if (!((null_bits & null_mask) && tabledef->maybe_null(i))) - pack_ptr+= tabledef->calc_field_size(i, (uchar *) pack_ptr); + if (!((null_bits & null_mask) && tabledef->maybe_null(i))) { + uint32 len= tabledef->calc_field_size(i, (uchar *) pack_ptr); + DBUG_DUMP("field_data", pack_ptr, len); + pack_ptr+= len; + } null_mask <<= 1; } } diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index af9b452acd8..a35e7bb1612 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -1274,6 +1274,9 @@ void Relay_log_info::slave_close_thread_tables(THD *thd) */ if (! thd->in_multi_stmt_transaction_mode()) thd->mdl_context.release_transactional_locks(); + else + thd->mdl_context.release_statement_locks(); + clear_tables_to_lock(); } #endif diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h index bf8381933c0..09b58d4f3a0 100644 --- a/sql/rpl_rli.h +++ b/sql/rpl_rli.h @@ -97,6 +97,16 @@ public: */ MYSQL_BIN_LOG relay_log; LOG_INFO linfo; + + /* + cur_log + Pointer that either points at relay_log.get_log_file() or + &rli->cache_buf, depending on whether the log is hot or there was + the need to open a cold relay_log. + + cache_buf + IO_CACHE used when opening cold relay logs. + */ IO_CACHE cache_buf,*cur_log; /* diff --git a/sql/share/Makefile.am b/sql/share/Makefile.am deleted file mode 100644 index 73f2ca13bb6..00000000000 --- a/sql/share/Makefile.am +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright (C) 2000 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 - -## Process this file with automake to create Makefile.in - -EXTRA_DIST= errmsg-utf8.txt \ - CMakeLists.txt - -dist-hook: - for dir in charsets @AVAILABLE_LANGUAGES@; do \ - test -d $(distdir)/$$dir || mkdir $(distdir)/$$dir; \ - $(INSTALL_DATA) $(srcdir)/$$dir/*.* $(distdir)/$$dir; \ - done; \ - sleep 1 ; touch $(srcdir)/*/errmsg.sys - $(INSTALL_DATA) $(srcdir)/charsets/README $(distdir)/charsets - $(INSTALL_DATA) $(srcdir)/charsets/Index.xml $(distdir)/charsets - -all-local: english/errmsg.sys - -# Use the english errmsg.sys as a flag that all errmsg.sys needs to be -# created. Normally these are created by extra/Makefile - -english/errmsg.sys: errmsg-utf8.txt - rm -f $(top_builddir)/include/mysqld_error.h - (cd $(top_builddir)/extra && $(MAKE)) - -install-data-local: - for lang in @AVAILABLE_LANGUAGES@; \ - do \ - $(mkinstalldirs) $(DESTDIR)$(pkgdatadir)/$$lang; \ - $(INSTALL_DATA) $(srcdir)/$$lang/errmsg.sys \ - $(DESTDIR)$(pkgdatadir)/$$lang/errmsg.sys; \ - done - $(mkinstalldirs) $(DESTDIR)$(pkgdatadir)/charsets - $(INSTALL_DATA) $(srcdir)/errmsg-utf8.txt \ - $(DESTDIR)$(pkgdatadir)/errmsg-utf8.txt; \ - $(INSTALL_DATA) $(srcdir)/charsets/README $(DESTDIR)$(pkgdatadir)/charsets/README - $(INSTALL_DATA) $(srcdir)/charsets/*.xml $(DESTDIR)$(pkgdatadir)/charsets - -# FIXME maybe shouldn't remove, could be needed by other installation? -uninstall-local: - @RM@ -f -r $(DESTDIR)$(pkgdatadir) - -distclean-local: - @RM@ -f */errmsg.sys - -# Do nothing -link_sources: diff --git a/sql/slave.cc b/sql/slave.cc index 3e77a5e7516..c4be7c7a6f7 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -721,9 +721,17 @@ int start_slave_thread( while (start_id == *slave_run_id) { DBUG_PRINT("sleep",("Waiting for slave thread to start")); - const char* old_msg = thd->enter_cond(start_cond,cond_lock, - "Waiting for slave thread to start"); - mysql_cond_wait(start_cond, cond_lock); + const char *old_msg= thd->enter_cond(start_cond, cond_lock, + "Waiting for slave thread to start"); + /* + It is not sufficient to test this at loop bottom. We must test + it after registering the mutex in enter_cond(). If the kill + happens after testing of thd->killed and before the mutex is + registered, we could otherwise go waiting though thd->killed is + set. + */ + if (!thd->killed) + mysql_cond_wait(start_cond, cond_lock); thd->exit_cond(old_msg); mysql_mutex_lock(cond_lock); // re-acquire it as exit_cond() released if (thd->killed) @@ -881,7 +889,17 @@ static bool sql_slave_killed(THD* thd, Relay_log_info* rli) DBUG_ASSERT(rli->slave_running == 1);// tracking buffer overrun if (abort_loop || thd->killed || rli->abort_slave) { - if (thd->transaction.all.modified_non_trans_table && rli->is_in_group()) + /* + The transaction should always be binlogged if OPTION_KEEP_LOG is set + (it implies that something can not be rolled back). And such case + should be regarded similarly as modifing a non-transactional table + because retrying of the transaction will lead to an error or inconsistency + as well. + Example: OPTION_KEEP_LOG is set if a temporary table is created or dropped. + */ + if ((thd->transaction.all.modified_non_trans_table || + (thd->variables.option_bits & OPTION_KEEP_LOG)) + && rli->is_in_group()) { char msg_stopped[]= "... The slave SQL is stopped, leaving the current group " @@ -2503,7 +2521,7 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli) if (slave_trans_retries) { - int temp_err; + int UNINIT_VAR(temp_err); if (exec_res && (temp_err= has_temporary_error(thd))) { const char *errmsg; @@ -3001,7 +3019,7 @@ err: sql_print_information("Slave I/O thread exiting, read up to log '%s', position %s", IO_RPL_LOG_NAME, llstr(mi->master_log_pos,llbuff)); RUN_HOOK(binlog_relay_io, thread_stop, (thd, mi)); - thd->set_query(NULL, 0); + thd->reset_query(); thd->reset_db(NULL, 0); if (mysql) { @@ -3391,7 +3409,7 @@ the slave SQL thread with \"SLAVE START\". We stopped at log \ variables is supposed to set them to 0 before terminating)). */ thd->catalog= 0; - thd->set_query(NULL, 0); + thd->reset_query(); thd->reset_db(NULL, 0); thd_proc_info(thd, "Waiting for slave mutex on exit"); mysql_mutex_lock(&rli->run_lock); @@ -4726,12 +4744,66 @@ static Log_event* next_event(Relay_log_info* rli) DBUG_ASSERT(rli->cur_log_fd == -1); /* - Read pointer has to be at the start since we are the only - reader. - We must keep the LOCK_log to read the 4 first bytes, as this is a hot - log (same as when we call read_log_event() above: for a hot log we - take the mutex). + When the SQL thread is [stopped and] (re)started the + following may happen: + + 1. Log was hot at stop time and remains hot at restart + + SQL thread reads again from hot_log (SQL thread was + reading from the active log when it was stopped and the + very same log is still active on SQL thread restart). + + In this case, my_b_seek is performed on cur_log, while + cur_log points to relay_log.get_log_file(); + + 2. Log was hot at stop time but got cold before restart + + The log was hot when SQL thread stopped, but it is not + anymore when the SQL thread restarts. + + In this case, the SQL thread reopens the log, using + cache_buf, ie, cur_log points to &cache_buf, and thence + its coordinates are reset. + + 3. Log was already cold at stop time + + The log was not hot when the SQL thread stopped, and, of + course, it will not be hot when it restarts. + + In this case, the SQL thread opens the cold log again, + using cache_buf, ie, cur_log points to &cache_buf, and + thence its coordinates are reset. + + 4. Log was hot at stop time, DBA changes to previous cold + log and restarts SQL thread + + The log was hot when the SQL thread was stopped, but the + user changed the coordinates of the SQL thread to + restart from a previous cold log. + + In this case, at start time, cur_log points to a cold + log, opened using &cache_buf as cache, and coordinates + are reset. However, as it moves on to the next logs, it + will eventually reach the hot log. If the hot log is the + same at the time the SQL thread was stopped, then + coordinates were not reset - the cur_log will point to + relay_log.get_log_file(), and not a freshly opened + IO_CACHE through cache_buf. For this reason we need to + deploy a my_b_seek before calling check_binlog_magic at + this point of the code (see: BUG#55263 for more + details). + + NOTES: + - We must keep the LOCK_log to read the 4 first bytes, as + this is a hot log (same as when we call read_log_event() + above: for a hot log we take the mutex). + + - Because of scenario #4 above, we need to have a + my_b_seek here. Otherwise, we might hit the assertion + inside check_binlog_magic. */ + + my_b_seek(cur_log, (my_off_t) 0); if (check_binlog_magic(cur_log,&errmsg)) { if (!hot_log) diff --git a/sql/sp.cc b/sql/sp.cc index 8821dc9365d..ae11c2ad14c 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -27,7 +27,7 @@ #include "sql_acl.h" // SUPER_ACL #include "sp_head.h" #include "sp_cache.h" -#include "lock.h" // lock_routine_name +#include "lock.h" // lock_object_name #include <my_user.h> @@ -440,7 +440,7 @@ static TABLE *open_proc_table_for_update(THD *thd) { TABLE_LIST table_list; TABLE *table; - MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint(); + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("open_proc_table_for_update"); table_list.init_one_table("mysql", 5, "proc", 4, "proc", TL_WRITE); @@ -922,6 +922,8 @@ sp_create_routine(THD *thd, int type, sp_head *sp) TABLE *table; char definer[USER_HOST_BUFF_SIZE]; ulong saved_mode= thd->variables.sql_mode; + MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ? + MDL_key::FUNCTION : MDL_key::PROCEDURE; CHARSET_INFO *db_cs= get_default_db_collation(thd, sp->m_db.str); @@ -941,8 +943,7 @@ sp_create_routine(THD *thd, int type, sp_head *sp) type == TYPE_ENUM_FUNCTION); /* Grab an exclusive MDL lock. */ - if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION, - sp->m_db.str, sp->m_name.str)) + if (lock_object_name(thd, mdl_type, sp->m_db.str, sp->m_name.str)) DBUG_RETURN(SP_OPEN_TABLE_FAILED); /* Reset sql_mode during data dictionary operations. */ @@ -1190,6 +1191,8 @@ sp_drop_routine(THD *thd, int type, sp_name *name) TABLE *table; int ret; bool save_binlog_row_based; + MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ? + MDL_key::FUNCTION : MDL_key::PROCEDURE; DBUG_ENTER("sp_drop_routine"); DBUG_PRINT("enter", ("type: %d name: %.*s", type, (int) name->m_name.length, name->m_name.str)); @@ -1198,8 +1201,7 @@ sp_drop_routine(THD *thd, int type, sp_name *name) 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)) + if (lock_object_name(thd, mdl_type, name->m_db.str, name->m_name.str)) DBUG_RETURN(SP_DELETE_ROW_FAILED); if (!(table= open_proc_table_for_update(thd))) @@ -1270,6 +1272,8 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics) TABLE *table; int ret; bool save_binlog_row_based; + MDL_key::enum_mdl_namespace mdl_type= type == TYPE_ENUM_FUNCTION ? + MDL_key::FUNCTION : MDL_key::PROCEDURE; DBUG_ENTER("sp_update_routine"); DBUG_PRINT("enter", ("type: %d name: %.*s", type, (int) name->m_name.length, name->m_name.str)); @@ -1278,8 +1282,7 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics) 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)) + if (lock_object_name(thd, mdl_type, name->m_db.str, name->m_name.str)) DBUG_RETURN(SP_OPEN_TABLE_FAILED); if (!(table= open_proc_table_for_update(thd))) @@ -1355,6 +1358,108 @@ err: /** + This internal handler is used to trap errors from opening mysql.proc. +*/ + +class Lock_db_routines_error_handler : public Internal_error_handler +{ +public: + bool handle_condition(THD *thd, + uint sql_errno, + const char* sqlstate, + MYSQL_ERROR::enum_warning_level level, + const char* msg, + MYSQL_ERROR ** cond_hdl) + { + if (sql_errno == ER_NO_SUCH_TABLE || + sql_errno == ER_CANNOT_LOAD_FROM_TABLE || + sql_errno == ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE || + sql_errno == ER_COL_COUNT_DOESNT_MATCH_CORRUPTED) + return true; + return false; + } +}; + + +/** + Acquires exclusive metadata lock on all stored routines in the + given database. + + @note Will also return false (=success) if mysql.proc can't be opened + or is outdated. This allows DROP DATABASE to continue in these + cases. + */ + +bool lock_db_routines(THD *thd, char *db) +{ + TABLE *table; + uint key_len; + int nxtres= 0; + Open_tables_backup open_tables_state_backup; + MDL_request_list mdl_requests; + Lock_db_routines_error_handler err_handler; + DBUG_ENTER("lock_db_routines"); + + /* + mysql.proc will be re-opened during deletion, so we can ignore + errors when opening the table here. The error handler is + used to avoid getting the same warning twice. + */ + thd->push_internal_handler(&err_handler); + table= open_proc_table_for_read(thd, &open_tables_state_backup); + thd->pop_internal_handler(); + if (!table) + { + /* + DROP DATABASE should not fail even if mysql.proc does not exist + or is outdated. We therefore only abort mysql_rm_db() if we + have errors not handled by the error handler. + */ + DBUG_RETURN(thd->is_error() || thd->killed); + } + + table->field[MYSQL_PROC_FIELD_DB]->store(db, strlen(db), system_charset_info); + key_len= table->key_info->key_part[0].store_length; + table->file->ha_index_init(0, 1); + + if (! table->file->index_read_map(table->record[0], + table->field[MYSQL_PROC_FIELD_DB]->ptr, + (key_part_map)1, HA_READ_KEY_EXACT)) + { + do + { + char *sp_name= get_field(thd->mem_root, + table->field[MYSQL_PROC_FIELD_NAME]); + longlong sp_type= table->field[MYSQL_PROC_MYSQL_TYPE]->val_int(); + MDL_request *mdl_request= new (thd->mem_root) MDL_request; + mdl_request->init(sp_type == TYPE_ENUM_FUNCTION ? + MDL_key::FUNCTION : MDL_key::PROCEDURE, + db, sp_name, MDL_EXCLUSIVE, MDL_TRANSACTION); + mdl_requests.push_front(mdl_request); + } while (! (nxtres= table->file->index_next_same(table->record[0], + table->field[MYSQL_PROC_FIELD_DB]->ptr, + key_len))); + } + table->file->ha_index_end(); + if (nxtres != 0 && nxtres != HA_ERR_END_OF_FILE) + { + table->file->print_error(nxtres, MYF(0)); + close_system_tables(thd, &open_tables_state_backup); + DBUG_RETURN(true); + } + close_system_tables(thd, &open_tables_state_backup); + + /* We should already hold a global IX lock and a schema X lock. */ + DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::GLOBAL, "", "", + MDL_INTENTION_EXCLUSIVE) && + thd->mdl_context.is_lock_owner(MDL_key::SCHEMA, db, "", + MDL_EXCLUSIVE)); + DBUG_RETURN(thd->mdl_context.acquire_locks(&mdl_requests, + thd->variables.lock_wait_timeout)); +} + + +/** Drop all routines in database 'db' @note Close the thread tables, the calling code might want to @@ -1367,7 +1472,7 @@ sp_drop_db_routines(THD *thd, char *db) TABLE *table; int ret; uint key_len; - MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint(); + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("sp_drop_db_routines"); DBUG_PRINT("enter", ("db: %s", db)); @@ -1636,38 +1741,6 @@ sp_exist_routines(THD *thd, TABLE_LIST *routines, bool any) } -/** - Check if a routine exists in the mysql.proc table, without actually - parsing the definition. (Used for dropping). - - @param thd thread context - @param name name of procedure - - @retval - 0 Success - @retval - non-0 Error; SP_OPEN_TABLE_FAILED or SP_KEY_NOT_FOUND -*/ - -int -sp_routine_exists_in_table(THD *thd, int type, sp_name *name) -{ - TABLE *table; - int ret; - Open_tables_backup open_tables_state_backup; - - if (!(table= open_proc_table_for_read(thd, &open_tables_state_backup))) - ret= SP_OPEN_TABLE_FAILED; - else - { - if ((ret= db_find_routine_aux(thd, type, name, table)) != SP_OK) - ret= SP_KEY_NOT_FOUND; - close_system_tables(thd, &open_tables_state_backup); - } - return ret; -} - - extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen, my_bool first) { @@ -1723,7 +1796,7 @@ bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena, (Sroutine_hash_entry *)arena->alloc(sizeof(Sroutine_hash_entry)); if (!rn) // OOM. Error will be reported using fatal_error(). return FALSE; - rn->mdl_request.init(key, MDL_SHARED); + rn->mdl_request.init(key, MDL_SHARED, MDL_TRANSACTION); if (my_hash_insert(&prelocking_ctx->sroutines, (uchar *)rn)) return FALSE; prelocking_ctx->sroutines_list.link_in_list(rn, &rn->next); @@ -84,6 +84,18 @@ enum int sp_drop_db_routines(THD *thd, char *db); +/** + Acquires exclusive metadata lock on all stored routines in the + given database. + + @param thd Thread handler + @param db Database name + + @retval false Success + @retval true Failure + */ +bool lock_db_routines(THD *thd, char *db); + sp_head * sp_find_routine(THD *thd, int type, sp_name *name, sp_cache **cp, bool cache_only); @@ -100,9 +112,6 @@ sp_cache_routine(THD *thd, int type, sp_name *name, bool sp_exist_routines(THD *thd, TABLE_LIST *procs, bool any); -int -sp_routine_exists_in_table(THD *thd, int type, sp_name *name); - bool sp_show_create_routine(THD *thd, int type, sp_name *name); diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 1fd4e9302c4..54e32b67c65 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -1176,10 +1176,17 @@ find_handler_after_execution(THD *thd, sp_rcontext *ctx) /** Execute the routine. The main instruction jump loop is there. Assume the parameters already set. + + @param thd Thread context. + @param merge_da_on_success Flag specifying if Warning Info should be + propagated to the caller on Completion + Condition or not. + @todo - Will write this SP statement into binlog separately (TODO: consider changing the condition to "not inside event union") + @return Error status. @retval FALSE on success @retval @@ -1187,7 +1194,7 @@ find_handler_after_execution(THD *thd, sp_rcontext *ctx) */ bool -sp_head::execute(THD *thd) +sp_head::execute(THD *thd, bool merge_da_on_success) { DBUG_ENTER("sp_head::execute"); char saved_cur_db_name_buf[NAME_LEN+1]; @@ -1484,8 +1491,15 @@ sp_head::execute(THD *thd) thd->stmt_arena= old_arena; state= EXECUTED; - /* Restore the caller's original warning information area. */ - saved_warning_info->merge_with_routine_info(thd, thd->warning_info); + /* + Restore the caller's original warning information area: + - warnings generated during trigger execution should not be + propagated to the caller on success; + - if there was an exception during execution, warning info should be + propagated to the caller in any case. + */ + if (err_status || merge_da_on_success) + saved_warning_info->merge_with_routine_info(thd, thd->warning_info); thd->warning_info= saved_warning_info; done: @@ -1499,7 +1513,7 @@ sp_head::execute(THD *thd) If the DB has changed, the pointer has changed too, but the original thd->db will then have been freed */ - if (cur_db_changed && !thd->killed) + if (cur_db_changed && thd->killed != THD::KILL_CONNECTION) { /* Force switching back to the saved current database, because it may be @@ -1707,7 +1721,7 @@ sp_head::execute_trigger(THD *thd, thd->spcont= nctx; - err_status= execute(thd); + err_status= execute(thd, FALSE); err_with_cleanup: thd->restore_active_arena(&call_arena, &backup_arena); @@ -1924,7 +1938,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, */ thd->set_n_backup_active_arena(&call_arena, &backup_arena); - err_status= execute(thd); + err_status= execute(thd, TRUE); thd->restore_active_arena(&call_arena, &backup_arena); @@ -2129,6 +2143,8 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) if (! thd->in_sub_stmt && ! thd->in_multi_stmt_transaction_mode()) thd->mdl_context.release_transactional_locks(); + else if (! thd->in_sub_stmt) + thd->mdl_context.release_statement_locks(); thd->rollback_item_tree_changes(); @@ -2157,7 +2173,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) #endif if (!err_status) - err_status= execute(thd); + err_status= execute(thd, TRUE); if (save_log_general) thd->variables.option_bits &= ~OPTION_LOG_OFF; @@ -2964,6 +2980,8 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, if (! thd->in_sub_stmt && ! thd->in_multi_stmt_transaction_mode()) thd->mdl_context.release_transactional_locks(); + else if (! thd->in_sub_stmt) + thd->mdl_context.release_statement_locks(); } if (m_lex->query_tables_own_last) @@ -3051,14 +3069,11 @@ int sp_instr::exec_core(THD *thd, uint *nextp) int sp_instr_stmt::execute(THD *thd, uint *nextp) { - char *query; - uint32 query_length; int res; DBUG_ENTER("sp_instr_stmt::execute"); DBUG_PRINT("info", ("command: %d", m_lex_keeper.sql_command())); - query= thd->query(); - query_length= thd->query_length(); + const CSET_STRING query_backup= thd->query_string; #if defined(ENABLED_PROFILING) /* This s-p instr is profilable and will be captured. */ thd->profiling.set_query_source(m_query.str, m_query.length); @@ -3079,7 +3094,12 @@ sp_instr_stmt::execute(THD *thd, uint *nextp) res= m_lex_keeper.reset_lex_and_exec_core(thd, nextp, FALSE, this); if (thd->stmt_da->is_eof()) + { + /* Finalize server status flags after executing a statement. */ + thd->update_server_status(); + thd->protocol->end_statement(); + } query_cache_end_of_result(thd); @@ -3088,7 +3108,7 @@ sp_instr_stmt::execute(THD *thd, uint *nextp) } else *nextp= m_ip+1; - thd->set_query(query, query_length); + thd->set_query(query_backup); thd->query_name_consts= 0; if (!thd->is_error()) @@ -4159,7 +4179,8 @@ sp_head::add_used_tables_to_table_list(THD *thd, */ 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); + MDL_SHARED_WRITE : MDL_SHARED_READ, + MDL_TRANSACTION); /* Everyting else should be zeroed */ @@ -4203,7 +4224,7 @@ sp_add_to_query_tables(THD *thd, LEX *lex, table->select_lex= lex->current_select; table->cacheable_table= 1; table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name, - mdl_type); + mdl_type, MDL_TRANSACTION); lex->add_to_query_tables(table); return table; diff --git a/sql/sp_head.h b/sql/sp_head.h index e72ce6455f6..5efd48fc7c6 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -527,7 +527,7 @@ private: HASH m_sptabs; bool - execute(THD *thd); + execute(THD *thd, bool merge_da_on_success); /** Perform a forward flow analysis in the generated code. diff --git a/sql/sp_rcontext.h b/sql/sp_rcontext.h index 1af758ed0af..ec8d82063e4 100644 --- a/sql/sp_rcontext.h +++ b/sql/sp_rcontext.h @@ -220,6 +220,7 @@ private: during execution. */ bool m_return_value_set; + /** TRUE if the context is created for a sub-statement. */ diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 5583e9a29f1..5ac31d1f578 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -268,11 +268,13 @@ class ACL_PROXY_USER :public ACL_ACCESS bool with_grant; typedef enum { - MYSQL_PROXY_PRIV_HOST, - MYSQL_PROXY_PRIV_USER, - MYSQL_PROXY_PRIV_PROXIED_HOST, - MYSQL_PROXY_PRIV_PROXIED_USER, - MYSQL_PROXY_PRIV_WITH_GRANT } old_acl_proxy_users; + MYSQL_PROXIES_PRIV_HOST, + MYSQL_PROXIES_PRIV_USER, + MYSQL_PROXIES_PRIV_PROXIED_HOST, + MYSQL_PROXIES_PRIV_PROXIED_USER, + MYSQL_PROXIES_PRIV_WITH_GRANT, + MYSQL_PROXIES_PRIV_GRANTOR, + MYSQL_PROXIES_PRIV_TIMESTAMP } old_acl_proxy_users; public: ACL_PROXY_USER () {}; @@ -308,11 +310,11 @@ public: void init(TABLE *table, MEM_ROOT *mem) { - init (get_field(mem, table->field[MYSQL_PROXY_PRIV_HOST]), - get_field(mem, table->field[MYSQL_PROXY_PRIV_USER]), - get_field(mem, table->field[MYSQL_PROXY_PRIV_PROXIED_HOST]), - get_field(mem, table->field[MYSQL_PROXY_PRIV_PROXIED_USER]), - table->field[MYSQL_PROXY_PRIV_WITH_GRANT]->val_int() != 0); + init (get_field(mem, table->field[MYSQL_PROXIES_PRIV_HOST]), + get_field(mem, table->field[MYSQL_PROXIES_PRIV_USER]), + get_field(mem, table->field[MYSQL_PROXIES_PRIV_PROXIED_HOST]), + get_field(mem, table->field[MYSQL_PROXIES_PRIV_PROXIED_USER]), + table->field[MYSQL_PROXIES_PRIV_WITH_GRANT]->val_int() != 0); } bool get_with_grant() { return with_grant; } @@ -337,7 +339,7 @@ public: (hostname_requires_resolving(host.hostname) || hostname_requires_resolving(proxied_host.hostname))) { - sql_print_warning("'proxy_priv' entry '%s@%s %s@%s' " + sql_print_warning("'proxies_priv' entry '%s@%s %s@%s' " "ignored in --skip-name-resolve mode.", proxied_user ? proxied_user : "", proxied_host.hostname ? proxied_host.hostname : "", @@ -452,19 +454,19 @@ public: user->str ? user->str : "<NULL>", proxied_host->str ? proxied_host->str : "<NULL>", proxied_user->str ? proxied_user->str : "<NULL>")); - if (table->field[MYSQL_PROXY_PRIV_HOST]->store(host->str, + if (table->field[MYSQL_PROXIES_PRIV_HOST]->store(host->str, host->length, system_charset_info)) DBUG_RETURN(TRUE); - if (table->field[MYSQL_PROXY_PRIV_USER]->store(user->str, + if (table->field[MYSQL_PROXIES_PRIV_USER]->store(user->str, user->length, system_charset_info)) DBUG_RETURN(TRUE); - if (table->field[MYSQL_PROXY_PRIV_PROXIED_HOST]->store(proxied_host->str, + if (table->field[MYSQL_PROXIES_PRIV_PROXIED_HOST]->store(proxied_host->str, proxied_host->length, system_charset_info)) DBUG_RETURN(TRUE); - if (table->field[MYSQL_PROXY_PRIV_PROXIED_USER]->store(proxied_user->str, + if (table->field[MYSQL_PROXIES_PRIV_PROXIED_USER]->store(proxied_user->str, proxied_user->length, system_charset_info)) DBUG_RETURN(TRUE); @@ -472,20 +474,25 @@ public: DBUG_RETURN(FALSE); } - static int store_data_record(TABLE *table, - const LEX_STRING *host, + static int store_data_record(TABLE *table, + const LEX_STRING *host, const LEX_STRING *user, - const LEX_STRING *proxied_host, + const LEX_STRING *proxied_host, const LEX_STRING *proxied_user, - bool with_grant) + bool with_grant, + const char *grantor) { - DBUG_ENTER ("ACL_PROXY_USER::store_pk"); - if (store_pk (table, host, user, proxied_host, proxied_user)) + DBUG_ENTER("ACL_PROXY_USER::store_pk"); + if (store_pk(table, host, user, proxied_host, proxied_user)) DBUG_RETURN(TRUE); - DBUG_PRINT ("info", ("with_grant=%s", with_grant ? "TRUE" : "FALSE")); - if (table->field[MYSQL_PROXY_PRIV_WITH_GRANT]->store(with_grant ? 1 : 0, + DBUG_PRINT("info", ("with_grant=%s", with_grant ? "TRUE" : "FALSE")); + if (table->field[MYSQL_PROXIES_PRIV_WITH_GRANT]->store(with_grant ? 1 : 0, TRUE)) DBUG_RETURN(TRUE); + if (table->field[MYSQL_PROXIES_PRIV_GRANTOR]->store(grantor, + strlen(grantor), + system_charset_info)) + DBUG_RETURN(TRUE); DBUG_RETURN(FALSE); } @@ -1022,24 +1029,35 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) end_read_record(&read_record_info); freeze_size(&acl_dbs); - init_read_record(&read_record_info, thd, table= tables[3].table, NULL, 1, - 0, FALSE); - table->use_all_columns(); (void) my_init_dynamic_array(&acl_proxy_users, sizeof(ACL_PROXY_USER), 50, 100); - while (!(read_record_info.read_record(&read_record_info))) + if (tables[3].table) { - ACL_PROXY_USER proxy; - proxy.init(table, &mem); - if (proxy.check_validity(check_no_resolve)) - continue; - if (push_dynamic(&acl_proxy_users, (uchar*) &proxy)) - return TRUE; + init_read_record(&read_record_info, thd, table= tables[3].table, NULL, 1, + 0, FALSE); + table->use_all_columns(); + while (!(read_record_info.read_record(&read_record_info))) + { + ACL_PROXY_USER proxy; + proxy.init(table, &mem); + if (proxy.check_validity(check_no_resolve)) + continue; + if (push_dynamic(&acl_proxy_users, (uchar*) &proxy)) + { + end_read_record(&read_record_info); + goto end; + } + } + my_qsort((uchar*) dynamic_element(&acl_proxy_users, 0, ACL_PROXY_USER*), + acl_proxy_users.elements, + sizeof(ACL_PROXY_USER), (qsort_cmp) acl_compare); + end_read_record(&read_record_info); + } + else + { + sql_print_error("Missing system table mysql.proxies_priv; " + "please run mysql_upgrade to create it"); } - my_qsort((uchar*) dynamic_element(&acl_proxy_users, 0, ACL_PROXY_USER*), - acl_proxy_users.elements, - sizeof(ACL_PROXY_USER), (qsort_cmp) acl_compare); - end_read_record(&read_record_info); freeze_size(&acl_proxy_users); init_check_host(); @@ -1113,13 +1131,14 @@ my_bool acl_reload(THD *thd) tables[2].init_one_table(C_STRING_WITH_LEN("mysql"), C_STRING_WITH_LEN("db"), "db", TL_READ); tables[3].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("proxy_priv"), - "proxy_priv", TL_READ); + C_STRING_WITH_LEN("proxies_priv"), + "proxies_priv", TL_READ); tables[0].next_local= tables[0].next_global= tables + 1; tables[1].next_local= tables[1].next_global= tables + 2; tables[2].next_local= tables[2].next_global= tables + 3; tables[0].open_type= tables[1].open_type= tables[2].open_type= tables[3].open_type= OT_BASE_ONLY; + tables[3].open_strategy= TABLE_LIST::OPEN_IF_EXISTS; if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) { @@ -2608,7 +2627,7 @@ acl_insert_proxy_user(ACL_PROXY_USER *new_value) static int -replace_proxy_priv_table(THD *thd, TABLE *table, const LEX_USER *user, +replace_proxies_priv_table(THD *thd, TABLE *table, const LEX_USER *user, const LEX_USER *proxied_user, bool with_grant_arg, bool revoke_grant) { @@ -2616,8 +2635,9 @@ replace_proxy_priv_table(THD *thd, TABLE *table, const LEX_USER *user, int error; uchar user_key[MAX_KEY_LENGTH]; ACL_PROXY_USER new_grant; + char grantor[USER_HOST_BUFF_SIZE]; - DBUG_ENTER("replace_proxy_priv_table"); + DBUG_ENTER("replace_proxies_priv_table"); if (!initialized) { @@ -2639,6 +2659,8 @@ replace_proxy_priv_table(THD *thd, TABLE *table, const LEX_USER *user, key_copy(user_key, table->record[0], table->key_info, table->key_info->key_length); + get_grantor(thd, grantor); + table->file->ha_index_init(0, 1); if (table->file->index_read_map(table->record[0], user_key, HA_WHOLE_KEY, @@ -2655,7 +2677,8 @@ replace_proxy_priv_table(THD *thd, TABLE *table, const LEX_USER *user, ACL_PROXY_USER::store_data_record(table, &user->host, &user->user, &proxied_user->host, &proxied_user->user, - with_grant_arg); + with_grant_arg, + grantor); } else { @@ -2712,7 +2735,7 @@ table_error: table->file->print_error(error, MYF(0)); /* purecov: inspected */ abort: - DBUG_PRINT("info", ("aborting replace_proxy_priv_table")); + DBUG_PRINT("info", ("aborting replace_proxies_priv_table")); table->file->ha_index_end(); DBUG_RETURN(-1); } @@ -3962,14 +3985,14 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, proxied_user= str_list++; } - /* open the mysql.user and mysql.db or mysql.proxy_priv tables */ + /* open the mysql.user and mysql.db or mysql.proxies_priv tables */ tables[0].init_one_table(C_STRING_WITH_LEN("mysql"), C_STRING_WITH_LEN("user"), "user", TL_WRITE); if (is_proxy) tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("proxy_priv"), - "proxy_priv", + C_STRING_WITH_LEN("proxies_priv"), + "proxies_priv", TL_WRITE); else tables[1].init_one_table(C_STRING_WITH_LEN("mysql"), @@ -4063,7 +4086,7 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list, } else if (is_proxy) { - if (replace_proxy_priv_table (thd, tables[1].table, Str, proxied_user, + if (replace_proxies_priv_table (thd, tables[1].table, Str, proxied_user, rights & GRANT_ACL ? TRUE : FALSE, revoke_grant)) result= -1; @@ -5690,8 +5713,10 @@ int open_grant_tables(THD *thd, TABLE_LIST *tables) C_STRING_WITH_LEN("procs_priv"), "procs_priv", TL_WRITE); (tables+5)->init_one_table(C_STRING_WITH_LEN("mysql"), - C_STRING_WITH_LEN("proxy_priv"), - "proxy_priv", TL_WRITE); + C_STRING_WITH_LEN("proxies_priv"), + "proxies_priv", TL_WRITE); + tables[5].open_strategy= TABLE_LIST::OPEN_IF_EXISTS; + tables->next_local= tables->next_global= tables + 1; (tables+1)->next_local= (tables+1)->next_global= tables + 2; (tables+2)->next_local= (tables+2)->next_global= tables + 3; @@ -6068,7 +6093,7 @@ static int handle_grant_struct(uint struct_no, bool drop, host= acl_proxy_user->get_host(); break; default: - assert(0); + MY_ASSERT_UNREACHABLE(); } if (! user) user= ""; @@ -6283,18 +6308,21 @@ static int handle_grant_data(TABLE_LIST *tables, bool drop, } } - /* Handle proxy_priv table. */ - if ((found= handle_grant_table(tables, 5, drop, user_from, user_to)) < 0) - { - /* Handle of table failed, don't touch the in-memory array. */ - result= -1; - } - else + /* Handle proxies_priv table. */ + if (tables[5].table) { - /* Handle proxy_priv array. */ - if ((handle_grant_struct(5, drop, user_from, user_to) && !result) || - found) - result= 1; /* At least one record/element found. */ + if ((found= handle_grant_table(tables, 5, drop, user_from, user_to)) < 0) + { + /* Handle of table failed, don't touch the in-memory array. */ + result= -1; + } + else + { + /* Handle proxies_priv array. */ + if ((handle_grant_struct(5, drop, user_from, user_to) && !result) || + found) + result= 1; /* At least one record/element found. */ + } } end: DBUG_RETURN(result); @@ -8091,6 +8119,24 @@ static bool send_plugin_request_packet(MPVIO_EXT *mpvio, DBUG_RETURN (1); } + /* + If we're dealing with an older client we can't just send a change plugin + packet to re-initiate the authentication handshake, because the client + won't understand it. The good thing is that we don't need to : the old client + expects us to just check the user credentials here, which we can do by just reading + the cached data that are placed there by parse_com_change_user_packet() + In this case we just do nothing and behave as if normal authentication + should continue. + */ + if (!(mpvio->client_capabilities & CLIENT_PLUGIN_AUTH)) + { + DBUG_PRINT("info", ("old client sent a COM_CHANGE_USER")); + DBUG_ASSERT(mpvio->cached_client_reply.pkt); + /* get the status back so the read can process the cached result */ + mpvio->status= MPVIO_EXT::RESTART; + DBUG_RETURN(0); + } + DBUG_PRINT("info", ("requesting client to use the %s plugin", client_auth_plugin)); DBUG_RETURN(net_write_command(net, switch_plugin_request_buf[0], @@ -8574,8 +8620,16 @@ static int server_mpvio_write_packet(MYSQL_PLUGIN_VIO *param, int res; DBUG_ENTER("server_mpvio_write_packet"); - /* reset cached_client_reply */ - mpvio->cached_client_reply.pkt= 0; + /* + Reset cached_client_reply if not an old client doing mysql_change_user, + as this is where the password from COM_CHANGE_USER is stored. + */ + if (!((!(mpvio->client_capabilities & CLIENT_PLUGIN_AUTH)) && + mpvio->status == MPVIO_EXT::RESTART && + mpvio->cached_client_reply.plugin == + ((st_mysql_auth *) (plugin_decl(mpvio->plugin)->info))->client_auth_plugin + )) + mpvio->cached_client_reply.pkt= 0; /* for the 1st packet we wrap plugin data into the handshake packet */ if (mpvio->packets_written == 0) res= send_server_handshake_packet(mpvio, (char*) packet, packet_len); @@ -8641,6 +8695,15 @@ static int server_mpvio_read_packet(MYSQL_PLUGIN_VIO *param, uchar **buf) mpvio->packets_read++; DBUG_RETURN ((int) mpvio->cached_client_reply.pkt_len); } + + /* older clients don't support change of client plugin request */ + if (!(mpvio->client_capabilities & CLIENT_PLUGIN_AUTH)) + { + mpvio->status= MPVIO_EXT::FAILURE; + pkt_len= packet_error; + goto err; + } + /* But if the client has used the wrong plugin, the cached data are useless. Furthermore, we have to send a "change plugin" request @@ -9188,8 +9251,8 @@ acl_authenticate(THD *thd, uint connect_errors, uint com_change_user_pkt_len) 2. client sends the encrypted password back to the server 3. the server checks the password. */ -static int native_password_authenticate(MYSQL_PLUGIN_VIO *vio, - MYSQL_SERVER_AUTH_INFO *info) +static int native_password_authenticate(MYSQL_PLUGIN_VIO *vio, + MYSQL_SERVER_AUTH_INFO *info) { uchar *pkt; int pkt_len; @@ -9203,7 +9266,7 @@ static int native_password_authenticate(MYSQL_PLUGIN_VIO *vio, /* send it to the client */ if (mpvio->write_packet(mpvio, (uchar*) mpvio->scramble, SCRAMBLE_LENGTH + 1)) - return CR_ERROR; + DBUG_RETURN(CR_ERROR); /* reply and authenticate */ diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc index 21a05a5baca..f648d219fac 100644 --- a/sql/sql_admin.cc +++ b/sql/sql_admin.cc @@ -85,7 +85,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, 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_EXCLUSIVE, MDL_TRANSACTION); if (lock_table_names(thd, table_list, table_list->next_global, thd->variables.lock_wait_timeout, diff --git a/sql/sql_audit.h b/sql/sql_audit.h index 7cb40181bd7..cbc4c7a7232 100644 --- a/sql/sql_audit.h +++ b/sql/sql_audit.h @@ -99,32 +99,29 @@ void mysql_audit_general(THD *thd, uint event_subtype, { time_t time= my_time(0); uint msglen= msg ? strlen(msg) : 0; - const char *query, *user; - uint querylen, userlen; + const char *user; + uint userlen; char user_buff[MAX_USER_HOST_SIZE]; - CHARSET_INFO *clientcs; + CSET_STRING query; ha_rows rows; if (thd) { - query= thd->query(); - querylen= thd->query_length(); + query= thd->query_string; user= user_buff; userlen= make_user_name(thd, user_buff); - clientcs= thd->variables.character_set_client; rows= thd->warning_info->current_row_for_warning(); } else { - query= user= 0; - querylen= userlen= 0; - clientcs= global_system_variables.character_set_client; + user= 0; + userlen= 0; rows= 0; } mysql_audit_notify(thd, MYSQL_AUDIT_GENERAL_CLASS, event_subtype, error_code, time, user, userlen, msg, msglen, - query, querylen, clientcs, rows); + query.str(), query.length(), query.charset(), rows); } #endif } diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 8305283cd17..60c32a1a376 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -21,7 +21,7 @@ #include "sql_priv.h" #include "unireg.h" #include "debug_sync.h" -#include "lock.h" // broadcast_refresh, mysql_lock_remove, +#include "lock.h" // mysql_lock_remove, // mysql_unlock_tables, // mysql_lock_have_duplicate #include "sql_show.h" // append_identifier @@ -1285,20 +1285,12 @@ static void mark_used_tables_as_free_for_reuse(THD *thd, TABLE *table) static void close_open_tables(THD *thd) { - bool found_old_table= 0; - mysql_mutex_assert_not_owner(&LOCK_open); DBUG_PRINT("info", ("thd->open_tables: 0x%lx", (long) thd->open_tables)); while (thd->open_tables) - found_old_table|= close_thread_table(thd, &thd->open_tables); - - if (found_old_table) - { - /* Tell threads waiting for refresh that something has happened */ - broadcast_refresh(); - } + (void) close_thread_table(thd, &thd->open_tables); } @@ -1364,11 +1356,6 @@ close_all_tables_for_name(THD *thd, TABLE_SHARE *share, /* Remove the table share from the cache. */ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db, table_name, FALSE); - /* - There could be a FLUSH thread waiting - on the table to go away. Wake it up. - */ - broadcast_refresh(); } @@ -2463,7 +2450,8 @@ open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx, mdl_request_shared.init(&mdl_request->key, (flags & MYSQL_OPEN_FORCE_SHARED_MDL) ? - MDL_SHARED : MDL_SHARED_HIGH_PRIO); + MDL_SHARED : MDL_SHARED_HIGH_PRIO, + MDL_TRANSACTION); mdl_request= &mdl_request_shared; } @@ -2628,32 +2616,6 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, TMP_TABLE_KEY_EXTRA); /* - We need this to work for all tables, including temporary - tables, for backwards compatibility. But not under LOCK - TABLES, since under LOCK TABLES one can't use a non-prelocked - table. This code only works for updates done inside DO/SELECT - f1() statements, normal DML is handled by means of - sql_command_flags. - */ - if (global_read_lock && table_list->lock_type >= TL_WRITE_ALLOW_WRITE && - ! (flags & MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK) && - ! thd->locked_tables_mode) - { - /* - Someone has issued FLUSH TABLES WITH READ LOCK and we want - a write lock. Wait until the lock is gone. - */ - if (thd->global_read_lock.wait_if_global_read_lock(thd, 1, 1)) - DBUG_RETURN(TRUE); - - if (thd->open_tables && thd->open_tables->s->version != refresh_version) - { - (void)ot_ctx->request_backoff_action(Open_table_context::OT_REOPEN_TABLES, - NULL); - DBUG_RETURN(TRUE); - } - } - /* Unless requested otherwise, try to resolve this table in the list of temporary tables of this thread. In MySQL temporary tables are always thread-local and "shadow" possible base tables with the @@ -2824,6 +2786,59 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK)) { + /* + We are not under LOCK TABLES and going to acquire write-lock/ + modify the base table. We need to acquire protection against + global read lock until end of this statement in order to have + this statement blocked by active FLUSH TABLES WITH READ LOCK. + + We don't block acquire this protection under LOCK TABLES as + such protection already acquired at LOCK TABLES time and + not released until UNLOCK TABLES. + + We don't block statements which modify only temporary tables + as these tables are not preserved by backup by any form of + backup which uses FLUSH TABLES WITH READ LOCK. + + TODO: The fact that we sometimes acquire protection against + GRL only when we encounter table to be write-locked + slightly increases probability of deadlock. + This problem will be solved once Alik pushes his + temporary table refactoring patch and we can start + pre-acquiring metadata locks at the beggining of + open_tables() call. + */ + if (table_list->mdl_request.type >= MDL_SHARED_WRITE && + ! (flags & (MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK | + MYSQL_OPEN_FORCE_SHARED_MDL | + MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL | + MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK)) && + ! ot_ctx->has_protection_against_grl()) + { + MDL_request protection_request; + MDL_deadlock_handler mdl_deadlock_handler(ot_ctx); + + if (thd->global_read_lock.can_acquire_protection()) + DBUG_RETURN(TRUE); + + protection_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE, + MDL_STATEMENT); + + /* + Install error handler which if possible will convert deadlock error + into request to back-off and restart process of opening tables. + */ + thd->push_internal_handler(&mdl_deadlock_handler); + bool result= thd->mdl_context.acquire_lock(&protection_request, + ot_ctx->get_timeout()); + thd->pop_internal_handler(); + + if (result) + DBUG_RETURN(TRUE); + + ot_ctx->set_has_protection_against_grl(); + } + if (open_table_get_mdl_lock(thd, ot_ctx, &table_list->mdl_request, flags, &mdl_ticket) || mdl_ticket == NULL) @@ -2902,8 +2917,12 @@ retry_share: */ if (check_and_update_table_version(thd, table_list, share)) goto err_unlock; - if (table_list->i_s_requested_object & OPEN_TABLE_ONLY) + if (table_list->i_s_requested_object & OPEN_TABLE_ONLY) + { + my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, + table_list->table_name); goto err_unlock; + } /* Open view */ if (open_new_frm(thd, share, alias, @@ -2931,7 +2950,11 @@ retry_share: */ if (table_list->i_s_requested_object & OPEN_VIEW_ONLY) + { + my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, + table_list->table_name); goto err_unlock; + } if (!(flags & MYSQL_OPEN_IGNORE_FLUSH)) { @@ -3371,7 +3394,6 @@ unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count) close_thread_table(thd, &thd->open_tables); } - broadcast_refresh(); } /* Exclude all closed tables from the LOCK TABLES list. */ for (TABLE_LIST *table_list= m_locked_tables; table_list; table_list= @@ -3848,7 +3870,8 @@ Open_table_context::Open_table_context(THD *thd, uint flags) LONG_TIMEOUT : thd->variables.lock_wait_timeout), m_flags(flags), m_action(OT_NO_ACTION), - m_has_locks(thd->mdl_context.has_locks()) + m_has_locks(thd->mdl_context.has_locks()), + m_has_protection_against_grl(FALSE) {} @@ -4005,6 +4028,12 @@ recover_from_failed_open(THD *thd) for safety. */ m_failed_table= NULL; + /* + Reset flag indicating that we have already acquired protection + against GRL. It is no longer valid as the corresponding lock was + released by close_tables_for_reopen(). + */ + m_has_protection_against_grl= FALSE; /* Prepare for possible another back-off. */ m_action= OT_NO_ACTION; return result; @@ -4543,11 +4572,20 @@ lock_table_names(THD *thd, if (schema_request == NULL) return TRUE; schema_request->init(MDL_key::SCHEMA, table->db, "", - MDL_INTENTION_EXCLUSIVE); + MDL_INTENTION_EXCLUSIVE, + MDL_TRANSACTION); mdl_requests.push_front(schema_request); } - /* Take the global intention exclusive lock. */ - global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE); + + /* + Protect this statement against concurrent global read lock + by acquiring global intention exclusive lock with statement + duration. + */ + if (thd->global_read_lock.can_acquire_protection()) + return TRUE; + global_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE, + MDL_STATEMENT); mdl_requests.push_front(&global_request); } @@ -5319,7 +5357,11 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, end: if (table == NULL) + { + if (!thd->in_sub_stmt) + trans_rollback_stmt(thd); close_thread_tables(thd); + } thd_proc_info(thd, 0); DBUG_RETURN(table); } @@ -5350,7 +5392,7 @@ bool open_and_lock_tables(THD *thd, TABLE_LIST *tables, Prelocking_strategy *prelocking_strategy) { uint counter; - MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint(); + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("open_and_lock_tables"); DBUG_PRINT("enter", ("derived handling: %d", derived)); @@ -5407,7 +5449,7 @@ bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags) { DML_prelocking_strategy prelocking_strategy; uint counter; - MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint(); + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); DBUG_ENTER("open_normal_and_derived_tables"); DBUG_ASSERT(!thd->fill_derived_tables()); if (open_tables(thd, &tables, &counter, flags, &prelocking_strategy) || @@ -5664,7 +5706,7 @@ bool lock_tables(THD *thd, TABLE_LIST *tables, uint count, */ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, - MDL_ticket *start_of_statement_svp) + const MDL_savepoint &start_of_statement_svp) { TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table(); TABLE_LIST *tmp; diff --git a/sql/sql_base.h b/sql/sql_base.h index 7ae3971942b..35fa04b3674 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -159,7 +159,7 @@ thr_lock_type read_lock_type_for_table(THD *thd, my_bool mysql_rm_tmp_tables(void); bool rm_temporary_table(handlerton *base, char *path); void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, - MDL_ticket *start_of_statement_svp); + const MDL_savepoint &start_of_statement_svp); TABLE_LIST *find_table_in_list(TABLE_LIST *table, TABLE_LIST *TABLE_LIST::*link, const char *db_name, @@ -243,7 +243,6 @@ bool open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags, bool open_and_lock_tables(THD *thd, TABLE_LIST *tables, bool derived, uint flags, Prelocking_strategy *prelocking_strategy); -int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived); /* 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, uint flags, @@ -508,7 +507,7 @@ public: the statement, so that we can rollback to it before waiting on locks. */ - MDL_ticket *start_of_statement_svp() const + const MDL_savepoint &start_of_statement_svp() const { return m_start_of_statement_svp; } @@ -519,6 +518,21 @@ public: } uint get_flags() const { return m_flags; } + + /** + Set flag indicating that we have already acquired metadata lock + protecting this statement against GRL while opening tables. + */ + void set_has_protection_against_grl() + { + m_has_protection_against_grl= TRUE; + } + + bool has_protection_against_grl() const + { + return m_has_protection_against_grl; + } + private: /** For OT_DISCOVER and OT_REPAIR actions, the table list element for @@ -526,7 +540,7 @@ private: should be repaired. */ TABLE_LIST *m_failed_table; - MDL_ticket *m_start_of_statement_svp; + MDL_savepoint m_start_of_statement_svp; /** Lock timeout in seconds. Initialized to LONG_TIMEOUT when opening system tables or to the "lock_wait_timeout" system variable for regular tables. @@ -542,6 +556,11 @@ private: and we can't safely do back-off (and release them). */ bool m_has_locks; + /** + Indicates that in the process of opening tables we have acquired + protection against global read lock. + */ + bool m_has_protection_against_grl; }; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 15fbc6a1480..2df7a2c8572 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -631,7 +631,7 @@ THD::THD() thr_lock_info_init(&lock_info); /* safety: will be reset after start */ m_internal_handler= NULL; - current_user_used= FALSE; + m_binlog_invoker= FALSE; memset(&invoker_user, 0, sizeof(invoker_user)); memset(&invoker_host, 0, sizeof(invoker_host)); } @@ -1179,36 +1179,70 @@ void add_diff_to_status(STATUS_VAR *to_var, STATUS_VAR *from_var, } +/** + Awake a thread. + + @param[in] state_to_set value for THD::killed + + This is normally called from another thread's THD object. + + @note Do always call this while holding LOCK_thd_data. +*/ + void THD::awake(THD::killed_state state_to_set) { DBUG_ENTER("THD::awake"); - DBUG_PRINT("enter", ("this: 0x%lx", (long) this)); + DBUG_PRINT("enter", ("this: %p current_thd: %p", this, current_thd)); THD_CHECK_SENTRY(this); mysql_mutex_assert_owner(&LOCK_thd_data); + /* Set the 'killed' flag of 'this', which is the target THD object. */ killed= state_to_set; + if (state_to_set != THD::KILL_QUERY) { - thr_alarm_kill(thread_id); - if (!slave_thread) - MYSQL_CALLBACK(thread_scheduler, post_kill_notification, (this)); #ifdef SIGNAL_WITH_VIO_CLOSE if (this != current_thd) { /* - In addition to a signal, let's close the socket of the thread that - is being killed. This is to make sure it does not block if the - signal is lost. This needs to be done only on platforms where - signals are not a reliable interruption mechanism. - - If we're killing ourselves, we know that we're not blocked, so this - hack is not used. + Before sending a signal, let's close the socket of the thread + that is being killed ("this", which is not the current thread). + This is to make sure it does not block if the signal is lost. + This needs to be done only on platforms where signals are not + a reliable interruption mechanism. + + Note that the downside of this mechanism is that we could close + the connection while "this" target thread is in the middle of + sending a result to the application, thus violating the client- + server protocol. + + On the other hand, without closing the socket we have a race + condition. If "this" target thread passes the check of + thd->killed, and then the current thread runs through + THD::awake(), sets the 'killed' flag and completes the + signaling, and then the target thread runs into read(), it will + block on the socket. As a result of the discussions around + Bug#37780, it has been decided that we accept the race + condition. A second KILL awakes the target from read(). + + If we are killing ourselves, we know that we are not blocked. + We also know that we will check thd->killed before we go for + reading the next statement. */ close_active_vio(); } -#endif +#endif + + /* Mark the target thread's alarm request expired, and signal alarm. */ + thr_alarm_kill(thread_id); + + /* Send an event to the scheduler that a thread should be killed. */ + if (!slave_thread) + MYSQL_CALLBACK(thread_scheduler, post_kill_notification, (this)); } + + /* Broadcast a condition to kick the target if it is waiting on it. */ if (mysys_var) { mysql_mutex_lock(&mysys_var->mutex); @@ -1232,6 +1266,11 @@ void THD::awake(THD::killed_state state_to_set) we issue a second KILL or the status it's waiting for happens). It's true that we have set its thd->killed but it may not see it immediately and so may have time to reach the cond_wait(). + + However, where possible, we test for killed once again after + enter_cond(). This should make the signaling as safe as possible. + However, there is still a small chance of failure on platforms with + instruction or memory write reordering. */ if (mysys_var->current_cond && mysys_var->current_mutex) { @@ -1336,7 +1375,7 @@ void THD::cleanup_after_query() where= THD::DEFAULT_WHERE; /* reset table map for multi-table update */ table_map_for_update= 0; - clean_current_user_used(); + m_binlog_invoker= FALSE; } @@ -1842,8 +1881,9 @@ void select_to_file::send_error(uint errcode,const char *err) bool select_to_file::send_eof() { int error= test(end_io_cache(&cache)); - if (mysql_file_close(file, MYF(MY_WME))) - error= 1; + if (mysql_file_close(file, MYF(MY_WME)) || thd->is_error()) + error= true; + if (!error) { ::my_ok(thd,row_count); @@ -2585,8 +2625,6 @@ Statement::Statement(LEX *lex_arg, MEM_ROOT *mem_root_arg, db(NULL), db_length(0) { - query_string.length= 0; - query_string.str= NULL; name.str= NULL; } @@ -2625,15 +2663,6 @@ void Statement::restore_backup_statement(Statement *stmt, Statement *backup) } -/** Assign a new value to thd->query. */ - -void Statement::set_query_inner(char *query_arg, uint32 query_length_arg) -{ - query_string.str= query_arg; - query_string.length= query_length_arg; -} - - void THD::end_statement() { /* Cleanup SQL processing state to reuse this statement in next query. */ @@ -2884,6 +2913,13 @@ bool select_dumpvar::send_eof() if (! row_count) push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_SP_FETCH_NO_DATA, ER(ER_SP_FETCH_NO_DATA)); + /* + Don't send EOF if we're in error condition (which implies we've already + sent or are sending an error) + */ + if (thd->is_error()) + return true; + ::my_ok(thd,row_count); return 0; } @@ -3159,7 +3195,7 @@ extern "C" struct charset_info_st *thd_charset(MYSQL_THD thd) */ extern "C" char **thd_query(MYSQL_THD thd) { - return(&thd->query_string.str); + return (&thd->query_string.string.str); } /** @@ -3170,7 +3206,7 @@ extern "C" char **thd_query(MYSQL_THD thd) */ extern "C" LEX_STRING * thd_query_string (MYSQL_THD thd) { - return(&thd->query_string); + return(&thd->query_string.string); } extern "C" int thd_slave_thread(const MYSQL_THD thd) @@ -3415,20 +3451,21 @@ void THD::set_statement(Statement *stmt) /** Assign a new value to thd->query. */ -void THD::set_query(char *query_arg, uint32 query_length_arg) +void THD::set_query(const CSET_STRING &string_arg) { mysql_mutex_lock(&LOCK_thd_data); - set_query_inner(query_arg, query_length_arg); + set_query_inner(string_arg); mysql_mutex_unlock(&LOCK_thd_data); } /** Assign a new value to thd->query and thd->query_id. */ void THD::set_query_and_id(char *query_arg, uint32 query_length_arg, + CHARSET_INFO *cs, query_id_t new_query_id) { mysql_mutex_lock(&LOCK_thd_data); - set_query_inner(query_arg, query_length_arg); + set_query_inner(query_arg, query_length_arg, cs); query_id= new_query_id; mysql_mutex_unlock(&LOCK_thd_data); } @@ -3458,16 +3495,20 @@ void THD::set_mysys_var(struct st_my_thread_var *new_mysys_var) 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()); + mdl_context.set_transaction_duration_for_all_locks(); + /* + Make sure we don't release the global read lock and commit blocker + when leaving LTM. + */ + global_read_lock.set_explicit_lock_duration(this); /* 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); + mysql_ha_set_explicit_lock_duration(this); } void THD::get_definer(LEX_USER *definer) { - set_current_user_used(); + binlog_invoker(); #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) if (slave_thread && has_invoker()) { diff --git a/sql/sql_class.h b/sql/sql_class.h index aa0f6cf1aa3..02f28b54e10 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -109,6 +109,41 @@ extern MYSQL_PLUGIN_IMPORT const char **errmesg; extern bool volatile shutdown_in_progress; +extern "C" LEX_STRING * thd_query_string (MYSQL_THD thd); +extern "C" char **thd_query(MYSQL_THD thd); + +/** + @class CSET_STRING + @brief Character set armed LEX_STRING +*/ +class CSET_STRING +{ +private: + LEX_STRING string; + CHARSET_INFO *cs; +public: + CSET_STRING() : cs(&my_charset_bin) + { + string.str= NULL; + string.length= 0; + } + CSET_STRING(char *str_arg, size_t length_arg, CHARSET_INFO *cs_arg) : + cs(cs_arg) + { + DBUG_ASSERT(cs_arg != NULL); + string.str= str_arg; + string.length= length_arg; + } + + inline char *str() const { return string.str; } + inline uint32 length() const { return string.length; } + CHARSET_INFO *charset() const { return cs; } + + friend LEX_STRING * thd_query_string (MYSQL_THD thd); + friend char **thd_query(MYSQL_THD thd); +}; + + #define TC_LOG_PAGE_SIZE 8192 #define TC_LOG_MIN_SIZE (3*TC_LOG_PAGE_SIZE) @@ -722,12 +757,24 @@ public: This printing is needed at least in SHOW PROCESSLIST and SHOW ENGINE INNODB STATUS. */ - LEX_STRING query_string; - - inline char *query() { return query_string.str; } - inline uint32 query_length() { return query_string.length; } - void set_query_inner(char *query_arg, uint32 query_length_arg); + CSET_STRING query_string; + inline char *query() const { return query_string.str(); } + inline uint32 query_length() const { return query_string.length(); } + CHARSET_INFO *query_charset() const { return query_string.charset(); } + void set_query_inner(const CSET_STRING &string_arg) + { + query_string= string_arg; + } + void set_query_inner(char *query_arg, uint32 query_length_arg, + CHARSET_INFO *cs_arg) + { + set_query_inner(CSET_STRING(query_arg, query_length_arg, cs_arg)); + } + void reset_query_inner() + { + set_query_inner(CSET_STRING()); + } /** Name of the current (default) database. @@ -822,8 +869,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; + /** State of metadata locks before this savepoint was set. */ + MDL_savepoint mdl_savepoint; }; enum xa_states {XA_NOTR=0, XA_ACTIVE, XA_IDLE, XA_PREPARED, XA_ROLLBACK_ONLY}; @@ -1058,12 +1105,12 @@ 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 + table or tables, we want to save state of metadata + locks which were acquired before the backup. It is used + to release metadata locks on system tables after they are no longer used. */ - MDL_ticket *mdl_system_tables_svp; + MDL_savepoint mdl_system_tables_svp; }; /** @@ -1336,26 +1383,43 @@ public: }; Global_read_lock() - :m_protection_count(0), m_state(GRL_NONE), m_mdl_global_shared_lock(NULL) + : m_state(GRL_NONE), + m_mdl_global_shared_lock(NULL), + m_mdl_blocks_commits_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); + /** + Check if this connection can acquire protection against GRL and + emit error if otherwise. + */ + bool can_acquire_protection() const + { + if (m_state) + { + my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0)); + return TRUE; + } + return FALSE; + } 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; } + void set_explicit_lock_duration(THD *thd); private: - uint m_protection_count; // GRL protection count + enum_grl_state m_state; /** In order to acquire the global read lock, the connection must - acquire a global shared metadata lock, to prohibit all DDL. + acquire shared metadata lock in GLOBAL namespace, to prohibit + all DDL. */ - enum_grl_state m_state; MDL_ticket *m_mdl_global_shared_lock; + /** + Also in order to acquire the global read lock, the connection + must acquire a shared metadata lock in COMMIT namespace, to + prohibit commits. + */ + MDL_ticket *m_mdl_blocks_commits_lock; }; @@ -1953,6 +2017,12 @@ public: DYNAMIC_ARRAY user_var_events; /* For user variables replication */ MEM_ROOT *user_var_events_alloc; /* Allocate above array elements here */ + /* + If checking this in conjunction with a wait condition, please + include a check after enter_cond() if you want to avoid a race + condition. For details see the implementation of awake(), + especially the "broadcast" part. + */ enum killed_state { NOT_KILLED=0, @@ -2211,6 +2281,20 @@ public: } void set_time_after_lock() { utime_after_lock= my_micro_time(); } ulonglong current_utime() { return my_micro_time(); } + /** + Update server status after execution of a top level statement. + + Currently only checks if a query was slow, and assigns + the status accordingly. + Evaluate the current time, and if it exceeds the long-query-time + setting, mark the query as slow. + */ + void update_server_status() + { + ulonglong end_utime_of_query= current_utime(); + if (end_utime_of_query > utime_after_lock + variables.long_query_time) + server_status|= SERVER_QUERY_WAS_SLOW; + } inline ulonglong found_rows(void) { return limit_found_rows; @@ -2676,9 +2760,20 @@ public: Assign a new value to thd->query and thd->query_id and mysys_var. Protected with LOCK_thd_data mutex. */ - void set_query(char *query_arg, uint32 query_length_arg); + void set_query(char *query_arg, uint32 query_length_arg, + CHARSET_INFO *cs_arg) + { + set_query(CSET_STRING(query_arg, query_length_arg, cs_arg)); + } + void set_query(char *query_arg, uint32 query_length_arg) /*Mutex protected*/ + { + set_query(CSET_STRING(query_arg, query_length_arg, charset())); + } + void set_query(const CSET_STRING &str); /* Mutex protected */ + void reset_query() /* Mutex protected */ + { set_query(CSET_STRING()); } void set_query_and_id(char *query_arg, uint32 query_length_arg, - query_id_t new_query_id); + CHARSET_INFO *cs, query_id_t new_query_id); void set_query_id(query_id_t new_query_id); void set_open_tables(TABLE *open_tables_arg) { @@ -2691,14 +2786,13 @@ public: { DBUG_ASSERT(locked_tables_mode == LTM_NONE); - mdl_context.set_trans_sentinel(); + mdl_context.set_explicit_duration_for_all_locks(); locked_tables_mode= mode_arg; } void leave_locked_tables_mode(); int decide_logging_format(TABLE_LIST *tables); - void set_current_user_used() { current_user_used= TRUE; } - bool is_current_user_used() { return current_user_used; } - void clean_current_user_used() { current_user_used= FALSE; } + void binlog_invoker() { m_binlog_invoker= TRUE; } + bool need_binlog_invoker() { return m_binlog_invoker; } void get_definer(LEX_USER *definer); void set_invoker(const LEX_STRING *user, const LEX_STRING *host) { @@ -2739,7 +2833,7 @@ private: Current user will be binlogged into Query_log_event if current_user_used is TRUE; It will be stored into invoker_host and invoker_user by SQL thread. */ - bool current_user_used; + bool m_binlog_invoker; /** It points to the invoker in the Query_log_event. @@ -3498,20 +3592,10 @@ 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) - -/** Identifies statements that may generate row events and that may end up in the binary log. */ -#define CF_CAN_GENERATE_ROW_EVENTS (1U << 11) +#define CF_CAN_GENERATE_ROW_EVENTS (1U << 9) /* Bits in server_command_flags */ diff --git a/sql/sql_cursor.cc b/sql/sql_cursor.cc index 7a9834b4cde..acc591f1ea2 100644 --- a/sql/sql_cursor.cc +++ b/sql/sql_cursor.cc @@ -277,7 +277,6 @@ int Materialized_cursor::open(JOIN *join __attribute__((unused))) rc= result->send_result_set_metadata(item_list, Protocol::SEND_NUM_ROWS); thd->server_status|= SERVER_STATUS_CURSOR_EXISTS; result->send_eof(); - thd->server_status&= ~SERVER_STATUS_CURSOR_EXISTS; } return rc; } @@ -318,12 +317,10 @@ void Materialized_cursor::fetch(ulong num_rows) case 0: thd->server_status|= SERVER_STATUS_CURSOR_EXISTS; result->send_eof(); - thd->server_status&= ~SERVER_STATUS_CURSOR_EXISTS; break; case HA_ERR_END_OF_FILE: thd->server_status|= SERVER_STATUS_LAST_ROW_SENT; result->send_eof(); - thd->server_status&= ~SERVER_STATUS_LAST_ROW_SENT; close(); break; default: diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 517cb9139e9..292fbba2352 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -28,6 +28,8 @@ #include "sql_acl.h" // SELECT_ACL, DB_ACLS, // acl_get, check_grant_db #include "log_event.h" // Query_log_event +#include "sql_base.h" // lock_table_names, tdc_remove_table +#include "sql_handler.h" // mysql_ha_rm_tables #include <mysys_err.h> #include "sp.h" #include "events.h" @@ -44,10 +46,12 @@ const char *del_exts[]= {".frm", ".BAK", ".TMD",".opt", NullS}; static TYPELIB deletable_extentions= {array_elements(del_exts)-1,"del_exts", del_exts, NULL}; -static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, - const char *db, const char *path, uint level, - TABLE_LIST **dropped_tables); - +static bool find_db_tables_and_rm_known_files(THD *thd, MY_DIR *dirp, + const char *db, + const char *path, + TABLE_LIST **tables, + bool *found_other_files); + long mysql_rm_arc_files(THD *thd, MY_DIR *dirp, const char *org_path); static my_bool rm_dir_w_symlink(const char *org_path, my_bool send_error); static void mysql_change_db_impl(THD *thd, @@ -736,36 +740,37 @@ exit: } -/* - Drop all tables in a database and the database itself - - SYNOPSIS - mysql_rm_db() - thd Thread handle - db Database name in the case given by user - It's already validated and set to lower case - (if needed) when we come here - if_exists Don't give error if database doesn't exists - silent Don't generate errors - - RETURN - FALSE ok (Database dropped) - ERROR Error +/** + Drop all tables, routines and events in a database and the database itself. + + @param thd Thread handle + @param db Database name in the case given by user + It's already validated and set to lower case + (if needed) when we come here + @param if_exists Don't give error if database doesn't exists + @param silent Don't write the statement to the binary log and don't + send ok packet to the client + + @retval false OK (Database dropped) + @retval true Error */ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) { - long deleted=0; - int error= 0; + ulong deleted_tables= 0; + bool error= true; char path[FN_REFLEN+16]; MY_DIR *dirp; uint length; - TABLE_LIST* dropped_tables= 0; + bool found_other_files= false; + TABLE_LIST *tables= NULL; + TABLE_LIST *table; + Drop_table_error_handler err_handler; DBUG_ENTER("mysql_rm_db"); if (lock_schema_name(thd, db)) - DBUG_RETURN(TRUE); + DBUG_RETURN(true); length= build_table_filename(path, sizeof(path) - 1, db, "", "", 0); strmov(path+length, MY_DB_OPT_FILE); // Append db option file name @@ -777,20 +782,72 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) { if (!if_exists) { - error= -1; my_error(ER_DB_DROP_EXISTS, MYF(0), db); - goto exit; + DBUG_RETURN(true); } else + { push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_DB_DROP_EXISTS, ER(ER_DB_DROP_EXISTS), db); + error= false; + goto update_binlog; + } } - else + + thd->push_internal_handler(&err_handler); + + if (find_db_tables_and_rm_known_files(thd, dirp, db, path, &tables, + &found_other_files)) { - Drop_table_error_handler err_handler; - thd->push_internal_handler(&err_handler); + thd->pop_internal_handler(); + goto exit; + } - error= -1; + /* + Disable drop of enabled log tables, must be done before name locking. + This check is only needed if we are dropping the "mysql" database. + */ + if ((my_strcasecmp(system_charset_info, MYSQL_SCHEMA_NAME.str, db) == 0)) + { + for (table= tables; table; table= table->next_local) + { + if (check_if_log_table(table->db_length, table->db, + table->table_name_length, table->table_name, true)) + { + my_error(ER_BAD_LOG_STATEMENT, MYF(0), "DROP"); + thd->pop_internal_handler(); + goto exit; + } + } + } + + /* Lock all tables and stored routines about to be dropped. */ + if (lock_table_names(thd, tables, NULL, thd->variables.lock_wait_timeout, + MYSQL_OPEN_SKIP_TEMPORARY) || + lock_db_routines(thd, db)) + { + thd->pop_internal_handler(); + goto exit; + } + + /* mysql_ha_rm_tables() requires a non-null TABLE_LIST. */ + if (tables) + mysql_ha_rm_tables(thd, tables); + + for (table= tables; table; table= table->next_local) + { + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name, + false); + deleted_tables++; + } + + if (thd->killed || + (tables && mysql_rm_table_no_locks(thd, tables, true, false, true, true))) + { + tables= NULL; + } + else + { /* We temporarily disable the binary log while dropping the objects in the database. Since the DROP DATABASE statement is always @@ -808,23 +865,30 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) ha_drop_database(), since NDB otherwise detects the binary log as disabled and will not log the drop database statement on any other connected server. - */ - if ((deleted= mysql_rm_known_files(thd, dirp, db, path, 0, - &dropped_tables)) >= 0) - { - ha_drop_database(path); - tmp_disable_binlog(thd); - query_cache_invalidate1(db); - (void) sp_drop_db_routines(thd, db); /* @todo Do not ignore errors */ + */ + + ha_drop_database(path); + tmp_disable_binlog(thd); + query_cache_invalidate1(db); + (void) sp_drop_db_routines(thd, db); /* @todo Do not ignore errors */ #ifdef HAVE_EVENT_SCHEDULER - Events::drop_schema_events(thd, db); + Events::drop_schema_events(thd, db); #endif - error = 0; - reenable_binlog(thd); - } - thd->pop_internal_handler(); + reenable_binlog(thd); + + /* + If the directory is a symbolic link, remove the link first, then + remove the directory the symbolic link pointed at + */ + if (found_other_files) + my_error(ER_DB_DROP_RMDIR, MYF(0), path, EEXIST); + else + error= rm_dir_w_symlink(path, true); } - if (!silent && deleted>=0) + thd->pop_internal_handler(); + +update_binlog: + if (!silent && !error) { const char *query; ulong query_length; @@ -859,14 +923,13 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) */ if (mysql_bin_log.write(&qinfo)) { - error= -1; + error= true; goto exit; } } thd->clear_error(); thd->server_status|= SERVER_STATUS_DB_DROPPED; - my_ok(thd, (ulong) deleted); - thd->server_status&= ~SERVER_STATUS_DB_DROPPED; + my_ok(thd, deleted_tables); } else if (mysql_bin_log.is_open()) { @@ -880,7 +943,7 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) query_end= query + MAX_DROP_TABLE_Q_LEN; db_len= strlen(db); - for (tbl= dropped_tables; tbl; tbl= tbl->next_local) + for (tbl= tables; tbl; tbl= tbl->next_local) { uint tbl_name_len; @@ -894,7 +957,7 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) */ if (write_to_binlog(thd, query, query_pos -1 - query, db, db_len)) { - error= -1; + error= true; goto exit; } query_pos= query_data_start; @@ -914,7 +977,7 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) */ if (write_to_binlog(thd, query, query_pos -1 - query, db, db_len)) { - error= -1; + error= true; goto exit; } } @@ -927,28 +990,23 @@ exit: SELECT DATABASE() in the future). For this we free() thd->db and set it to 0. */ - if (thd->db && !strcmp(thd->db, db) && error == 0) + if (thd->db && !strcmp(thd->db, db) && !error) mysql_change_db_impl(thd, NULL, 0, thd->variables.collation_server); + my_dirend(dirp); DBUG_RETURN(error); } -/* - Removes files with known extensions plus all found subdirectories that - are 2 hex digits (raid directories). - thd MUST be set when calling this function! -*/ -static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db, - const char *org_path, uint level, - TABLE_LIST **dropped_tables) +static bool find_db_tables_and_rm_known_files(THD *thd, MY_DIR *dirp, + const char *db, + const char *path, + TABLE_LIST **tables, + bool *found_other_files) { - long deleted=0; - ulong found_other_files=0; char filePath[FN_REFLEN]; TABLE_LIST *tot_list=0, **tot_list_next_local, **tot_list_next_global; - List<String> raid_dirs; - DBUG_ENTER("mysql_rm_known_files"); - DBUG_PRINT("enter",("path: %s", org_path)); + DBUG_ENTER("find_db_tables_and_rm_known_files"); + DBUG_PRINT("enter",("path: %s", path)); tot_list_next_local= tot_list_next_global= &tot_list; @@ -965,36 +1023,7 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db, (file->name[1] == '.' && !file->name[2]))) continue; - /* Check if file is a raid directory */ - if ((my_isdigit(system_charset_info, file->name[0]) || - (file->name[0] >= 'a' && file->name[0] <= 'f')) && - (my_isdigit(system_charset_info, file->name[1]) || - (file->name[1] >= 'a' && file->name[1] <= 'f')) && - !file->name[2] && !level) - { - char newpath[FN_REFLEN], *copy_of_path; - MY_DIR *new_dirp; - String *dir; - uint length; - - strxmov(newpath,org_path,"/",file->name,NullS); - length= unpack_filename(newpath,newpath); - if ((new_dirp = my_dir(newpath,MYF(MY_DONT_SORT)))) - { - DBUG_PRINT("my",("New subdir found: %s", newpath)); - if ((mysql_rm_known_files(thd, new_dirp, NullS, newpath,1,0)) < 0) - goto err; - if (!(copy_of_path= (char*) thd->memdup(newpath, length+1)) || - !(dir= new (thd->mem_root) String(copy_of_path, length, - &my_charset_bin)) || - raid_dirs.push_back(dir)) - goto err; - continue; - } - found_other_files++; - continue; - } - else if (file->name[0] == 'a' && file->name[1] == 'r' && + if (file->name[0] == 'a' && file->name[1] == 'r' && file->name[2] == 'c' && file->name[3] == '\0') { /* .frm archive: @@ -1003,16 +1032,16 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db, */ char newpath[FN_REFLEN]; MY_DIR *new_dirp; - strxmov(newpath, org_path, "/", "arc", NullS); + strxmov(newpath, path, "/", "arc", NullS); (void) unpack_filename(newpath, newpath); if ((new_dirp = my_dir(newpath, MYF(MY_DONT_SORT)))) { DBUG_PRINT("my",("Archive subdir found: %s", newpath)); if ((mysql_rm_arc_files(thd, new_dirp, newpath)) < 0) - goto err; + DBUG_RETURN(true); continue; } - found_other_files++; + *found_other_files= true; continue; } if (!(extension= strrchr(file->name, '.'))) @@ -1020,7 +1049,7 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db, if (find_type(extension, &deletable_extentions,1+2) <= 0) { if (find_type(extension, ha_known_exts(),1+2) <= 0) - found_other_files++; + *found_other_files= true; continue; } /* just for safety we use files_charset_info */ @@ -1036,7 +1065,7 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db, strlen(file->name) + 1); if (!table_list) - goto err; + DBUG_RETURN(true); table_list->db= (char*) (table_list+1); table_list->db_length= strmov(table_list->db, db) - table_list->db; table_list->table_name= table_list->db + table_list->db_length + 1; @@ -1054,61 +1083,23 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db, 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); table_list->mdl_request.init(MDL_key::TABLE, table_list->db, - table_list->table_name, MDL_EXCLUSIVE); + table_list->table_name, MDL_EXCLUSIVE, + MDL_TRANSACTION); /* Link into list */ (*tot_list_next_local)= table_list; (*tot_list_next_global)= table_list; tot_list_next_local= &table_list->next_local; tot_list_next_global= &table_list->next_global; - deleted++; } else { - strxmov(filePath, org_path, "/", file->name, NullS); + strxmov(filePath, path, "/", file->name, NullS); if (mysql_file_delete_with_symlink(key_file_misc, filePath, MYF(MY_WME))) - { - goto err; - } + DBUG_RETURN(true); } } - if (thd->killed || - (tot_list && mysql_rm_table_part2(thd, tot_list, 1, 0, 1, 1))) - goto err; - - /* Remove RAID directories */ - { - List_iterator<String> it(raid_dirs); - String *dir; - while ((dir= it++)) - if (rmdir(dir->c_ptr()) < 0) - found_other_files++; - } - my_dirend(dirp); - - if (dropped_tables) - *dropped_tables= tot_list; - - /* - If the directory is a symbolic link, remove the link first, then - remove the directory the symbolic link pointed at - */ - if (found_other_files) - { - my_error(ER_DB_DROP_RMDIR, MYF(0), org_path, EEXIST); - DBUG_RETURN(-1); - } - else - { - /* Don't give errors if we can't delete 'RAID' directory */ - if (rm_dir_w_symlink(org_path, level == 0)) - DBUG_RETURN(-1); - } - - DBUG_RETURN(deleted); - -err: - my_dirend(dirp); - DBUG_RETURN(-1); + *tables= tot_list; + DBUG_RETURN(false); } diff --git a/sql/sql_error.cc b/sql/sql_error.cc index 8c038e10a1f..d0982b879e7 100644 --- a/sql/sql_error.cc +++ b/sql/sql_error.cc @@ -334,7 +334,6 @@ Diagnostics_area::reset_diagnostics_area() /** Don't take chances in production */ m_message[0]= '\0'; m_sql_errno= 0; - m_server_status= 0; m_affected_rows= 0; m_last_insert_id= 0; m_statement_warn_count= 0; @@ -365,7 +364,6 @@ Diagnostics_area::set_ok_status(THD *thd, ulonglong affected_rows_arg, if (is_error() || is_disabled()) return; - m_server_status= thd->server_status; m_statement_warn_count= thd->warning_info->statement_warn_count(); m_affected_rows= affected_rows_arg; m_last_insert_id= last_insert_id_arg; @@ -395,7 +393,6 @@ Diagnostics_area::set_eof_status(THD *thd) if (is_error() || is_disabled()) return; - m_server_status= thd->server_status; /* If inside a stored procedure, do not return the total number of warnings, since they are not available to the client diff --git a/sql/sql_error.h b/sql/sql_error.h index 87e98e27673..14dc5e6d12c 100644 --- a/sql/sql_error.h +++ b/sql/sql_error.h @@ -79,12 +79,6 @@ public: const char* get_sqlstate() const { DBUG_ASSERT(m_status == DA_ERROR); return m_sqlstate; } - uint server_status() const - { - DBUG_ASSERT(m_status == DA_OK || m_status == DA_EOF); - return m_server_status; - } - ulonglong affected_rows() const { DBUG_ASSERT(m_status == DA_OK); return m_affected_rows; } @@ -111,15 +105,6 @@ private: char m_sqlstate[SQLSTATE_LENGTH+1]; /** - Copied from thd->server_status when the diagnostics area is assigned. - We need this member as some places in the code use the following pattern: - thd->server_status|= ... - my_eof(thd); - thd->server_status&= ~... - Assigned by OK, EOF or ERROR. - */ - uint m_server_status; - /** The number of rows affected by the last statement. This is semantically close to thd->row_count_func, but has a different life cycle. thd->row_count_func stores the value returned by diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index a5c126a8521..b5cd3ac9e9a 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -55,7 +55,7 @@ #include "sql_handler.h" #include "unireg.h" // REQUIRED: for other includes #include "sql_base.h" // close_thread_tables -#include "lock.h" // broadcast_refresh, mysql_unlock_tables +#include "lock.h" // mysql_unlock_tables #include "key.h" // key_copy #include "sql_base.h" // insert_fields #include "sql_select.h" @@ -131,11 +131,7 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) /* Non temporary table. */ tables->table->file->ha_index_or_rnd_end(); tables->table->open_by_handler= 0; - if (close_thread_table(thd, &tables->table)) - { - /* Tell threads waiting for refresh that something has happened */ - broadcast_refresh(); - } + (void) close_thread_table(thd, &tables->table); thd->mdl_context.release_lock(tables->mdl_request.ticket); } else if (tables->table) @@ -183,7 +179,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) uint dblen, namelen, aliaslen, counter; bool error; TABLE *backup_open_tables; - MDL_ticket *mdl_savepoint; + MDL_savepoint mdl_savepoint; DBUG_ENTER("mysql_ha_open"); DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d", tables->db, tables->table_name, tables->alias, @@ -252,7 +248,13 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) 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); + /* + We can't request lock with explicit duration for this table + right from the start as open_tables() can't handle properly + back-off for such locks. + */ + hash_tables->mdl_request.init(MDL_key::TABLE, db, name, MDL_SHARED, + MDL_TRANSACTION); /* for now HANDLER can be used only for real TABLES */ hash_tables->required_type= FRMTYPE_TABLE; @@ -332,8 +334,8 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) thd->set_open_tables(backup_open_tables); if (hash_tables->mdl_request.ticket) { - thd->mdl_context. - move_ticket_after_trans_sentinel(hash_tables->mdl_request.ticket); + thd->mdl_context.set_lock_duration(hash_tables->mdl_request.ticket, + MDL_EXPLICIT); thd->mdl_context.set_needs_thr_lock_abort(TRUE); } @@ -969,24 +971,23 @@ void mysql_ha_cleanup(THD *thd) /** - 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. + Set explicit duration for metadata locks corresponding to open HANDLERs + 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) +void mysql_ha_set_explicit_lock_duration(THD *thd) { TABLE_LIST *hash_tables; - DBUG_ENTER("mysql_ha_move_tickets_after_trans_sentinel"); + DBUG_ENTER("mysql_ha_set_explicit_lock_duration"); 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); + thd->mdl_context.set_lock_duration(hash_tables->table->mdl_ticket, + MDL_EXPLICIT); } DBUG_VOID_RETURN; } diff --git a/sql/sql_handler.h b/sql/sql_handler.h index c5da3c4d468..2eea552d7c9 100644 --- a/sql/sql_handler.h +++ b/sql/sql_handler.h @@ -31,6 +31,6 @@ void mysql_ha_flush(THD *thd); void mysql_ha_flush_tables(THD *thd, TABLE_LIST *all_tables); 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); +void mysql_ha_set_explicit_lock_duration(THD *thd); #endif /* SQL_HANDLER_INCLUDED */ diff --git a/sql/sql_help.cc b/sql/sql_help.cc index 4e3df950134..7d106fbe936 100644 --- a/sql/sql_help.cc +++ b/sql/sql_help.cc @@ -699,7 +699,7 @@ bool mysqld_help(THD *thd, const char *mask) if (count_topics == 0) { - int key_id; + int UNINIT_VAR(key_id); if (!(select= prepare_select_for_name(thd,mask,mlen,tables,tables[3].table, used_fields[help_keyword_name].field, diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 1f8da3fab5c..bd2aa70806a 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -77,7 +77,8 @@ #include "sql_audit.h" #ifndef EMBEDDED_LIBRARY -static bool delayed_get_table(THD *thd, TABLE_LIST *table_list); +static bool delayed_get_table(THD *thd, MDL_request *grl_protection_request, + TABLE_LIST *table_list); static int write_delayed(THD *thd, TABLE *table, enum_duplicates duplic, LEX_STRING query, bool ignore, bool log_on); static void end_delayed_insert(THD *thd); @@ -529,32 +530,28 @@ void upgrade_lock_type(THD *thd, thr_lock_type *lock_type, static bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list) { + MDL_request protection_request; DBUG_ENTER("open_and_lock_for_insert_delayed"); #ifndef EMBEDDED_LIBRARY - if (thd->locked_tables_mode && thd->global_read_lock.is_acquired()) - { - /* - If this connection has the global read lock, the handler thread - will not be able to lock the table. It will wait for the global - read lock to go away, but this will never happen since the - connection thread will be stuck waiting for the handler thread - to open and lock the table. - If we are not in locked tables mode, INSERT will seek protection - against the global read lock (and fail), thus we will only get - to this point in locked tables mode. - */ - my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0)); - DBUG_RETURN(TRUE); - } - /* In order for the deadlock detector to be able to find any deadlocks - caused by the handler thread locking this table, we take the metadata - lock inside the connection thread. If this goes ok, the ticket is cloned - and added to the list of granted locks held by the handler thread. + caused by the handler thread waiting for GRL or this table, we acquire + protection against GRL (global IX metadata lock) and metadata lock on + table to being inserted into inside the connection thread. + If this goes ok, the tickets are cloned and added to the list of granted + locks held by the handler thread. */ - MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint(); + if (thd->global_read_lock.can_acquire_protection()) + DBUG_RETURN(TRUE); + + protection_request.init(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE, + MDL_STATEMENT); + + if (thd->mdl_context.acquire_lock(&protection_request, + thd->variables.lock_wait_timeout)) + DBUG_RETURN(TRUE); + if (thd->mdl_context.acquire_lock(&table_list->mdl_request, thd->variables.lock_wait_timeout)) /* @@ -564,7 +561,7 @@ bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list) DBUG_RETURN(TRUE); bool error= FALSE; - if (delayed_get_table(thd, table_list)) + if (delayed_get_table(thd, &protection_request, table_list)) error= TRUE; else if (table_list->table) { @@ -589,13 +586,13 @@ bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list) } /* - If a lock was acquired above, we should release it after - handle_delayed_insert() has cloned the ticket. Note that acquire_lock() can - succeed because the connection already has the lock. In this case the ticket - will be before the mdl_savepoint and we should not release it here. + We can't release protection against GRL and metadata lock on the table + being inserted into here. These locks might be required, for example, + because this INSERT DELAYED calls functions which may try to update + this or another tables (updating the same table is of course illegal, + but such an attempt can be discovered only later during statement + execution). */ - if (!thd->mdl_context.has_lock(mdl_savepoint, table_list->mdl_request.ticket)) - thd->mdl_context.release_lock(table_list->mdl_request.ticket); /* Reset the ticket in case we end up having to use normal insert and @@ -1873,10 +1870,11 @@ public: mysql_cond_t cond, cond_client; volatile uint tables_in_use,stacked_inserts; volatile bool status; - /* + /** When the handler thread starts, it clones a metadata lock ticket - for the table to be inserted. This is done to allow the deadlock - detector to detect deadlocks resulting from this lock. + which protects against GRL and ticket for the table to be inserted. + This is done to allow the deadlock detector to detect deadlocks + resulting from these locks. Before this is done, the connection thread cannot safely exit without causing problems for clone_ticket(). Once handler_thread_initialized has been set, it is safe for the @@ -1888,6 +1886,11 @@ public: I_List<delayed_row> rows; ulong group_count; TABLE_LIST table_list; // Argument + /** + Request for IX metadata lock protecting against GRL which is + passed from connection thread to the handler thread. + */ + MDL_request grl_protection; Delayed_insert() :locks_in_memory(0), table(0),tables_in_use(0),stacked_inserts(0), @@ -2066,7 +2069,8 @@ Delayed_insert *find_handler(THD *thd, TABLE_LIST *table_list) */ static -bool delayed_get_table(THD *thd, TABLE_LIST *table_list) +bool delayed_get_table(THD *thd, MDL_request *grl_protection_request, + TABLE_LIST *table_list) { int error; Delayed_insert *di; @@ -2099,7 +2103,8 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list) mysql_mutex_unlock(&LOCK_thread_count); di->thd.set_db(table_list->db, (uint) strlen(table_list->db)); di->thd.set_query(my_strdup(table_list->table_name, - MYF(MY_WME | ME_FATALERROR)), 0); + MYF(MY_WME | ME_FATALERROR)), + 0, system_charset_info); if (di->thd.db == NULL || di->thd.query() == NULL) { /* The error is reported */ @@ -2110,7 +2115,10 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list) /* Replace volatile strings with local copies */ di->table_list.alias= di->table_list.table_name= di->thd.query(); di->table_list.db= di->thd.db; - /* We need the ticket so that it can be cloned in handle_delayed_insert */ + /* We need the tickets so that they can be cloned in handle_delayed_insert */ + di->grl_protection.init(MDL_key::GLOBAL, "", "", + MDL_INTENTION_EXCLUSIVE, MDL_STATEMENT); + di->grl_protection.ticket= grl_protection_request->ticket; init_mdl_requests(&di->table_list); di->table_list.mdl_request.ticket= table_list->mdl_request.ticket; @@ -2650,13 +2658,15 @@ pthread_handler_t handle_delayed_insert(void *arg) thd->set_current_stmt_binlog_format_row_if_mixed(); /* - Clone the ticket representing the lock on the target table for - the insert and add it to the list of granted metadata locks held by - the handler thread. This is safe since the handler thread is - not holding nor waiting on any metadata locks. + Clone tickets representing protection against GRL and the lock on + the target table for the insert and add them to the list of granted + metadata locks held by the handler thread. This is safe since the + handler thread is not holding nor waiting on any metadata locks. */ - if (thd->mdl_context.clone_ticket(&di->table_list.mdl_request)) + if (thd->mdl_context.clone_ticket(&di->grl_protection) || + thd->mdl_context.clone_ticket(&di->table_list.mdl_request)) { + thd->mdl_context.release_transactional_locks(); di->handler_thread_initialized= TRUE; goto err; } @@ -3506,6 +3516,9 @@ bool select_insert::send_eof() error= (thd->locked_tables_mode <= LTM_LOCK_TABLES ? table->file->ha_end_bulk_insert() : 0); + if (!error && thd->is_error()) + error= thd->stmt_da->sql_errno(); + table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE); @@ -4049,7 +4062,7 @@ bool select_create::send_eof() { bool tmp=select_insert::send_eof(); if (tmp) - abort(); + abort_result_set(); else { /* @@ -4081,7 +4094,7 @@ void select_create::abort_result_set() DBUG_ENTER("select_create::abort_result_set"); /* - In select_insert::abort() we roll back the statement, including + In select_insert::abort_result_set() we roll back the statement, including truncating the transaction cache of the binary log. To do this, we pretend that the statement is transactional, even though it might be the case that it was not. diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index d91489b4a7a..7a8a86d3ca4 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -417,7 +417,6 @@ void lex_start(THD *thd) lex->nest_level=0 ; lex->allow_sum_func= 0; lex->in_sum_func= NULL; - lex->protect_against_global_read_lock= FALSE; /* ok, there must be a better solution for this, long-term I tried "bzero" in the sql_yacc.yy code, but that for diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 0f93275434d..191562bd3e8 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -1389,6 +1389,7 @@ public: STMT_ACCESS_TABLE_COUNT }; +#ifndef DBUG_OFF static inline const char *stmt_accessed_table_string(enum_stmt_accessed_table accessed_table) { switch (accessed_table) @@ -1422,7 +1423,10 @@ public: DBUG_ASSERT(0); break; } + MY_ASSERT_UNREACHABLE(); + return ""; } +#endif /* DBUG */ #define BINLOG_DIRECT_ON 0xF0 /* unsafe when --binlog-direct-non-trans-updates @@ -2390,22 +2394,6 @@ struct LEX: public Query_tables_list bool escape_used; bool is_lex_started; /* If lex_start() did run. For debugging. */ - /* - Special case for SELECT .. FOR UPDATE and LOCK TABLES .. WRITE. - - Protect from a impending GRL as otherwise the thread might deadlock - if it starts waiting for the GRL in mysql_lock_tables. - - The protection is needed because there is a race between setting - the global read lock and waiting for all open tables to be closed. - The problem is a circular wait where a thread holding "old" open - tables will wait for the global read lock to be released while the - thread holding the global read lock will wait for all "old" open - tables to be closed -- the flush part of flush tables with read - lock. - */ - bool protect_against_global_read_lock; - LEX(); virtual ~LEX() diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2f8a72ee25c..cb641b5ff68 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -18,12 +18,9 @@ #include "sql_priv.h" #include "unireg.h" // REQUIRED: for other includes #include "sql_parse.h" // sql_kill, *_precheck, *_prepare -#include "lock.h" // wait_if_global_read_lock, - // unlock_global_read_lock, - // try_transactional_lock, +#include "lock.h" // try_transactional_lock, // check_transactional_lock, // set_handler_table_locks, - // start_waiting_global_read_lock, // lock_global_read_lock, // make_global_read_lock_block_commit #include "sql_base.h" // find_temporary_tablesx @@ -260,21 +257,20 @@ void init_update_queries(void) the code, in particular in the Query_log_event's constructor. */ sql_command_flags[SQLCOM_CREATE_TABLE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL | + CF_AUTO_COMMIT_TRANS | CF_CAN_GENERATE_ROW_EVENTS; 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; + CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_TRUNCATE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND | - CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL; + 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 | CF_CAN_GENERATE_ROW_EVENTS; - sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL; - sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL; - sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]= CF_AUTO_COMMIT_TRANS | CF_PROTECT_AGAINST_GRL; - sql_command_flags[SQLCOM_ALTER_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | 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 | @@ -285,26 +281,18 @@ void init_update_queries(void) 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_REEXECUTION_FRAGILE | - CF_PROTECT_AGAINST_GRL | CF_CAN_GENERATE_ROW_EVENTS; sql_command_flags[SQLCOM_UPDATE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_PROTECT_AGAINST_GRL | CF_CAN_GENERATE_ROW_EVENTS; sql_command_flags[SQLCOM_INSERT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_PROTECT_AGAINST_GRL | CF_CAN_GENERATE_ROW_EVENTS; sql_command_flags[SQLCOM_INSERT_SELECT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_PROTECT_AGAINST_GRL | CF_CAN_GENERATE_ROW_EVENTS; sql_command_flags[SQLCOM_DELETE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_PROTECT_AGAINST_GRL | CF_CAN_GENERATE_ROW_EVENTS; sql_command_flags[SQLCOM_DELETE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_PROTECT_AGAINST_GRL | CF_CAN_GENERATE_ROW_EVENTS; sql_command_flags[SQLCOM_REPLACE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | CF_CAN_GENERATE_ROW_EVENTS; @@ -366,20 +354,19 @@ void init_update_queries(void) 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_CREATE_USER]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_RENAME_USER]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_DROP_USER]= CF_CHANGES_DATA; 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_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_INSTALL_PLUGIN]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]= CF_CHANGES_DATA; @@ -550,7 +537,7 @@ static void handle_bootstrap_impl(THD *thd) query= (char *) thd->memdup_w_gap(buff, length + 1, thd->db_length + 1 + QUERY_CACHE_FLAGS_SIZE); - thd->set_query_and_id(query, length, next_query_id()); + thd->set_query_and_id(query, length, thd->charset(), next_query_id()); DBUG_PRINT("query",("%-.4096s",thd->query())); #if defined(ENABLED_PROFILING) thd->profiling.start_new_query(); @@ -712,6 +699,22 @@ bool do_command(THD *thd) net_new_transaction(net); + /* + Synchronization point for testing of KILL_CONNECTION. + This sync point can wait here, to simulate slow code execution + between the last test of thd->killed and blocking in read(). + + The goal of this test is to verify that a connection does not + hang, if it is killed at this point of execution. + (Bug#37780 - main.kill fails randomly) + + Note that the sync point wait itself will be terminated by a + kill. In this case it consumes a condition broadcast, but does + not change anything else. The consumed broadcast should not + matter here, because the read/recv() below doesn't use it. + */ + DEBUG_SYNC(thd, "before_do_command_net_read"); + if ((packet_length= my_net_read(net)) == packet_error) { DBUG_PRINT("info",("Got error %d reading command from socket %s", @@ -1031,11 +1034,13 @@ bool dispatch_command(enum enum_server_command command, THD *thd, while (!thd->killed && (parser_state.m_lip.found_semicolon != NULL) && ! thd->is_error()) { - char *beginning_of_next_stmt= (char*) - parser_state.m_lip.found_semicolon; /* Multiple queries exits, execute them individually */ + char *beginning_of_next_stmt= (char*) parser_state.m_lip.found_semicolon; + + /* Finalize server status flags after executing a statement. */ + thd->update_server_status(); thd->protocol->end_statement(); query_cache_end_of_result(thd); ulong length= (ulong)(packet_end - beginning_of_next_stmt); @@ -1065,7 +1070,8 @@ bool dispatch_command(enum enum_server_command command, THD *thd, thd->security_ctx->priv_user, (char *) thd->security_ctx->host_or_ip); - thd->set_query_and_id(beginning_of_next_stmt, length, next_query_id()); + thd->set_query_and_id(beginning_of_next_stmt, length, + thd->charset(), next_query_id()); /* Count each statement from the client. */ @@ -1095,7 +1101,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, SHOW statements should not add the used tables to the list of tables used in a transaction. */ - MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint(); + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); status_var_increment(thd->status_var.com_stat[SQLCOM_SHOW_FIELDS]); if (thd->copy_db_to(&db.str, &db.length)) @@ -1381,6 +1387,8 @@ bool dispatch_command(enum enum_server_command command, THD *thd, (thd->open_tables == NULL || (thd->locked_tables_mode == LTM_LOCK_TABLES))); + /* Finalize server status flags after executing a command. */ + thd->update_server_status(); thd->protocol->end_statement(); query_cache_end_of_result(thd); @@ -1390,7 +1398,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, log_slow_statement(thd); thd_proc_info(thd, "cleaning up"); - thd->set_query(NULL, 0); + thd->reset_query(); thd->command=COM_SLEEP; dec_thread_running(); thd_proc_info(thd, 0); @@ -1435,8 +1443,7 @@ void log_slow_statement(THD *thd) ulonglong end_utime_of_query= thd->current_utime(); thd_proc_info(thd, "logging slow query"); - if (((end_utime_of_query - thd->utime_after_lock) > - thd->variables.long_query_time || + if (((thd->server_status & SERVER_QUERY_WAS_SLOW) || ((thd->server_status & (SERVER_QUERY_NO_INDEX_USED | SERVER_QUERY_NO_GOOD_INDEX_USED)) && opt_log_queries_not_using_indexes && @@ -1738,16 +1745,6 @@ bool sp_process_definer(THD *thd) /** Execute command saved in thd and lex->sql_command. - Before every operation that can request a write lock for a table - wait if a global read lock exists. However do not wait if this - thread has locked tables already. No new locks can be requested - until the other locks are released. The thread that requests the - global read lock waits for write locked tables to become unlocked. - - Note that wait_if_global_read_lock() sets a protection against a new - global read lock when it succeeds. This needs to be released by - start_waiting_global_read_lock() after the operation. - @param thd Thread handle @todo @@ -1781,7 +1778,6 @@ mysql_execute_command(THD *thd) /* have table map for update for multi-update statement (BUG#37051) */ bool have_table_map_for_update= FALSE; #endif - /* Saved variable value */ DBUG_ENTER("mysql_execute_command"); #ifdef WITH_PARTITION_STORAGE_ENGINE thd->work_part_info= 0; @@ -1973,17 +1969,6 @@ mysql_execute_command(THD *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; - #ifndef DBUG_OFF if (lex->sql_command != SQLCOM_SET_OPTION) DEBUG_SYNC(thd,"before_execute_sql_command"); @@ -2058,10 +2043,6 @@ mysql_execute_command(THD *thd) if (res) break; - 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); break; } @@ -2320,20 +2301,6 @@ case SQLCOM_PREPARE: create_info.default_table_charset= create_info.table_charset; create_info.table_charset= 0; } - /* - The create-select command will open and read-lock the select table - and then create, open and write-lock the new table. If a global - read lock steps in, we get a deadlock. The write lock waits for - the global read lock, while the global read lock waits for the - select table to be closed. So we wait until the global readlock is - gone before starting both steps. Note that - wait_if_global_read_lock() sets a protection against a new global - 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 global read lock. - Protection against grl is covered by the CF_PROTECT_AGAINST_GRL flag. - */ #ifdef WITH_PARTITION_STORAGE_ENGINE { @@ -2832,6 +2799,15 @@ end_with_restore_list: thd->first_successful_insert_id_in_cur_stmt= thd->first_successful_insert_id_in_prev_stmt; + DBUG_EXECUTE_IF("after_mysql_insert", + { + const char act[]= + "now " + "wait_for signal.continue"; + DBUG_ASSERT(opt_debug_sync_timeout > 0); + DBUG_ASSERT(!debug_sync_set_action(current_thd, + STRING_WITH_LEN(act))); + };); break; } case SQLCOM_REPLACE_SELECT: @@ -3118,9 +3094,6 @@ end_with_restore_list: 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 && - thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) - goto error; thd->variables.option_bits|= OPTION_TABLE_LOCK; thd->in_lock_tables=1; @@ -3405,6 +3378,10 @@ end_with_restore_list: if (check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 1) && check_global_access(thd,CREATE_USER_ACL)) break; + + /* Replicate current user as grantor */ + thd->binlog_invoker(); + /* Conditionally writes to binlog */ if (!(res = mysql_revoke_all(thd, lex->users_list))) my_ok(thd); @@ -3421,6 +3398,9 @@ end_with_restore_list: first_table ? 0 : 1, 0)) goto error; + /* Replicate current user as grantor */ + thd->binlog_invoker(); + if (thd->security_ctx->user) // If not replication { LEX_USER *user, *tmp_user; @@ -3769,13 +3749,22 @@ 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. + We're going to issue an implicit GRANT statement so we close all + open tables. We have to keep metadata locks as this ensures that + this statement is atomic against concurent FLUSH TABLES WITH READ + LOCK. Deadlocks which can arise due to fact that this implicit + statement takes metadata locks should be detected by a deadlock + detector in MDL subsystem and reported as errors. + + No need to commit/rollback statement transaction, it's not started. + + TODO: Long-term we should either ensure that implicit GRANT statement + is written into binary log as a separate statement or make both + creation of routine and implicit GRANT parts of one fully atomic + statement. */ - close_mysql_tables(thd); + DBUG_ASSERT(thd->transaction.stmt.is_empty()); + close_thread_tables(thd); /* Check if the definer exists on slave, then use definer privilege to insert routine privileges to mysql.procs_priv. @@ -4023,49 +4012,48 @@ create_sp_error: int sp_result; int type= (lex->sql_command == SQLCOM_DROP_PROCEDURE ? TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION); + char *db= lex->spname->m_db.str; + char *name= lex->spname->m_name.str; - /* - @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) - { - char *db= lex->spname->m_db.str; - char *name= lex->spname->m_name.str; - - if (check_routine_access(thd, ALTER_PROC_ACL, db, name, - lex->sql_command == SQLCOM_DROP_PROCEDURE, 0)) - goto error; + if (check_routine_access(thd, ALTER_PROC_ACL, db, name, + lex->sql_command == SQLCOM_DROP_PROCEDURE, 0)) + goto error; - /* Conditionally writes to binlog */ - sp_result= sp_drop_routine(thd, type, lex->spname); + /* 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. - */ - close_mysql_tables(thd); + /* + We're going to issue an implicit REVOKE statement so we close all + open tables. We have to keep metadata locks as this ensures that + this statement is atomic against concurent FLUSH TABLES WITH READ + LOCK. Deadlocks which can arise due to fact that this implicit + statement takes metadata locks should be detected by a deadlock + detector in MDL subsystem and reported as errors. + + No need to commit/rollback statement transaction, it's not started. + + TODO: Long-term we should either ensure that implicit REVOKE statement + is written into binary log as a separate statement or make both + dropping of routine and implicit REVOKE parts of one fully atomic + statement. + */ + DBUG_ASSERT(thd->transaction.stmt.is_empty()); + close_thread_tables(thd); - if (sp_automatic_privileges && !opt_noacl && - sp_revoke_privileges(thd, db, name, - lex->sql_command == SQLCOM_DROP_PROCEDURE)) - { - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_PROC_AUTO_REVOKE_FAIL, - ER(ER_PROC_AUTO_REVOKE_FAIL)); - /* If this happens, an error should have been reported. */ - goto error; - } -#endif + if (sp_result != SP_KEY_NOT_FOUND && + sp_automatic_privileges && !opt_noacl && + sp_revoke_privileges(thd, db, name, + lex->sql_command == SQLCOM_DROP_PROCEDURE)) + { + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_PROC_AUTO_REVOKE_FAIL, + ER(ER_PROC_AUTO_REVOKE_FAIL)); + /* If this happens, an error should have been reported. */ + goto error; } +#endif + res= sp_result; switch (sp_result) { case SP_OK: @@ -4340,14 +4328,6 @@ error: res= TRUE; finish: - 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. - */ - thd->global_read_lock.start_waiting_global_read_lock(thd); - } DBUG_ASSERT(!thd->in_active_multi_stmt_transaction() || thd->in_multi_stmt_transaction_mode()); @@ -4383,6 +4363,11 @@ finish: close_thread_tables(thd); thd_proc_info(thd, 0); +#ifndef DBUG_OFF + if (lex->sql_command != SQLCOM_SET_OPTION && ! thd->in_sub_stmt) + DEBUG_SYNC(thd, "execute_command_after_close_tables"); +#endif + if (stmt_causes_implicit_commit(thd, CF_IMPLICIT_COMMIT_END)) { /* No transaction control allowed in sub-statements. */ @@ -4408,6 +4393,10 @@ finish: */ thd->mdl_context.release_transactional_locks(); } + else if (! thd->in_sub_stmt) + { + thd->mdl_context.release_statement_locks(); + } DBUG_RETURN(res || thd->is_error()); } @@ -5119,10 +5108,17 @@ bool check_stack_overrun(THD *thd, long margin, if ((stack_used=used_stack(thd->thread_stack,(char*) &stack_used)) >= (long) (my_thread_stack_size - margin)) { - char ebuff[MYSQL_ERRMSG_SIZE]; - my_snprintf(ebuff, sizeof(ebuff), ER(ER_STACK_OVERRUN_NEED_MORE), - stack_used, my_thread_stack_size, margin); - my_message(ER_STACK_OVERRUN_NEED_MORE, ebuff, MYF(ME_FATALERROR)); + /* + Do not use stack for the message buffer to ensure correct + behaviour in cases we have close to no stack left. + */ + char* ebuff= new char[MYSQL_ERRMSG_SIZE]; + if (ebuff) { + my_snprintf(ebuff, MYSQL_ERRMSG_SIZE, ER(ER_STACK_OVERRUN_NEED_MORE), + stack_used, my_thread_stack_size, margin); + my_message(ER_STACK_OVERRUN_NEED_MORE, ebuff, MYF(ME_FATALERROR)); + delete [] ebuff; + } return 1; } #ifndef DBUG_OFF @@ -5481,7 +5477,8 @@ void mysql_parse(THD *thd, char *rawbuf, uint length, if (found_semicolon && (ulong) (found_semicolon - thd->query())) thd->set_query_inner(thd->query(), (uint32) (found_semicolon - - thd->query() - 1)); + thd->query() - 1), + thd->charset()); /* Actually execute the query */ if (found_semicolon) { @@ -5870,7 +5867,8 @@ 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, mdl_type); + ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name, mdl_type, + MDL_TRANSACTION); DBUG_RETURN(ptr); } diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 0629d9f3737..cec047d11fc 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -166,12 +166,13 @@ int get_part_iter_for_interval_via_walking(partition_info *part_info, uint min_len, uint max_len, uint flags, PARTITION_ITERATOR *part_iter); + +#ifdef WITH_PARTITION_STORAGE_ENGINE static int cmp_rec_and_tuple(part_column_list_val *val, uint32 nvals_in_rec); static int cmp_rec_and_tuple_prune(part_column_list_val *val, uint32 n_vals_in_rec, bool tail_is_min); -#ifdef WITH_PARTITION_STORAGE_ENGINE /* Convert constants in VALUES definition to the character set the corresponding field uses. @@ -1996,7 +1997,7 @@ static int add_part_field_list(File fptr, List<char> field_list) String field_string("", 0, system_charset_info); THD *thd= current_thd; ulonglong save_options= thd->variables.option_bits; - thd->variables.option_bits= 0; + thd->variables.option_bits&= ~OPTION_QUOTE_SHOW_CREATE; append_identifier(thd, &field_string, field_str, strlen(field_str)); thd->variables.option_bits= save_options; @@ -2015,8 +2016,7 @@ static int add_name_string(File fptr, const char *name) String name_string("", 0, system_charset_info); THD *thd= current_thd; ulonglong save_options= thd->variables.option_bits; - - thd->variables.option_bits= 0; + thd->variables.option_bits&= ~OPTION_QUOTE_SHOW_CREATE; append_identifier(thd, &name_string, name, strlen(name)); thd->variables.option_bits= save_options; @@ -7484,8 +7484,8 @@ int get_part_iter_for_interval_via_mapping(partition_info *part_info, PARTITION_ITERATOR *part_iter) { Field *field= part_info->part_field_array[0]; - uint32 max_endpoint_val; - get_endpoint_func get_endpoint; + uint32 UNINIT_VAR(max_endpoint_val); + get_endpoint_func UNINIT_VAR(get_endpoint); bool can_match_multiple_values; /* is not '=' */ uint field_len= field->pack_length_in_rec(); DBUG_ENTER("get_part_iter_for_interval_via_mapping"); @@ -7528,8 +7528,8 @@ int get_part_iter_for_interval_via_mapping(partition_info *part_info, } } else - assert(0); - + MY_ASSERT_UNREACHABLE(); + can_match_multiple_values= (flags || !min_value || !max_value || memcmp(min_value, max_value, field_len)); if (can_match_multiple_values && diff --git a/sql/sql_partition_admin.cc b/sql/sql_partition_admin.cc index 98750314a4a..8f6ab5803d7 100644 --- a/sql/sql_partition_admin.cc +++ b/sql/sql_partition_admin.cc @@ -18,7 +18,9 @@ #include "sql_lex.h" // Sql_statement #include "sql_admin.h" // Analyze/Check/.._table_statement #include "sql_partition_admin.h" // Alter_table_*_partition +#ifdef WITH_PARTITION_STORAGE_ENGINE #include "ha_partition.h" // ha_partition +#endif #include "sql_base.h" // open_and_lock_tables #ifndef WITH_PARTITION_STORAGE_ENGINE diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 451277712db..0fe89cd3748 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -1738,7 +1738,11 @@ bool mysql_install_plugin(THD *thd, const LEX_STRING *name, const LEX_STRING *dl mysql_mutex_lock(&LOCK_plugin); mysql_rwlock_wrlock(&LOCK_system_variables_hash); - my_load_defaults(MYSQL_CONFIG_NAME, load_default_groups, &argc, &argv, NULL); + if (my_load_defaults(MYSQL_CONFIG_NAME, load_default_groups, &argc, &argv, NULL)) + { + report_error(REPORT_TO_USER, ER_PLUGIN_IS_NOT_LOADED, name->str); + goto err; + } error= plugin_add(thd->mem_root, name, dl, &argc, argv, REPORT_TO_USER); if (argv) free_defaults(argv); diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index fcbf2c48896..4a1d1ae7fe3 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1185,7 +1185,7 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, uint32 length= 0; THD *thd= stmt->thd; - DBUG_ENTER("insert_params_from_vars"); + DBUG_ENTER("insert_params_from_vars_with_log"); if (query->copy(stmt->query(), stmt->query_length(), default_charset_info)) DBUG_RETURN(1); @@ -2898,8 +2898,15 @@ bool Select_fetch_protocol_binary::send_result_set_metadata(List<Item> &list, ui bool Select_fetch_protocol_binary::send_eof() { + /* + Don't send EOF if we're in error condition (which implies we've already + sent or are sending an error) + */ + if (thd->is_error()) + return true; + ::my_eof(thd); - return FALSE; + return false; } @@ -3172,7 +3179,6 @@ 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. @@ -3244,7 +3250,7 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) Marker used to release metadata locks acquired while the prepared statement is being checked. */ - mdl_savepoint= thd->mdl_context.mdl_savepoint(); + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); /* The only case where we should have items in the thd->free_list is @@ -3735,7 +3741,7 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) to point at it even after we restore from backup. This is ok, as expanded query was allocated in thd->mem_root. */ - stmt_backup.set_query_inner(thd->query(), thd->query_length()); + stmt_backup.set_query_inner(thd->query_string); /* At first execution of prepared statement we may perform logical diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc index ac15239b040..6a7b0b0b3ad 100644 --- a/sql/sql_rename.cc +++ b/sql/sql_rename.cc @@ -24,8 +24,7 @@ #include "sql_table.h" // build_table_filename #include "sql_view.h" // mysql_frm_type, mysql_rename_view #include "sql_trigger.h" -#include "lock.h" // wait_if_global_read_lock - // start_waiting_global_read_lock +#include "lock.h" // MYSQL_OPEN_SKIP_TEMPORARY #include "sql_base.h" // tdc_remove_table, lock_table_names, #include "sql_handler.h" // mysql_ha_rm_tables #include "datadict.h" @@ -63,9 +62,6 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) mysql_ha_rm_tables(thd, table_list); - 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) || logger.is_log_table_enabled(QUERY_LOG_SLOW)) { @@ -108,7 +104,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) */ my_error(ER_CANT_RENAME_LOG_TABLE, MYF(0), ren_table->table_name, ren_table->table_name); - DBUG_RETURN(1); + goto err; } } else @@ -121,7 +117,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) */ my_error(ER_CANT_RENAME_LOG_TABLE, MYF(0), ren_table->table_name, ren_table->table_name); - DBUG_RETURN(1); + goto err; } else { @@ -139,7 +135,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) else my_error(ER_CANT_RENAME_LOG_TABLE, MYF(0), rename_log_table[1], rename_log_table[1]); - DBUG_RETURN(1); + goto err; } } @@ -189,7 +185,6 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) query_cache_invalidate3(thd, table_list, 0); err: - thd->global_read_lock.start_waiting_global_read_lock(thd); DBUG_RETURN(error || binlog_error); } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 2a2fe3eb36f..ef2dd1d76e1 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -47,6 +47,7 @@ #include "records.h" // init_read_record, end_read_record #include "filesort.h" // filesort_free_buffers #include "sql_union.h" // mysql_union +#include "debug_sync.h" // DEBUG_SYNC #include <m_ctype.h> #include <my_bit.h> #include <hash.h> @@ -852,6 +853,7 @@ JOIN::optimize() if (optimized) DBUG_RETURN(0); optimized= 1; + DEBUG_SYNC(thd, "before_join_optimize"); thd_proc_info(thd, "optimizing"); row_limit= ((select_distinct || order || group_list) ? HA_POS_ERROR : @@ -1754,6 +1756,9 @@ JOIN::reinit() func->clear(); } + if (!(select_options & SELECT_DESCRIBE)) + init_ftfuncs(thd, select_lex, test(order)); + DBUG_RETURN(0); } @@ -2511,6 +2516,13 @@ mysql_select(THD *thd, Item ***rref_pointer_array, { DBUG_RETURN(TRUE); } + /* + Original join tabs might be overwritten at first + subselect execution. So we need to restore them. + */ + Item_subselect *subselect= select_lex->master_unit()->item; + if (subselect && subselect->is_uncacheable() && join->reinit()) + DBUG_RETURN(TRUE); } else { @@ -4048,8 +4060,12 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, continue; } -#ifdef HAVE_purify - /* Valgrind complains about overlapped memcpy when save_pos==use. */ +#if defined(__GNUC__) && !MY_GNUC_PREREQ(4,4) + /* + Old gcc used a memcpy(), which is undefined if save_pos==use: + http://gcc.gnu.org/bugzilla/show_bug.cgi?id=19410 + http://gcc.gnu.org/bugzilla/show_bug.cgi?id=39480 + */ if (save_pos != use) #endif *save_pos= *use; @@ -13429,6 +13445,8 @@ static bool list_contains_unique_index(TABLE *table, bool (*find_func) (Field *, void *), void *data) { + if (table->pos_in_table_list->outer_join) + return 0; for (uint keynr= 0; keynr < table->s->keys; keynr++) { if (keynr == table->s->primary_key || @@ -13442,7 +13460,7 @@ list_contains_unique_index(TABLE *table, key_part < key_part_end; key_part++) { - if (key_part->field->maybe_null() || + if (key_part->field->real_maybe_null() || !find_func(key_part->field, data)) break; } @@ -15218,6 +15236,8 @@ calc_group_buffer(JOIN *join,ORDER *group) { key_length+= 8; } + else if (type == MYSQL_TYPE_BLOB) + key_length+= MAX_BLOB_WIDTH; // Can't be used as a key else { /* diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 16deb50b17c..74924f74fec 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -29,6 +29,8 @@ #include "repl_failsafe.h" #include "sql_parse.h" // check_access, check_table_access #include "sql_partition.h" // partition_element +#include "sql_derived.h" // mysql_derived_prepare, + // mysql_handle_derived, #include "sql_db.h" // check_db_dir_existence, load_db_opt_by_name #include "sql_time.h" // interval_type_to_name #include "tztime.h" // struct Time_zone @@ -98,11 +100,13 @@ static TYPELIB grant_types = { sizeof(grant_names)/sizeof(char **), static void store_key_options(THD *thd, String *packet, TABLE *table, KEY *key_info); +#ifdef WITH_PARTITION_STORAGE_ENGINE static void get_cs_converted_string_value(THD *thd, String *input_str, String *output_str, CHARSET_INFO *cs, bool use_hex); +#endif static void append_algorithm(TABLE_LIST *table, String *buff); @@ -478,12 +482,6 @@ find_files(THD *thd, List<LEX_STRING> *files, const char *db, else if (wild_compare(uname, wild, 0)) continue; } - if (!(file_name= - thd->make_lex_string(file_name, uname, file_name_len, TRUE))) - { - my_dirend(dirp); - DBUG_RETURN(FIND_FILES_OOM); - } } else { @@ -600,11 +598,11 @@ public: return m_view_access_denied_message_ptr; } - bool handle_condition(THD *thd, uint sql_errno, const char */* sqlstate */, + bool handle_condition(THD *thd, uint sql_errno, const char * /* sqlstate */, MYSQL_ERROR::enum_warning_level level, - const char *message, MYSQL_ERROR **/* cond_hdl */) + const char *message, MYSQL_ERROR ** /* cond_hdl */) { - /* + /* The handler does not handle the errors raised by itself. At this point we know if top_view is really a view. */ @@ -614,7 +612,7 @@ public: m_handling= TRUE; bool is_handled; - + switch (sql_errno) { case ER_TABLEACCESS_DENIED_ERROR: @@ -675,17 +673,24 @@ mysqld_show_create(THD *thd, TABLE_LIST *table_list) Metadata locks taken during SHOW CREATE should be released when the statmement completes as it is an information statement. */ - MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint(); + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); /* We want to preserve the tree for views. */ thd->lex->view_prepare_mode= TRUE; { + /* + Use open_tables() directly rather than open_normal_and_derived_tables(). + This ensures that close_thread_tables() is not called if open tables fails + and the error is ignored. This allows us to handle broken views nicely. + */ + uint counter; Show_create_error_handler view_error_suppressor(thd, table_list); thd->push_internal_handler(&view_error_suppressor); bool open_error= - open_normal_and_derived_tables(thd, table_list, - MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL); + open_tables(thd, &table_list, &counter, + MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL) || + mysql_handle_derived(thd->lex, &mysql_derived_prepare); thd->pop_internal_handler(); if (open_error && (thd->killed || thd->is_error())) goto exit; @@ -1731,7 +1736,7 @@ public: time_t start_time; uint command; const char *user,*host,*db,*proc_info,*state_info; - char *query; + CSET_STRING query_string; }; #ifdef HAVE_EXPLICIT_TEMPLATE_INSTANTIATION @@ -1828,12 +1833,14 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) if (mysys_var) mysql_mutex_unlock(&mysys_var->mutex); - thd_info->query=0; /* Lock THD mutex that protects its data when looking at it. */ if (tmp->query()) { uint length= min(max_query_length, tmp->query_length()); - thd_info->query= (char*) thd->strmake(tmp->query(),length); + char *q= thd->strmake(tmp->query(),length); + /* Safety: in case strmake failed, we set length to 0. */ + thd_info->query_string= + CSET_STRING(q, q ? length : 0, tmp->query_charset()); } mysql_mutex_unlock(&tmp->LOCK_thd_data); thd_info->start_time= tmp->start_time; @@ -1861,7 +1868,8 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) else protocol->store_null(); protocol->store(thd_info->state_info, system_charset_info); - protocol->store(thd_info->query, system_charset_info); + protocol->store(thd_info->query_string.str(), + thd_info->query_string.charset()); if (protocol->write()) break; /* purecov: inspected */ } @@ -3190,7 +3198,7 @@ try_acquire_high_prio_shared_mdl_lock(THD *thd, TABLE_LIST *table, { bool error; table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name, - MDL_SHARED_HIGH_PRIO); + MDL_SHARED_HIGH_PRIO, MDL_TRANSACTION); if (can_deadlock) { @@ -4934,18 +4942,29 @@ static int get_schema_views_record(THD *thd, TABLE_LIST *tables, else table->field[4]->store(STRING_WITH_LEN("NONE"), cs); - if (table->pos_in_table_list->table_open_method & - OPEN_FULL_TABLE) + /* + Only try to fill in the information about view updatability + if it is requested as part of the top-level query (i.e. + it's select * from i_s.views, as opposed to, say, select + security_type from i_s.views). Do not try to access the + underlying tables if there was an error when opening the + view: all underlying tables are released back to the table + definition cache on error inside open_normal_and_derived_tables(). + If a field is not assigned explicitly, it defaults to NULL. + */ + if (res == FALSE && + table->pos_in_table_list->table_open_method & OPEN_FULL_TABLE) { updatable_view= 0; if (tables->algorithm != VIEW_ALGORITHM_TMPTABLE) { /* - We should use tables->view->select_lex.item_list here and - can not use Field_iterator_view because the view always uses - temporary algorithm during opening for I_S and - TABLE_LIST fields 'field_translation' & 'field_translation_end' - are uninitialized is this case. + We should use tables->view->select_lex.item_list here + and can not use Field_iterator_view because the view + always uses temporary algorithm during opening for I_S + and TABLE_LIST fields 'field_translation' + & 'field_translation_end' are uninitialized is this + case. */ List<Item> *fields= &tables->view->select_lex.item_list; List_iterator<Item> it(*fields); @@ -7738,7 +7757,7 @@ bool show_create_trigger(THD *thd, const sp_name *trg_name) Metadata locks taken during SHOW CREATE TRIGGER should be released when the statement completes as it is an information statement. */ - MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint(); + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); /* Open the table by name in order to load Table_triggers_list object. @@ -7839,6 +7858,7 @@ void initialize_information_schema_acl() &is_internal_schema_access); } +#ifdef WITH_PARTITION_STORAGE_ENGINE /* Convert a string in character set in column character set format to utf8 character set if possible, the utf8 character set string @@ -7930,3 +7950,4 @@ static void get_cs_converted_string_value(THD *thd, } return; } +#endif diff --git a/sql/sql_show.h b/sql/sql_show.h index d1323ede8c1..de8f2525baa 100644 --- a/sql/sql_show.h +++ b/sql/sql_show.h @@ -104,7 +104,6 @@ bool mysqld_show_storage_engines(THD *thd); bool mysqld_show_authors(THD *thd); bool mysqld_show_contributors(THD *thd); bool mysqld_show_privileges(THD *thd); -bool mysqld_show_column_types(THD *thd); char *make_backup_log_name(char *buff, const char *name, const char* log_ext); void calc_sum_of_all_status(STATUS_VAR *to); void append_definer(THD *thd, String *buffer, const LEX_STRING *definer_user, diff --git a/sql/sql_string.h b/sql/sql_string.h index 845b7c280b1..3642a96de35 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -91,9 +91,13 @@ public: } static void *operator new(size_t size, MEM_ROOT *mem_root) throw () { return (void*) alloc_root(mem_root, (uint) size); } - static void operator delete(void *ptr_arg,size_t size) - { TRASH(ptr_arg, size); } - static void operator delete(void *ptr_arg, MEM_ROOT *mem_root) + static void operator delete(void *ptr_arg, size_t size) + { + (void) ptr_arg; + (void) size; + TRASH(ptr_arg, size); + } + static void operator delete(void *, MEM_ROOT *) { /* never called */ } ~String() { free(); } diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 1c6ca4a48d9..772496a10d5 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -23,9 +23,7 @@ #include "sql_parse.h" // test_if_data_home_dir #include "sql_cache.h" // query_cache_* #include "sql_base.h" // open_table_uncached, lock_table_names -#include "lock.h" // wait_if_global_read_lock - // start_waiting_global_read_lock, - // mysql_unlock_tables +#include "lock.h" // mysql_unlock_tables #include "strfunc.h" // find_type2, find_set #include "sql_view.h" // view_checksum #include "sql_truncate.h" // regenerate_locked_table @@ -1851,64 +1849,117 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists, { bool error; Drop_table_error_handler err_handler; + TABLE_LIST *table; DBUG_ENTER("mysql_rm_table"); - /* mark for close and remove all cached entries */ + /* Disable drop of enabled log tables, must be done before name locking */ + for (table= tables; table; table= table->next_local) + { + if (check_if_log_table(table->db_length, table->db, + table->table_name_length, table->table_name, true)) + { + my_error(ER_BAD_LOG_STATEMENT, MYF(0), "DROP"); + DBUG_RETURN(true); + } + } + + mysql_ha_rm_tables(thd, tables); if (!drop_temporary) { - if (!thd->locked_tables_mode && - thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) - DBUG_RETURN(TRUE); + if (!thd->locked_tables_mode) + { + if (lock_table_names(thd, tables, NULL, thd->variables.lock_wait_timeout, + MYSQL_OPEN_SKIP_TEMPORARY)) + DBUG_RETURN(true); + for (table= tables; table; table= table->next_local) + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name, + false); + } + else + { + for (table= tables; table; table= table->next_local) + if (table->open_type != OT_BASE_ONLY && + find_temporary_table(thd, table)) + { + /* + 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(true); + table->mdl_request.ticket= table->table->mdl_ticket; + } + } } + /* mark for close and remove all cached entries */ thd->push_internal_handler(&err_handler); - error= mysql_rm_table_part2(thd, tables, if_exists, drop_temporary, 0, 0); + error= mysql_rm_table_no_locks(thd, tables, if_exists, drop_temporary, + false, false); thd->pop_internal_handler(); - if (thd->global_read_lock.has_protection()) - thd->global_read_lock.start_waiting_global_read_lock(thd); - if (error) DBUG_RETURN(TRUE); my_ok(thd); DBUG_RETURN(FALSE); } -/* - Execute the drop of a normal or temporary table - - SYNOPSIS - mysql_rm_table_part2() - thd Thread handler - tables Tables to drop - if_exists If set, don't give an error if table doesn't exists. - In this case we give an warning of level 'NOTE' - drop_temporary Only drop temporary tables - drop_view Allow to delete VIEW .frm - dont_log_query Don't write query to log files. This will also not - generate warnings if the handler files doesn't exists - - TODO: - When logging to the binary log, we should log - tmp_tables and transactional tables as separate statements if we - are in a transaction; This is needed to get these tables into the - cached binary log that is only written on COMMIT. - - The current code only writes DROP statements that only uses temporary - tables to the cache binary log. This should be ok on most cases, but - not all. - RETURN - 0 ok - 1 Error - -1 Thread was killed +/** + Execute the drop of a normal or temporary table. + + @param thd Thread handler + @param tables Tables to drop + @param if_exists If set, don't give an error if table doesn't exists. + In this case we give an warning of level 'NOTE' + @param drop_temporary Only drop temporary tables + @param drop_view Allow to delete VIEW .frm + @param dont_log_query Don't write query to log files. This will also not + generate warnings if the handler files doesn't exists + + @retval 0 ok + @retval 1 Error + @retval -1 Thread was killed + + @note This function assumes that metadata locks have already been taken. + It is also assumed that the tables have been removed from TDC. + + @todo When logging to the binary log, we should log + tmp_tables and transactional tables as separate statements if we + are in a transaction; This is needed to get these tables into the + cached binary log that is only written on COMMIT. + The current code only writes DROP statements that only uses temporary + tables to the cache binary log. This should be ok on most cases, but + not all. */ -int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, - bool drop_temporary, bool drop_view, - bool dont_log_query) +int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, + bool drop_temporary, bool drop_view, + bool dont_log_query) { TABLE_LIST *table; char path[FN_REFLEN + 1], *alias= NULL; @@ -1922,7 +1973,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, bool non_tmp_table_deleted= 0; String built_query; String built_trans_tmp_query, built_non_trans_tmp_query; - DBUG_ENTER("mysql_rm_table_part2"); + DBUG_ENTER("mysql_rm_table_no_locks"); /* Prepares the drop statements that will be written into the binary @@ -1976,71 +2027,6 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, } } - mysql_ha_rm_tables(thd, tables); - - /* Disable drop of enabled log tables, must be done before name locking */ - for (table= tables; table; table= table->next_local) - { - if (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"); - DBUG_RETURN(1); - } - } - - if (!drop_temporary) - { - if (!thd->locked_tables_mode) - { - if (lock_table_names(thd, tables, NULL, thd->variables.lock_wait_timeout, - MYSQL_OPEN_SKIP_TEMPORARY)) - DBUG_RETURN(1); - for (table= tables; table; table= table->next_local) - { - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name, - FALSE); - } - } - else - { - for (table= tables; table; table= table->next_local) - if (table->open_type != OT_BASE_ONLY && - find_temporary_table(thd, table)) - { - /* - 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) { bool is_trans; @@ -2053,6 +2039,16 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, table->table ? (long) table->table->s : (long) -1)); /* + If we are in locked tables mode and are dropping a temporary table, + the ticket should be NULL to ensure that we don't release a lock + on a base table later. + */ + DBUG_ASSERT(!(thd->locked_tables_mode && + table->open_type != OT_BASE_ONLY && + find_temporary_table(thd, table) && + table->mdl_request.ticket != NULL)); + + /* drop_temporary_table may return one of the following error codes: . 0 - a temporary table was successfully dropped. . 1 - a temporary table was not found. @@ -2127,6 +2123,10 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, table->table= 0; } + /* Check that we have an exclusive lock on the table to be dropped. */ + DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, table->db, + table->table_name, + MDL_EXCLUSIVE)); if (thd->killed) { error= -1; @@ -2169,8 +2169,8 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, built_query.append("`,"); } } - DEBUG_SYNC(thd, "rm_table_part2_before_delete_table"); - DBUG_EXECUTE_IF("sleep_before_part2_delete_table", + DEBUG_SYNC(thd, "rm_table_no_locks_before_delete_table"); + DBUG_EXECUTE_IF("sleep_before_no_locks_delete_table", my_sleep(100000);); error= 0; if (drop_temporary || @@ -2257,7 +2257,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, ER(ER_BAD_TABLE_ERROR), MYF(0), table->table_name);); } - DEBUG_SYNC(thd, "rm_table_part2_before_binlog"); + DEBUG_SYNC(thd, "rm_table_no_locks_before_binlog"); thd->thread_specific_used|= (trans_tmp_table_deleted || non_trans_tmp_table_deleted); error= 0; @@ -3815,6 +3815,46 @@ void sp_prepare_create_field(THD *thd, Create_field *sql_field) (void) prepare_blob_field(thd, sql_field); } + +/** + Auxiliary function which allows to check if freshly created .FRM + file for table can be opened. + + @retval FALSE - Success. + @retval TRUE - Failure. +*/ + +static bool check_if_created_table_can_be_opened(THD *thd, + const char *path, + const char *db, + const char *table_name, + HA_CREATE_INFO *create_info, + handler *file) +{ + TABLE table; + TABLE_SHARE share; + bool result; + + /* + It is impossible to open definition of partitioned table without .par file. + */ + if (file->ha_create_handler_files(path, NULL, CHF_CREATE_FLAG, create_info)) + return TRUE; + + init_tmp_table_share(thd, &share, db, 0, table_name, path); + + result= (open_table_def(thd, &share, 0) || + open_table_from_share(thd, &share, "", 0, (uint) READ_ALL, + 0, &table, TRUE)); + if (! result) + (void) closefrm(&table, 0); + + free_table_share(&share); + (void) file->ha_create_handler_files(path, NULL, CHF_DELETE_FLAG, create_info); + return result; +} + + /* Create a table @@ -4241,6 +4281,29 @@ bool mysql_create_table_no_lock(THD *thd, thd->thread_specific_used= TRUE; } +#ifdef WITH_PARTITION_STORAGE_ENGINE + else if (part_info && create_info->frm_only) + { + /* + For partitioned tables we can't find some problems with table + until table is opened. Therefore in order to disallow creation + of corrupted tables we have to try to open table as the part + of its creation process. + In cases when both .FRM and SE part of table are created table + is implicitly open in ha_create_table() call. + In cases when we create .FRM without SE part we have to open + table explicitly. + */ + if (check_if_created_table_can_be_opened(thd, path, db, table_name, + create_info, file)) + { + char frm_name[FN_REFLEN]; + strxmov(frm_name, path, reg_ext, NullS); + (void) mysql_file_delete(key_file_frm, frm_name, MYF(0)); + goto err; + } + } +#endif error= FALSE; err: @@ -5592,7 +5655,6 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, 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; @@ -5754,7 +5816,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, else { target_mdl_request.init(MDL_key::TABLE, new_db, new_name, - MDL_EXCLUSIVE); + MDL_EXCLUSIVE, MDL_TRANSACTION); /* 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. @@ -5772,7 +5834,6 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, 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. @@ -5959,10 +6020,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, along with the implicit commit. */ 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); } @@ -6667,10 +6725,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) { 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); } @@ -6731,8 +6786,6 @@ err: alter_info->datetime_field->field_name); thd->abort_on_warning= save_abort_on_warning; } - if (has_target_mdl_lock) - thd->mdl_context.release_lock(target_mdl_request.ticket); DBUG_RETURN(TRUE); @@ -6744,9 +6797,6 @@ err_with_mdl: tables and release the exclusive metadata lock. */ 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); } diff --git a/sql/sql_table.h b/sql/sql_table.h index eb0b1aa94dd..aa5738fd4c9 100644 --- a/sql/sql_table.h +++ b/sql/sql_table.h @@ -174,8 +174,9 @@ bool mysql_checksum_table(THD* thd, TABLE_LIST* table_list, HA_CHECK_OPT* check_opt); bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists, my_bool drop_temporary); -int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, - bool drop_temporary, bool drop_view, bool log_query); +int mysql_rm_table_no_locks(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); diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index a9b52eee9fc..c2e3cd9944a 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -24,8 +24,6 @@ #include "parse_file.h" #include "sp.h" #include "sql_base.h" // find_temporary_table -#include "lock.h" // wait_if_global_read_lock, - // start_waiting_global_read_lock #include "sql_show.h" // append_definer, append_identifier #include "sql_table.h" // build_table_filename, // check_n_cut_mysql50_prefix @@ -391,15 +389,6 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) DBUG_RETURN(TRUE); } - /* - We don't want perform our operations while global read lock is held - so we have to wait until its end and then prevent it from occurring - again until we are done, unless we are under lock tables. - */ - if (!thd->locked_tables_mode && - thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) - DBUG_RETURN(TRUE); - if (!create) { bool if_exists= thd->lex->drop_if_exists; @@ -547,9 +536,6 @@ end: if (!create) thd->lex->restore_backup_query_tables_list(&backup); - if (thd->global_read_lock.has_protection()) - thd->global_read_lock.start_waiting_global_read_lock(thd); - if (!result) my_ok(thd); diff --git a/sql/sql_truncate.cc b/sql/sql_truncate.cc index 0cff2875ac8..909c6a08b67 100644 --- a/sql/sql_truncate.cc +++ b/sql/sql_truncate.cc @@ -472,6 +472,13 @@ bool Truncate_statement::truncate_table(THD *thd, TABLE_LIST *table_ref) binlog_stmt= !error || error != HA_ERR_WRONG_COMMAND; } + /* + If we tried to open a MERGE table and failed due to problems with the + children tables, the table will have been closed and table_ref->table + will be invalid. Reset the pointer here in any case as + query_cache_invalidate does not need a valid TABLE object. + */ + table_ref->table= NULL; query_cache_invalidate3(thd, table_ref, FALSE); } diff --git a/sql/sql_union.cc b/sql/sql_union.cc index acc0f704c44..1c0c48c0fec 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -176,7 +176,6 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, SELECT_LEX *sl, *first_sl= first_select(); select_result *tmp_result; bool is_union_select; - TABLE *empty_table= 0; DBUG_ENTER("st_select_lex_unit::prepare"); describe= test(additional_options & SELECT_DESCRIBE); @@ -278,14 +277,6 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, types= first_sl->item_list; else if (sl == first_sl) { - /* - We need to create an empty table object. It is used - to create tmp_table fields in Item_type_holder. - The main reason of this is that we can't create - field object without table. - */ - DBUG_ASSERT(!empty_table); - empty_table= (TABLE*) thd->calloc(sizeof(TABLE)); types.empty(); List_iterator_fast<Item> it(sl->item_list); Item *item_tmp; @@ -443,6 +434,7 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, err: thd_arg->lex->current_select= lex_select_save; + (void) cleanup(); DBUG_RETURN(TRUE); } diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 96b1ac67b49..b49ed2beafe 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1026,9 +1026,17 @@ int mysql_multi_update_prepare(THD *thd) /* following need for prepared statements, to run next time multi-update */ thd->lex->sql_command= SQLCOM_UPDATE_MULTI; - /* open tables and create derived ones, but do not lock and fill them */ + /* + Open tables and create derived ones, but do not lock and fill them yet. + + During prepare phase acquire only S metadata locks instead of SW locks to + keep prepare of multi-UPDATE compatible with concurrent LOCK TABLES WRITE + and global read lock. + */ if ((original_multiupdate && - open_tables(thd, &table_list, &table_count, 0)) || + open_tables(thd, &table_list, &table_count, + (thd->stmt_arena->is_stmt_prepare() ? + MYSQL_OPEN_FORCE_SHARED_MDL : 0))) || mysql_handle_derived(lex, &mysql_derived_prepare)) DBUG_RETURN(TRUE); /* @@ -2064,7 +2072,9 @@ bool multi_update::send_eof() Does updates for the last n - 1 tables, returns 0 if ok; error takes into account killed status gained in do_updates() */ - int local_error = (table_count) ? do_updates() : 0; + int local_error= thd->is_error(); + if (!local_error) + local_error = (table_count) ? do_updates() : 0; /* if local_error is not set ON until after do_updates() then later carried out killing should not affect binlogging. diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 5fdf7b8d850..54b5eb43ab1 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -22,7 +22,7 @@ #include "sql_base.h" // find_table_in_global_list, lock_table_names #include "sql_parse.h" // sql_parse #include "sql_cache.h" // query_cache_* -#include "lock.h" // wait_if_global_read_lock +#include "lock.h" // MYSQL_OPEN_SKIP_TEMPORARY #include "sql_show.h" // append_identifier #include "sql_table.h" // build_table_filename #include "sql_db.h" // mysql_opt_change_db, mysql_change_db @@ -649,13 +649,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, } #endif - - if (thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE)) - { - res= TRUE; - goto err; - } - res= mysql_register_view(thd, view, mode); if (mysql_bin_log.is_open()) @@ -704,7 +697,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, if (mode != VIEW_CREATE_NEW) query_cache_invalidate3(thd, view, 0); - thd->global_read_lock.start_waiting_global_read_lock(thd); if (res) goto err; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index d73dff9aaeb..9aa938437b1 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -7385,7 +7385,6 @@ select_lock_type: LEX *lex=Lex; lex->current_select->set_lock_for_tables(TL_WRITE); lex->safe_to_cache_query=0; - lex->protect_against_global_read_lock= TRUE; } | LOCK_SYM IN_SYM SHARE_SYM MODE_SYM { @@ -12334,6 +12333,12 @@ user: system_charset_info, 0) || check_host_name(&$$->host)) MYSQL_YYABORT; + /* + Convert hostname part of username to lowercase. + It's OK to use in-place lowercase as long as + the character set is utf8. + */ + my_casedn_str(system_charset_info, $$->host.str); } | CURRENT_USER optional_braces { @@ -12491,6 +12496,7 @@ keyword_sp: | FILE_SYM {} | FIRST_SYM {} | FIXED_SYM {} + | GENERAL {} | GEOMETRY_SYM {} | GEOMETRYCOLLECTION {} | GET_FORMAT {} @@ -12500,6 +12506,7 @@ keyword_sp: | HOSTS_SYM {} | HOUR_SYM {} | IDENTIFIED_SYM {} + | IGNORE_SERVER_IDS_SYM {} | INVOKER_SYM {} | IMPORT {} | INDEXES {} @@ -12522,6 +12529,7 @@ keyword_sp: | LOGS_SYM {} | MAX_ROWS {} | MASTER_SYM {} + | MASTER_HEARTBEAT_PERIOD_SYM {} | MASTER_HOST_SYM {} | MASTER_PORT_SYM {} | MASTER_LOG_FILE_SYM {} @@ -12627,6 +12635,7 @@ keyword_sp: | SIMPLE_SYM {} | SHARE_SYM {} | SHUTDOWN {} + | SLOW {} | SNAPSHOT_SYM {} | SOUNDS_SYM {} | SOURCE_SYM {} @@ -13172,9 +13181,6 @@ table_lock: MDL_SHARED_NO_READ_WRITE : MDL_SHARED_READ))) MYSQL_YYABORT; - /* If table is to be write locked, protect from a impending GRL. */ - if (lock_for_write) - Lex->protect_against_global_read_lock= TRUE; } ; diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 5c9df82ddac..9ca97b5dded 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -1436,7 +1436,6 @@ static Sys_var_ulong Sys_read_buff_size( VALID_RANGE(IO_SIZE*2, INT_MAX32), DEFAULT(128*1024), BLOCK_SIZE(IO_SIZE)); -static my_bool read_only; static bool check_read_only(sys_var *self, THD *thd, set_var *var) { /* Prevent self dead-lock */ @@ -1520,6 +1519,16 @@ static bool fix_read_only(sys_var *self, THD *thd, enum_var_type type) read_only= opt_readonly; DBUG_RETURN(result); } + + +/** + The read_only boolean is always equal to the opt_readonly boolean except + during fix_read_only(); when that function is entered, opt_readonly is + the pre-update value and read_only is the post-update value. + fix_read_only() compares them and runs needed operations for the + transition (especially when transitioning from false to true) and + synchronizes both booleans in the end. +*/ static Sys_var_mybool Sys_readonly( "read_only", "Make all non-temporary tables read-only, with the exception for " diff --git a/sql/table.cc b/sql/table.cc index f55095d6e82..8cd2e9e9bab 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -5219,7 +5219,8 @@ void init_mdl_requests(TABLE_LIST *table_list) 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); + MDL_SHARED_WRITE : MDL_SHARED_READ, + MDL_TRANSACTION); } diff --git a/sql/table.h b/sql/table.h index c8e1ad8e658..6aba4f21db2 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1384,7 +1384,8 @@ struct TABLE_LIST 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); + MDL_SHARED_WRITE : MDL_SHARED_READ, + MDL_TRANSACTION); } /* diff --git a/sql/transaction.cc b/sql/transaction.cc index d3e3ba142b9..b331fea89fe 100644 --- a/sql/transaction.cc +++ b/sql/transaction.cc @@ -21,6 +21,7 @@ #include "sql_priv.h" #include "transaction.h" #include "rpl_handler.h" +#include "debug_sync.h" // DEBUG_SYNC /* Conditions under which the transaction state must not change. */ static bool trans_check(THD *thd) @@ -391,15 +392,15 @@ bool trans_savepoint(THD *thd, LEX_STRING name) 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 + Remember locks acquired before the savepoint was set. + They are 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(); + newsv->mdl_savepoint= thd->mdl_context.mdl_savepoint(); DBUG_RETURN(FALSE); } @@ -645,17 +646,31 @@ bool trans_xa_commit(THD *thd) } 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)) + MDL_request mdl_request; + + /* + Acquire metadata lock which will ensure that COMMIT is blocked + by active FLUSH TABLES WITH READ LOCK (and vice versa COMMIT in + progress blocks FTWRL). + + We allow FLUSHer to COMMIT; we assume FLUSHer knows what it does. + */ + mdl_request.init(MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE, + MDL_TRANSACTION); + + if (thd->mdl_context.acquire_lock(&mdl_request, + thd->variables.lock_wait_timeout)) { ha_rollback_trans(thd, TRUE); my_error(ER_XAER_RMERR, MYF(0)); } else { + DEBUG_SYNC(thd, "trans_xa_commit_after_acquire_commit_lock"); + 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 |