From aaf2bdde94c582086e4ced9f9ce84b9a276d36aa Mon Sep 17 00:00:00 2001 From: Mats Kindahl Date: Mon, 7 Jun 2010 16:01:39 +0200 Subject: WL#5363: Thread Pool Service Interface In order to allow thread schedulers to be dynamically loaded, it is necessary to make the following changes to the server: - Two new service interfaces - Modifications to InnoDB to inform the thread scheduler of state changes. - Changes to the VIO subsystem for checking if data is available on a socket. - Elimination of remains of the old thread pool implementation. The two new service interfaces introduces are: my_thread_scheduler A service interface to register a thread scheduler. thd_wait A service interface to inform thread scheduler that the thread is about to start waiting. In addition, the patch adds code that: - Add a call to thd_wait for table locks in mysys thd_lock.c by introducing a set function that can be used to set a callback to be used when waiting on a lock and resuming from waiting. - Calling the mysys set function from the server to set the callbacks correctly. --- sql/authors.h | 1 + sql/mysqld.cc | 51 +++++---------- sql/mysqld.h | 3 +- sql/scheduler.cc | 162 +++++++++++++++++++++++++++++++++++----------- sql/scheduler.h | 61 +++++++++++++---- sql/sql_callback.h | 43 ++++++++++++ sql/sql_class.cc | 75 ++++++++++++++++++++- sql/sql_class.h | 7 +- sql/sql_connect.cc | 15 +++-- sql/sql_connect.h | 4 ++ sql/sql_plugin_services.h | 16 ++++- sql/sql_show.cc | 6 +- sql/sys_vars.cc | 19 +----- 13 files changed, 350 insertions(+), 113 deletions(-) create mode 100644 sql/sql_callback.h (limited to 'sql') diff --git a/sql/authors.h b/sql/authors.h index 555fe2ae43a..210141b5e22 100644 --- a/sql/authors.h +++ b/sql/authors.h @@ -92,6 +92,7 @@ struct show_table_authors_st show_table_authors[]= { { "Arjen Lentz", "Brisbane, Australia", "Documentation (2001-2004), Dutch error messages, LOG2()" }, { "Marc Liyanage", "", "Created Mac OS X packages" }, + { "Kelly Long", "Denver, CO, USA", "Pool Of Threads" }, { "Zarko Mocnik", "", "Sorting for Slovenian language" }, { "Per-Erik Martin", "Uppsala, Sweden", "Stored Procedures (5.0)" }, { "Alexis Mikhailov", "", "User-defined functions" }, diff --git a/sql/mysqld.cc b/sql/mysqld.cc index b35e2545a18..a61f2ccbc23 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -64,7 +64,9 @@ #include "events.h" #include "sql_audit.h" #include "probes_mysql.h" +#include "scheduler.h" #include "debug_sync.h" +#include "sql_callback.h" #ifdef WITH_PERFSCHEMA_STORAGE_ENGINE #include "../storage/perfschema/pfs_server.h" @@ -504,7 +506,7 @@ ulong slave_trans_retries; uint slave_net_timeout; uint slave_exec_mode_options; ulonglong slave_type_conversions_options; -ulong thread_cache_size=0, thread_pool_size= 0; +ulong thread_cache_size=0; ulong binlog_cache_size=0; ulonglong max_binlog_cache_size=0; ulong query_cache_size=0; @@ -935,8 +937,6 @@ my_bool opt_enable_shared_memory; HANDLE smem_event_connect_request= 0; #endif -scheduler_functions thread_scheduler; - my_bool opt_use_ssl = 0; char *opt_ssl_ca= NULL, *opt_ssl_capath= NULL, *opt_ssl_cert= NULL, *opt_ssl_cipher= NULL, *opt_ssl_key= NULL; @@ -1125,7 +1125,8 @@ static void close_connections(void) continue; tmp->killed= THD::KILL_CONNECTION; - thread_scheduler.post_kill_notification(tmp); + MYSQL_CALLBACK(thread_scheduler, post_kill_notification, (tmp)); + mysql_mutex_lock(&tmp->LOCK_thd_data); if (tmp->mysys_var) { tmp->mysys_var->abort=1; @@ -1138,6 +1139,7 @@ static void close_connections(void) } mysql_mutex_unlock(&tmp->mysys_var->mutex); } + mysql_mutex_unlock(&tmp->LOCK_thd_data); } mysql_mutex_unlock(&LOCK_thread_count); // For unlink from list @@ -1543,7 +1545,7 @@ void clean_up(bool print_message) if (print_message && my_default_lc_messages && server_start_time) sql_print_information(ER_DEFAULT(ER_SHUTDOWN_COMPLETE),my_progname); cleanup_errmsgs(); - thread_scheduler.end(); + MYSQL_CALLBACK(thread_scheduler, end, ()); finish_client_errs(); DBUG_PRINT("quit", ("Error messages freed")); /* Tell main we are ready */ @@ -1823,7 +1825,7 @@ static void network_init(void) DBUG_ENTER("network_init"); LINT_INIT(ret); - if (thread_scheduler.init()) + if (MYSQL_CALLBACK_ELSE(thread_scheduler, init, (), 0)) unireg_abort(1); /* purecov: inspected */ set_ports(); @@ -2071,7 +2073,7 @@ extern "C" sig_handler end_thread_signal(int sig __attribute__((unused))) if (thd && ! thd->bootstrap) { statistic_increment(killed_threads, &LOCK_status); - thread_scheduler.end_thread(thd,0); /* purecov: inspected */ + MYSQL_CALLBACK(thread_scheduler, end_thread, (thd,0)); /* purecov: inspected */ } DBUG_VOID_RETURN; /* purecov: deadcode */ } @@ -2679,7 +2681,7 @@ and this may fail.\n\n"); (ulong) dflt_key_cache->key_cache_mem_size); fprintf(stderr, "read_buffer_size=%ld\n", (long) global_system_variables.read_buff_size); fprintf(stderr, "max_used_connections=%lu\n", max_used_connections); - fprintf(stderr, "max_threads=%u\n", thread_scheduler.max_threads); + fprintf(stderr, "max_threads=%u\n", thread_scheduler->max_threads); fprintf(stderr, "thread_count=%u\n", thread_count); fprintf(stderr, "connection_count=%u\n", connection_count); fprintf(stderr, "It is possible that mysqld could use up to \n\ @@ -2687,7 +2689,7 @@ key_buffer_size + (read_buffer_size + sort_buffer_size)*max_threads = %lu K\n\ bytes of memory\n", ((ulong) dflt_key_cache->key_cache_mem_size + (global_system_variables.read_buff_size + global_system_variables.sortbuff_size) * - thread_scheduler.max_threads + + thread_scheduler->max_threads + max_connections * sizeof(THD)) / 1024); fprintf(stderr, "Hope that's ok; if not, decrease some variables in the equation.\n\n"); @@ -2932,7 +2934,7 @@ pthread_handler_t signal_hand(void *arg __attribute__((unused))) This should actually be '+ max_number_of_slaves' instead of +10, but the +10 should be quite safe. */ - init_thr_alarm(thread_scheduler.max_threads + + init_thr_alarm(thread_scheduler->max_threads + global_system_variables.max_insert_delayed_threads + 10); if (thd_lib_detected != THD_LIB_LT && (test_flags & TEST_SIGINT)) { @@ -4640,23 +4642,6 @@ int mysqld_main(int argc, char **argv) } #endif -#ifdef __WIN__ - /* - Before performing any socket operation (like retrieving hostname - in init_common_variables we have to call WSAStartup - */ - { - WSADATA WsaData; - if (SOCKET_ERROR == WSAStartup (0x0101, &WsaData)) - { - /* errors are not read yet, so we use english text here */ - my_message(ER_WSAS_FAILED, "WSAStartup Failed", MYF(0)); - /* Not enough initializations for unireg_abort() */ - return 1; - } - } -#endif /* __WIN__ */ - if (init_common_variables()) unireg_abort(1); // Will do exit @@ -5310,7 +5295,7 @@ static void create_new_thread(THD *thd) thread_count++; - thread_scheduler.add_connection(thd); + MYSQL_CALLBACK(thread_scheduler, add_connection, (thd)); DBUG_VOID_RETURN; } @@ -7633,14 +7618,12 @@ static int get_options(int *argc_ptr, char ***argv_ptr) return 1; #ifdef EMBEDDED_LIBRARY - one_thread_scheduler(&thread_scheduler); + one_thread_scheduler(); #else if (thread_handling <= SCHEDULER_ONE_THREAD_PER_CONNECTION) - one_thread_per_connection_scheduler(&thread_scheduler); - else if (thread_handling == SCHEDULER_NO_THREADS) - one_thread_scheduler(&thread_scheduler); - else - pool_of_threads_scheduler(&thread_scheduler); /* purecov: tested */ + one_thread_per_connection_scheduler(); + else /* thread_handling == SCHEDULER_NO_THREADS) */ + one_thread_scheduler(); #endif global_system_variables.engine_condition_pushdown= diff --git a/sql/mysqld.h b/sql/mysqld.h index 2547100d8ff..3ad43df3208 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -177,7 +177,7 @@ extern ulong binlog_cache_size, open_files_limit; extern ulonglong max_binlog_cache_size; extern ulong max_binlog_size, max_relay_log_size; extern ulong opt_binlog_rows_event_max_size; -extern ulong rpl_recovery_rank, thread_cache_size, thread_pool_size; +extern ulong rpl_recovery_rank, thread_cache_size; extern ulong back_log; extern char language[FN_REFLEN]; extern ulong server_id, concurrency; @@ -211,7 +211,6 @@ extern int bootstrap_error; extern FILE *stderror_file; extern I_List threads; extern char err_shared_dir[]; -extern scheduler_functions thread_scheduler; extern TYPELIB thread_handling_typelib; extern my_decimal decimal_zero; diff --git a/sql/scheduler.cc b/sql/scheduler.cc index 10009246428..19f8ddc7355 100644 --- a/sql/scheduler.cc +++ b/sql/scheduler.cc @@ -25,55 +25,98 @@ #include "unireg.h" // REQUIRED: for other includes #include "scheduler.h" #include "sql_connect.h" // init_new_connection_handler_thread +#include "scheduler.h" +#include "sql_callback.h" /* - 'Dummy' functions to be used when we don't need any handling for a scheduler - event - */ + End connection, in case when we are using 'no-threads' +*/ -static bool init_dummy(void) {return 0;} -static void post_kill_dummy(THD* thd) {} -static void end_dummy(void) {} -static bool end_thread_dummy(THD *thd, bool cache_thread) { return 0; } +static bool no_threads_end(THD *thd, bool put_in_cache) +{ + unlink_thd(thd); + mysql_mutex_unlock(&LOCK_thread_count); + return 1; // Abort handle_one_connection +} -/* - Initialize default scheduler with dummy functions so that setup functions - only need to declare those that are relvant for their usage +static scheduler_functions one_thread_scheduler_functions= +{ + 1, // max_threads + NULL, // init + init_new_connection_handler_thread, // init_new_connection_thread + handle_connection_in_main_thread, // add_connection + NULL, // thd_wait_begin + NULL, // thd_wait_end + NULL, // post_kill_notification + no_threads_end, // end_thread + NULL, // end +}; + +static scheduler_functions one_thread_per_connection_scheduler_functions= +{ + 0, // max_threads + NULL, // init + init_new_connection_handler_thread, // init_new_connection_thread + create_thread_to_handle_connection, // add_connection + NULL, // thd_wait_begin + NULL, // thd_wait_end + NULL, // post_kill_notification + one_thread_per_connection_end, // end_thread + NULL, // end +}; + + +scheduler_functions *thread_scheduler= + &one_thread_per_connection_scheduler_functions; + +/** @internal + Helper functions to allow mysys to call the thread scheduler when + waiting for locks. */ -scheduler_functions::scheduler_functions() - :init(init_dummy), - init_new_connection_thread(init_new_connection_handler_thread), - add_connection(0), // Must be defined - post_kill_notification(post_kill_dummy), - end_thread(end_thread_dummy), end(end_dummy) -{} +/**@{*/ +static void scheduler_wait_begin(void) { + MYSQL_CALLBACK(thread_scheduler, + thd_wait_begin, (current_thd, THD_WAIT_ROW_TABLE_LOCK)); +} +static void scheduler_wait_end(void) { + MYSQL_CALLBACK(thread_scheduler, thd_wait_end, (current_thd)); +} +/**@}*/ + +/** + Common scheduler init function. + + The scheduler is either initialized by calling + one_thread_scheduler() or one_thread_per_connection_scheduler() in + mysqld.cc, so this init function will always be called. + */ +static void scheduler_init() { + thr_set_lock_wait_callback(scheduler_wait_begin, scheduler_wait_end); +} /* - End connection, in case when we are using 'no-threads' + Initialize scheduler for --thread-handling=one-thread-per-connection */ -static bool no_threads_end(THD *thd, bool put_in_cache) +#ifndef EMBEDDED_LIBRARY +void one_thread_per_connection_scheduler() { - unlink_thd(thd); - mysql_mutex_unlock(&LOCK_thread_count); - return 1; // Abort handle_one_connection + scheduler_init(); + one_thread_per_connection_scheduler_functions.max_threads= max_connections; + thread_scheduler= &one_thread_per_connection_scheduler_functions; } - +#endif /* Initailize scheduler for --thread-handling=no-threads */ -void one_thread_scheduler(scheduler_functions* func) +void one_thread_scheduler() { - func->max_threads= 1; -#ifndef EMBEDDED_LIBRARY - func->add_connection= handle_connection_in_main_thread; -#endif - func->init_new_connection_thread= init_dummy; - func->end_thread= no_threads_end; + scheduler_init(); + thread_scheduler= &one_thread_scheduler_functions; } @@ -81,11 +124,58 @@ void one_thread_scheduler(scheduler_functions* func) Initialize scheduler for --thread-handling=one-thread-per-connection */ -#ifndef EMBEDDED_LIBRARY -void one_thread_per_connection_scheduler(scheduler_functions* func) +/* + thd_scheduler keeps the link between THD and events. + It's embedded in the THD class. +*/ + +thd_scheduler::thd_scheduler() + : m_psi(NULL), data(NULL) { - func->max_threads= max_connections; - func->add_connection= create_thread_to_handle_connection; - func->end_thread= one_thread_per_connection_end; +#ifndef DBUG_OFF + dbug_explain[0]= '\0'; + set_explain= FALSE; +#endif } -#endif /* EMBEDDED_LIBRARY */ + + +thd_scheduler::~thd_scheduler() +{ +} + +static scheduler_functions *saved_thread_scheduler; +static uint saved_thread_handling; + +extern "C" +int my_thread_scheduler_set(scheduler_functions *scheduler) +{ + DBUG_ASSERT(scheduler != 0); + + if (scheduler == NULL) + return 1; + + saved_thread_scheduler= thread_scheduler; + saved_thread_handling= thread_handling; + thread_scheduler= scheduler; + // Scheduler loaded dynamically + thread_handling= SCHEDULER_TYPES_COUNT; + return 0; +} + + +extern "C" +int my_thread_scheduler_reset() +{ + DBUG_ASSERT(saved_thread_scheduler != NULL); + + if (saved_thread_scheduler == NULL) + return 1; + + thread_scheduler= saved_thread_scheduler; + thread_handling= saved_thread_handling; + saved_thread_scheduler= 0; + return 0; +} + + + diff --git a/sql/scheduler.h b/sql/scheduler.h index e7916031a27..40f0e28bc2c 100644 --- a/sql/scheduler.h +++ b/sql/scheduler.h @@ -28,38 +28,77 @@ class THD; /* Functions used when manipulating threads */ -class scheduler_functions +struct scheduler_functions { -public: uint max_threads; bool (*init)(void); bool (*init_new_connection_thread)(void); void (*add_connection)(THD *thd); + void (*thd_wait_begin)(THD *thd, int wait_type); + void (*thd_wait_end)(THD *thd); void (*post_kill_notification)(THD *thd); bool (*end_thread)(THD *thd, bool cache_thread); void (*end)(void); - scheduler_functions(); }; + +/** + Scheduler types enumeration. + + The default of --thread-handling is the first one in the + thread_handling_names array, this array has to be consistent with + the order in this array, so to change default one has to change the + first entry in this enum and the first entry in the + thread_handling_names array. + + @note The last entry of the enumeration is also used to mark the + thread handling as dynamic. In this case the name of the thread + handling is fetched from the name of the plugin that implements it. +*/ enum scheduler_types { SCHEDULER_ONE_THREAD_PER_CONNECTION=0, SCHEDULER_NO_THREADS, - SCHEDULER_POOL_OF_THREADS + SCHEDULER_TYPES_COUNT }; -void one_thread_per_connection_scheduler(scheduler_functions* func); -void one_thread_scheduler(scheduler_functions* func); +void one_thread_per_connection_scheduler(); +void one_thread_scheduler(); enum pool_command_op { NOT_IN_USE_OP= 0, NORMAL_OP= 1, CONNECT_OP, KILL_OP, DIE_OP }; -#define HAVE_POOL_OF_THREADS 0 /* For easyer tests */ -#define pool_of_threads_scheduler(A) one_thread_per_connection_scheduler(A) - +/* + To be used for pool-of-threads (implemeneted differently on various OSs) +*/ class thd_scheduler -{}; +{ +public: + /* + Thread instrumentation for the user job. + This member holds the instrumentation while the user job is not run + by a thread. + + Note that this member is not conditionally declared + (ifdef HAVE_PSI_INTERFACE), because doing so will change the binary + layout of THD, which is exposed to plugin code that may be compiled + differently. + */ + PSI_thread *m_psi; + + void *data; /* scheduler-specific data structure */ + +# ifndef DBUG_OFF + char dbug_explain[512]; + bool set_explain; +# endif -#endif /* SCHEDULER_INCLUDED */ + thd_scheduler(); + ~thd_scheduler(); +}; + +extern scheduler_functions *thread_scheduler; + +#endif diff --git a/sql/sql_callback.h b/sql/sql_callback.h new file mode 100644 index 00000000000..430514d3d7e --- /dev/null +++ b/sql/sql_callback.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2010, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef SQL_CALLBACK_INCLUDED +#define SQL_CALLBACK_INCLUDED + +/** + Macro used for an internal callback. + + The macro will check that the object exists and that the function + is defined. If that is the case, it will call the function with the + given parameters. + + If the object or the function is not defined, the callback will be + considered successful (nothing needed to be done) and will + therefore return no error. + */ + +#define MYSQL_CALLBACK(OBJ, FUNC, PARAMS) \ + do { \ + if ((OBJ) && ((OBJ)->FUNC)) \ + (OBJ)->FUNC PARAMS; \ + } while (0) + +#define MYSQL_CALLBACK_ELSE(OBJ, FUNC, PARAMS, ELSE) \ + (((OBJ) && ((OBJ)->FUNC)) ? (OBJ)->FUNC PARAMS : (ELSE)) + + +#endif /* SQL_CALLBACK_INCLUDED */ diff --git a/sql/sql_class.cc b/sql/sql_class.cc index ef6dc6cf209..a47c6046bac 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -58,6 +58,7 @@ #include "transaction.h" #include "debug_sync.h" #include "sql_parse.h" // is_update_query +#include "sql_callback.h" /* The following is used to initialise Table_ident with a internal @@ -1055,6 +1056,7 @@ THD::~THD() DBUG_ENTER("~THD()"); /* Ensure that no one is using THD */ mysql_mutex_lock(&LOCK_thd_data); + mysys_var=0; // Safety (shouldn't be needed) mysql_mutex_unlock(&LOCK_thd_data); add_to_status(&global_status_var, &status_var); @@ -1080,7 +1082,6 @@ THD::~THD() main_security_ctx.destroy(); safeFree(db); free_root(&transaction.mem_root,MYF(0)); - mysys_var=0; // Safety (shouldn't be needed) mysql_mutex_destroy(&LOCK_thd_data); #ifndef DBUG_OFF dbug_sentry= THD_SENTRY_GONE; @@ -1163,7 +1164,7 @@ void THD::awake(THD::killed_state state_to_set) { thr_alarm_kill(thread_id); if (!slave_thread) - thread_scheduler.post_kill_notification(this); + MYSQL_CALLBACK(thread_scheduler, post_kill_notification, (this)); #ifdef SIGNAL_WITH_VIO_CLOSE if (this != current_thd) { @@ -1232,6 +1233,15 @@ bool THD::store_globals() if (my_pthread_setspecific_ptr(THR_THD, this) || my_pthread_setspecific_ptr(THR_MALLOC, &mem_root)) return 1; + /* + mysys_var is concurrently readable by a killer thread. + It is protected by LOCK_thd_data, it is not needed to lock while the + pointer is changing from NULL not non-NULL. If the kill thread reads + NULL it doesn't refer to anything, but if it is non-NULL we need to + ensure that the thread doesn't proceed to assign another thread to + have the mysys_var reference (which in fact refers to the worker + threads local storage with key THR_KEY_mysys. + */ mysys_var=my_thread_var; /* Let mysqld define the thread id (not mysys) @@ -3145,6 +3155,60 @@ extern "C" bool thd_binlog_filter_ok(const MYSQL_THD thd) { return binlog_filter->db_ok(thd->db); } + +#ifndef EMBEDDED_LIBRARY +extern "C" void thd_pool_wait_begin(MYSQL_THD thd, int wait_type); +extern "C" void thd_pool_wait_end(MYSQL_THD thd); + +/* + Interface for MySQL Server, plugins and storage engines to report + when they are going to sleep/stall. + + SYNOPSIS + thd_wait_begin() + thd Thread object + wait_type Type of wait + 1 -- short wait (e.g. for mutex) + 2 -- medium wait (e.g. for disk io) + 3 -- large wait (e.g. for locked row/table) + NOTES + This is used by the threadpool to have better knowledge of which + threads that currently are actively running on CPUs. When a thread + reports that it's going to sleep/stall, the threadpool scheduler is + free to start another thread in the pool most likely. The expected wait + time is simply an indication of how long the wait is expected to + become, the real wait time could be very different. + + thd_wait_end MUST be called immediately after waking up again. +*/ +extern "C" void thd_wait_begin(MYSQL_THD thd, thd_wait_type wait_type) +{ + MYSQL_CALLBACK(thread_scheduler, thd_wait_begin, (thd, wait_type)); +} + +/** + Interface for MySQL Server, plugins and storage engines to report + when they waking up from a sleep/stall. + + @param thd Thread handle +*/ +extern "C" void thd_wait_end(MYSQL_THD thd) +{ + MYSQL_CALLBACK(thread_scheduler, thd_wait_end, (thd)); +} +#else +extern "C" void thd_wait_begin(MYSQL_THD thd, thd_wait_type wait_type) +{ + /* do NOTHING for the embedded library */ + return; +} + +extern "C" void thd_wait_end(MYSQL_THD thd) +{ + /* do NOTHING for the embedded library */ + return; +} +#endif #endif // INNODB_COMPATIBILITY_HOOKS */ /**************************************************************************** @@ -3324,6 +3388,13 @@ void THD::set_query_id(query_id_t new_query_id) mysql_mutex_unlock(&LOCK_thd_data); } +/** Assign a new value to thd->mysys_var. */ +void THD::set_mysys_var(struct st_my_thread_var *new_mysys_var) +{ + mysql_mutex_lock(&LOCK_thd_data); + mysys_var= new_mysys_var; + mysql_mutex_unlock(&LOCK_thd_data); +} /** Leave explicit LOCK TABLES or prelocked mode and restore value of diff --git a/sql/sql_class.h b/sql/sql_class.h index 0a098fc8492..50c6855e2c3 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1812,6 +1812,10 @@ public: xid_state.xid.null(); free_root(&mem_root,MYF(MY_KEEP_PREALLOC)); } + my_bool is_active() + { + return (all.ha_list != NULL); + } st_transactions() { bzero((char*)this, sizeof(*this)); @@ -2734,13 +2738,14 @@ public: virtual void set_statement(Statement *stmt); /** - Assign a new value to thd->query and thd->query_id. + 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_and_id(char *query_arg, uint32 query_length_arg, query_id_t new_query_id); void set_query_id(query_id_t new_query_id); + void set_mysys_var(struct st_my_thread_var *new_mysys_var); void enter_locked_tables_mode(enum_locked_tables_mode mode_arg) { DBUG_ASSERT(locked_tables_mode == LTM_NONE); diff --git a/sql/sql_connect.cc b/sql/sql_connect.cc index e2d0977def7..00bfe372efd 100644 --- a/sql/sql_connect.cc +++ b/sql/sql_connect.cc @@ -35,6 +35,7 @@ #include "hostname.h" // inc_host_errors, ip_to_hostname, // reset_host_errors #include "sql_acl.h" // acl_getroot, NO_ACCESS, SUPER_ACL +#include "sql_callback.h" #if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) /* @@ -958,7 +959,7 @@ bool setup_connection_thread_globals(THD *thd) { close_connection(thd, ER_OUT_OF_RESOURCES, 1); statistic_increment(aborted_connects,&LOCK_status); - thread_scheduler.end_thread(thd, 0); + MYSQL_CALLBACK(thread_scheduler, end_thread, (thd, 0)); return 1; // Error } return 0; @@ -981,7 +982,7 @@ bool setup_connection_thread_globals(THD *thd) */ -static bool login_connection(THD *thd) +bool login_connection(THD *thd) { NET *net= &thd->net; int error; @@ -1019,7 +1020,7 @@ static bool login_connection(THD *thd) This mainly updates status variables */ -static void end_connection(THD *thd) +void end_connection(THD *thd) { NET *net= &thd->net; plugin_thdvar_cleanup(thd); @@ -1060,7 +1061,7 @@ static void end_connection(THD *thd) Initialize THD to handle queries */ -static void prepare_new_connection_state(THD* thd) +void prepare_new_connection_state(THD* thd) { Security_context *sctx= thd->security_ctx; @@ -1134,11 +1135,11 @@ void do_handle_one_connection(THD *thd_arg) thd->thr_create_utime= my_micro_time(); - if (thread_scheduler.init_new_connection_thread()) + if (MYSQL_CALLBACK_ELSE(thread_scheduler, init_new_connection_thread, (), 0)) { close_connection(thd, ER_OUT_OF_RESOURCES, 1); statistic_increment(aborted_connects,&LOCK_status); - thread_scheduler.end_thread(thd,0); + MYSQL_CALLBACK(thread_scheduler, end_thread, (thd, 0)); return; } @@ -1192,7 +1193,7 @@ void do_handle_one_connection(THD *thd_arg) end_thread: close_connection(thd, 0, 1); - if (thread_scheduler.end_thread(thd,1)) + if (MYSQL_CALLBACK_ELSE(thread_scheduler, end_thread, (thd, 1), 0)) return; // Probably no-threads /* diff --git a/sql/sql_connect.h b/sql/sql_connect.h index 2334b7303be..bfcc04ba093 100644 --- a/sql/sql_connect.h +++ b/sql/sql_connect.h @@ -40,4 +40,8 @@ int check_user(THD *thd, enum enum_server_command command, const char *passwd, uint passwd_len, const char *db, bool check_count); +bool login_connection(THD *thd); +void prepare_new_connection_state(THD* thd); +void end_connection(THD *thd); + #endif /* SQL_CONNECT_INCLUDED */ diff --git a/sql/sql_plugin_services.h b/sql/sql_plugin_services.h index 7491ddab79d..f39e22f1e21 100644 --- a/sql/sql_plugin_services.h +++ b/sql/sql_plugin_services.h @@ -36,9 +36,23 @@ static struct thd_alloc_service_st thd_alloc_handler= { thd_make_lex_string }; +static struct thd_wait_service_st thd_wait_handler= { + thd_wait_begin, + thd_wait_end +}; + +static struct my_thread_scheduler_service my_thread_scheduler_handler= { + my_thread_scheduler_set, + my_thread_scheduler_reset, +}; + + static struct st_service_ref list_of_services[]= { { "my_snprintf_service", VERSION_my_snprintf, &my_snprintf_handler }, - { "thd_alloc_service", VERSION_thd_alloc, &thd_alloc_handler } + { "thd_alloc_service", VERSION_thd_alloc, &thd_alloc_handler }, + { "thd_wait_service", VERSION_thd_wait, &thd_wait_handler }, + { "my_thread_scheduler_service", + VERSION_my_thread_scheduler, &my_thread_scheduler_handler }, }; diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 41117650e4a..217128653c6 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -1789,6 +1789,7 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) if ((thd_info->db=tmp->db)) // Safe test thd_info->db=thd->strdup(thd_info->db); thd_info->command=(int) tmp->command; + mysql_mutex_lock(&tmp->LOCK_thd_data); if ((mysys_var= tmp->mysys_var)) mysql_mutex_lock(&mysys_var->mutex); thd_info->proc_info= (char*) (tmp->killed == THD::KILL_CONNECTION? "Killed" : 0); @@ -1796,16 +1797,15 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) if (mysys_var) mysql_mutex_unlock(&mysys_var->mutex); - thd_info->start_time= tmp->start_time; thd_info->query=0; /* Lock THD mutex that protects its data when looking at it. */ - mysql_mutex_lock(&tmp->LOCK_thd_data); if (tmp->query()) { uint length= min(max_query_length, tmp->query_length()); thd_info->query= (char*) thd->strmake(tmp->query(),length); } mysql_mutex_unlock(&tmp->LOCK_thd_data); + thd_info->start_time= tmp->start_time; thread_infos.append(thd_info); } } @@ -1892,6 +1892,7 @@ int fill_schema_processlist(THD* thd, TABLE_LIST* tables, COND* cond) table->field[3]->set_notnull(); } + mysql_mutex_lock(&tmp->LOCK_thd_data); if ((mysys_var= tmp->mysys_var)) mysql_mutex_lock(&mysys_var->mutex); /* COMMAND */ @@ -1912,6 +1913,7 @@ int fill_schema_processlist(THD* thd, TABLE_LIST* tables, COND* cond) if (mysys_var) mysql_mutex_unlock(&mysys_var->mutex); + mysql_mutex_unlock(&tmp->LOCK_thd_data); /* INFO */ if (tmp->query()) diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index b5df2ae58c1..e743e994444 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -1658,19 +1658,13 @@ static Sys_var_ulong Sys_trans_prealloc_size( static const char *thread_handling_names[]= { - "one-thread-per-connection", "no-threads", -#if HAVE_POOL_OF_THREADS == 1 - "pool-of-threads", -#endif + "one-thread-per-connection", "no-threads", "loaded-dynamically", 0 }; static Sys_var_enum Sys_thread_handling( "thread_handling", "Define threads usage for handling queries, one of " - "one-thread-per-connection, no-threads" -#if HAVE_POOL_OF_THREADS == 1 - ", pool-of-threads" -#endif + "one-thread-per-connection, no-threads, loaded-dynamically" , READ_ONLY GLOBAL_VAR(thread_handling), CMD_LINE(REQUIRED_ARG), thread_handling_names, DEFAULT(0)); @@ -1997,15 +1991,6 @@ static Sys_var_ulong Sys_thread_cache_size( GLOBAL_VAR(thread_cache_size), CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 16384), DEFAULT(0), BLOCK_SIZE(1)); -#if HAVE_POOL_OF_THREADS == 1 -static Sys_var_ulong Sys_thread_pool_size( - "thread_pool_size", - "How many threads we should create to handle query requests in " - "case of 'thread_handling=pool-of-threads'", - GLOBAL_VAR(thread_pool_size), CMD_LINE(REQUIRED_ARG), - VALID_RANGE(1, 16384), DEFAULT(20), BLOCK_SIZE(0)); -#endif - // Can't change the 'next' tx_isolation if we are already in a transaction static bool check_tx_isolation(sys_var *self, THD *thd, set_var *var) { -- cgit v1.2.1 From 7017775fd11c4249c1b64ff45564c88b94cf526f Mon Sep 17 00:00:00 2001 From: Mats Kindahl Date: Tue, 15 Jun 2010 09:44:26 +0200 Subject: WL#5363: Thread Pool Service Interface Fixes to ensure that it builds with the embedded server as well. --- sql/scheduler.cc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'sql') diff --git a/sql/scheduler.cc b/sql/scheduler.cc index 19f8ddc7355..d61a452b99e 100644 --- a/sql/scheduler.cc +++ b/sql/scheduler.cc @@ -44,7 +44,11 @@ static scheduler_functions one_thread_scheduler_functions= 1, // max_threads NULL, // init init_new_connection_handler_thread, // init_new_connection_thread +#ifndef EMBEDDED_LIBRARY handle_connection_in_main_thread, // add_connection +#else + NULL, // add_connection +#endif // EMBEDDED_LIBRARY NULL, // thd_wait_begin NULL, // thd_wait_end NULL, // post_kill_notification @@ -52,6 +56,7 @@ static scheduler_functions one_thread_scheduler_functions= NULL, // end }; +#ifndef EMBEDDED_LIBRARY static scheduler_functions one_thread_per_connection_scheduler_functions= { 0, // max_threads @@ -64,10 +69,10 @@ static scheduler_functions one_thread_per_connection_scheduler_functions= one_thread_per_connection_end, // end_thread NULL, // end }; +#endif // EMBEDDED_LIBRARY -scheduler_functions *thread_scheduler= - &one_thread_per_connection_scheduler_functions; +scheduler_functions *thread_scheduler= NULL; /** @internal Helper functions to allow mysys to call the thread scheduler when -- cgit v1.2.1 From b7ad17d06a31e0ca8b8ef771e8ba3fcd8b534b32 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Thu, 8 Jul 2010 14:36:55 +0200 Subject: Bug#46086: crash when dropping a partitioned table and the original engine is disabled Missing check that engine is available. mysql-test/include/not_blackhole.inc: new include file mysql-test/r/partition_not_blackhole.result: new result file mysql-test/std_data/parts/t1_blackhole.frm: blackhole partitioned table .frm file: create table `t1` (`id` int primary key) engine=blackhole partition by key () partitions 1; mysql-test/std_data/parts/t1_blackhole.par: .par file matching blackhole partitioned .frm mysql-test/t/partition_not_blackhole-master.opt: new master-opt to disable blackhole if compiled in. mysql-test/t/partition_not_blackhole.test: New test sql/ha_partition.cc: Added check that engine is available. --- sql/ha_partition.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'sql') diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index 60722f0100e..07a9035f865 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -2403,9 +2403,14 @@ bool ha_partition::get_from_handler_file(const char *name, MEM_ROOT *mem_root) tot_partition_words= (m_tot_parts + 3) / 4; engine_array= (handlerton **) my_alloca(m_tot_parts * sizeof(handlerton*)); for (i= 0; i < m_tot_parts; i++) + { engine_array[i]= ha_resolve_by_legacy_type(ha_thd(), (enum legacy_db_type) - *(uchar *) ((file_buffer) + 12 + i)); + *(uchar *) ((file_buffer) + + 12 + i)); + if (!engine_array[i]) + goto err3; + } address_tot_name_len= file_buffer + 12 + 4 * tot_partition_words; tot_name_words= (uint4korr(address_tot_name_len) + 3) / 4; if (len_words != (tot_partition_words + tot_name_words + 4)) -- cgit v1.2.1 From 1837dcfee747b697bce2023d94a8daff6e393039 Mon Sep 17 00:00:00 2001 From: Alexey Kopytov Date: Fri, 23 Jul 2010 15:52:54 +0400 Subject: Bug #54476: crash when group_concat and 'with rollup' in prepared statements Using GROUP_CONCAT() together with the WITH ROLLUP modifier could crash the server. The reason was a combination of several facts: 1. The Item_func_group_concat class stores pointers to ORDER objects representing the columns in the ORDER BY clause of GROUP_CONCAT(). 2. find_order_in_list() called from Item_func_group_concat::setup() modifies the ORDER objects so that their 'item' member points to the arguments list allocated in the Item_func_group_concat constructor. 3. In some cases (e.g. in JOIN::rollup_make_fields) a copy of the original Item_func_group_concat object could be created by using the Item_func_group_concat::Item_func_group_concat(THD *thd, Item_func_group_concat *item) copy constructor. The latter essentially creates a shallow copy of the source object. Memory for the arguments array is allocated on thd->mem_root, but the pointers for arguments and ORDER are copied verbatim. What happens in the test case is that when executing the query for the first time, after a copy of the original Item_func_group_concat object has been created by JOIN::rollup_make_fields(), find_order_in_list() is called for this new object. It then resolves ORDER BY by modifying the ORDER objects so that they point to elements of the arguments array which is local to the cloned object. When thd->mem_root is freed upon completing the execution, pointers in the ORDER objects become invalid. Those ORDER objects, however, are also shared with the original Item_func_group_concat object which is preserved between executions of a prepared statement. So the first call to find_order_in_list() for the original object on the second execution tries to dereference an invalid pointer. The solution is to create copies of the ORDER objects when copying Item_func_group_concat to not leave any stale pointers in other instances with different lifecycles. mysql-test/r/func_gconcat.result: Test case for bug #54476. mysql-test/t/func_gconcat.test: Test case for bug #54476. sql/item_sum.cc: Copy the ORDER objects pointed to by the elements of the 'order' array in the copy constructor of Item_func_group_concat. sql/table.h: Removed the unused 'item_copy' member of the ORDER class. --- sql/item_sum.cc | 19 ++++++++++++++++++- sql/table.h | 1 - 2 files changed, 18 insertions(+), 2 deletions(-) (limited to 'sql') diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 228e36fc9f9..1048bd3d6ff 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -3034,7 +3034,6 @@ Item_func_group_concat::Item_func_group_concat(THD *thd, tree(item->tree), unique_filter(item->unique_filter), table(item->table), - order(item->order), context(item->context), arg_count_order(item->arg_count_order), arg_count_field(item->arg_count_field), @@ -3047,6 +3046,24 @@ Item_func_group_concat::Item_func_group_concat(THD *thd, { quick_group= item->quick_group; result.set_charset(collation.collation); + + /* + Since the ORDER structures pointed to by the elements of the 'order' array + may be modified in find_order_in_list() called from + Item_func_group_concat::setup(), create a copy of those structures so that + such modifications done in this object would not have any effect on the + object being copied. + */ + ORDER *tmp; + if (!(order= (ORDER **) thd->alloc(sizeof(ORDER *) * arg_count_order + + sizeof(ORDER) * arg_count_order))) + return; + tmp= (ORDER *)(order + arg_count_order); + for (uint i= 0; i < arg_count_order; i++, tmp++) + { + memcpy(tmp, item->order[i], sizeof(ORDER)); + order[i]= tmp; + } } diff --git a/sql/table.h b/sql/table.h index 3ef3c5e0cb2..9088b3b6965 100644 --- a/sql/table.h +++ b/sql/table.h @@ -55,7 +55,6 @@ typedef struct st_order { struct st_order *next; Item **item; /* Point at item in select fields */ Item *item_ptr; /* Storage for initial item */ - Item **item_copy; /* For SPs; the original item ptr */ int counter; /* position in SELECT list, correct only if counter_used is true*/ bool asc; /* true if ascending */ -- cgit v1.2.1 From 00496b7acd1f2ac8b099ba7e6a4c7bbf09178384 Mon Sep 17 00:00:00 2001 From: Dmitry Lenev Date: Tue, 27 Jul 2010 17:34:58 +0400 Subject: Fix for bug #52044 "FLUSH TABLES WITH READ LOCK and FLUSH TABLES WITH READ LOCK are incompatible". The problem was that FLUSH TABLES WITH READ LOCK which was issued when other connection has acquired global read lock using FLUSH TABLES WITH READ LOCK was blocked and has to wait until global read lock is released. This issue stemmed from the fact that FLUSH TABLES WITH READ LOCK implementation has acquired X metadata locks on tables to be flushed. Since these locks required acquiring of global IX lock this statement was incompatible with global read lock. This patch addresses problem by using SNW metadata type of lock for tables to be flushed by FLUSH TABLES WITH READ LOCK. It is OK to acquire them without global IX lock as long as we won't try to upgrade those locks. Since SNW locks allow concurrent statements using same table FLUSH TABLE WITH READ LOCK now has to wait until old versions of tables to be flushed go away after acquiring metadata locks. Since such waiting can lead to deadlock MDL deadlock detector was extended to take into account waits for flush and resolve such deadlocks. As a bonus code in open_tables() which was responsible for waiting old versions of tables to go away was refactored. Now when we encounter old version of table in open_table() we don't back-off and wait for all old version to go away, but instead wait for this particular table to be flushed. Such approach supported by deadlock detection should reduce number of scenarios in which FLUSH TABLES aborts concurrent multi-statement transactions. Note that active FLUSH TABLES WITH READ LOCK still blocks concurrent FLUSH TABLES WITH READ LOCK statement as the former keeps tables open and thus prevents the latter statement from doing flush. mysql-test/include/handler.inc: Adjusted test case after changing status which is set when FLUSH TABLES waits for tables to be flushed from "Flushing tables" to "Waiting for table". mysql-test/r/flush.result: Added test which checks that "flush tables with read lock" is compatible with active "flush tables with read lock" but not vice-versa. This test also covers bug #52044 "FLUSH TABLES WITH READ LOCK and FLUSH TABLES WITH READ LOCK are incompatible". mysql-test/r/mdl_sync.result: Added scenarios in which wait for table to be flushed causes deadlocks to the coverage of MDL deadlock detector. mysql-test/suite/perfschema/r/dml_setup_instruments.result: Adjusted test results after removal of COND_refresh condition variable. mysql-test/suite/perfschema/r/server_init.result: Adjusted test and its results after removal of COND_refresh condition variable. mysql-test/suite/perfschema/t/server_init.test: Adjusted test and its results after removal of COND_refresh condition variable. mysql-test/t/flush.test: Added test which checks that "flush tables with read lock" is compatible with active "flush tables with read lock" but not vice-versa. This test also covers bug #52044 "FLUSH TABLES WITH READ LOCK and FLUSH TABLES WITH READ LOCK are incompatible". mysql-test/t/kill.test: Adjusted test case after changing status which is set when FLUSH TABLES waits for tables to be flushed from "Flushing tables" to "Waiting for table". mysql-test/t/lock_multi.test: Adjusted test case after changing status which is set when FLUSH TABLES waits for tables to be flushed from "Flushing tables" to "Waiting for table". mysql-test/t/mdl_sync.test: Added scenarios in which wait for table to be flushed causes deadlocks to the coverage of MDL deadlock detector. sql/ha_ndbcluster.cc: Adjusted code after adding one more parameter for close_cached_tables() call - timeout for waiting for table to be flushed. sql/ha_ndbcluster_binlog.cc: Adjusted code after adding one more parameter for close_cached_tables() call - timeout for waiting for table to be flushed. sql/lock.cc: Removed COND_refresh condition variable. See comment for sql_base.cc for details. sql/mdl.cc: Now MDL deadlock detector takes into account information about waits for table flushes when searching for deadlock. To implement this change: - Declaration of enum_deadlock_weight and Deadlock_detection_visitor were moved to mdl.h header to make them available to the code in table.cc which implements deadlock detector traversal through edges of waiters graph representing waiting for flush. - Since now MDL_context may wait not only for metadata lock but also for table to be flushed an abstract Wait_for_edge class was introduced. Its descendants MDL_ticket and Flush_ticket incapsulate specifics of inspecting waiters graph when following through edge representing wait of particular type. We no longer require global IX metadata lock when acquiring SNW or SNRW locks. Such locks are needed only when metadata locks of these types are upgraded to X locks. This allows to use SNW locks in FLUSH TABLES WITH READ LOCK implementation and keep the latter compatible with global read lock. sql/mdl.h: Now MDL deadlock detector takes into account information about waits for table flushes when searching for deadlock. To implement this change: - Declaration of enum_deadlock_weight and Deadlock_detection_visitor were moved to mdl.h header to make them available to the code in table.cc which implements deadlock detector traversal through edges of waiters graph representing waiting for flush. - Since now MDL_context may wait not only for metadata lock but also for table to be flushed an abstract Wait_for_edge class was introduced. Its descendants MDL_ticket and Flush_ticket incapsulate specifics of inspecting waiters graph when following through edge representing wait of particular type. - Deadlock_detection_visitor now has m_table_shares_visited member which allows to support recursive locking for LOCK_open. This is required when deadlock detector inspects waiters graph which contains several edges representing waits for flushes or needs to come through the such edge more than once. sql/mysqld.cc: Removed COND_refresh condition variable. See comment for sql_base.cc for details. sql/mysqld.h: Removed COND_refresh condition variable. See comment for sql_base.cc for details. sql/sql_base.cc: Changed approach to how threads are waiting for table to be flushed. Now thread that wants to wait for old table to go away subscribes for notification by adding Flush_ticket to table's share and waits using MDL_context::m_wait object. Once table gets flushed (i.e. all tables are closed and table share is ready to be destroyed) all such waiters are notified individually. Thanks to this change MDL deadlock detector can take such waits into account. To implement this/as result of this change: - tdc_wait_for_old_versions() was replaced with tdc_wait_for_old_version() which waits for individual old share to go away and which is called by open_table() after finding out that share is outdated. We don't need to perform back-off before such waiting thanks to the fact that deadlock detector now sees such waits. - As result Open_table_ctx::m_mdl_requests became unnecessary and was removed. We no longer allocate copies of MDL_request objects on MEM_ROOT when MYSQL_OPEN_FORCE_SHARED/SHARED_HIGH_PRIO flags are in effect. - close_cached_tables() and tdc_wait_for_old_version() share code which implements waiting for share to be flushed - the both use TABLE_SHARE::wait_until_flush() method. Thanks to this close_cached_tables() supports timeouts and has extra parameter for this. - Open_table_context::OT_MDL_CONFLICT enum element was renamed to OT_CONFLICT as it is now also used in cases when back-off is required to resolve deadlock caused by waiting for flush and not metadata lock. - In cases when we discover that current connection tries to open tables from different generation we now simply back-off and restart process of opening tables. To support this Open_table_context::OT_REOPEN_TABLES enum element was added. - COND_refresh condition variable became unnecessary and was removed. - mysql_notify_thread_having_shared_lock() no longer wakes up connections waiting for flush as all such connections can be waken up by deadlock detector if necessary. sql/sql_base.h: - close_cached_tables() now has one more parameter - timeout for waiting for table to be flushed. - Open_table_context::OT_MDL_CONFLICT enum element was renamed to OT_CONFLICT as it is now also used in cases when back-off is required to resolve deadlock caused by waiting for flush and not metadata lock. Added new OT_REOPEN_TABLES enum element to be used in cases when we need to restart open tables process even in the middle of transaction. - Open_table_ctx::m_mdl_requests became unnecessary and was removed. sql/sql_class.h: Added assert ensuring that we won't use LOCK_open mutex with THD::enter_cond(). Otherwise deadlocks can arise in MDL deadlock detector. sql/sql_parse.cc: Changed FLUSH TABLES WITH READ LOCK to take SNW metadata locks instead of X locks on tables to be flushed. Since we no longer require global IX lock to be taken when SNW locks are taken this makes this statement compatible with FLUSH TABLES WITH READ LOCK statement. Since SNW locks allow other connections to have table opened FLUSH TABLES WITH READ LOCK now has to wait during open_tables() for old version to go away. Such waits can lead to deadlocks which will be detected by MDL deadlock detector which now takes waits for table to be flushed into account. Also adjusted code after adding one more parameter for close_cached_tables() call - timeout for waiting for table to be flushed. sql/sql_yacc.yy: FLUSH TABLES WITH READ LOCK now needs only SNW metadata locks on tables. sql/sys_vars.cc: Adjusted code after adding one more parameter for close_cached_tables() call - timeout for waiting for table to be flushed. sql/table.cc: Implemented new approach to how threads are waiting for table to be flushed. Now thread that wants to wait for old table to go away subscribes for notification by adding Flush_ticket to table's share and waits using MDL_context::m_wait object. Once table gets flushed (i.e. all tables are closed and table share is ready to be destroyed) all such waiters are notified individually. This change allows to make such waits visible inside of MDL deadlock detector. To do it: - Added list of waiters/Flush_tickets to TABLE_SHARE class. - Changed free_table_share() to postpone freeing of share memory until last waiter goes away and to wake up subscribed waiters. - Added TABLE_SHARE::wait_until_flushed() method which implements subscription to the list of waiters for table to be flushed and waiting for this event. Implemented interface which allows to expose waits for flushes to MDL deadlock detector: - Introduced Flush_ticket class a descendant of Wait_for_edge class. - Added TABLE_SHARE::find_deadlock() method which allows deadlock detector to find out what contexts are still using old version of table in question (i.e. to find out what contexts are waited for by owner of Flush_ticket). sql/table.h: In order to support new strategy of waiting for table flush (see comment for table.cc for details) added list of waiters/Flush_tickets to TABLE_SHARE class. Implemented interface which allows to expose waits for flushes to MDL deadlock detector: - Introduced Flush_ticket class a descendant of Wait_for_edge class. - Added TABLE_SHARE::find_deadlock() method which allows deadlock detector to find out what contexts are still using old version of table in question (i.e. to find out what contexts are waited for by owner of Flush_ticket). --- sql/ha_ndbcluster.cc | 4 +- sql/ha_ndbcluster_binlog.cc | 12 +- sql/lock.cc | 26 ++--- sql/mdl.cc | 88 ++++----------- sql/mdl.h | 99 +++++++++++++++- sql/mysqld.cc | 7 +- sql/mysqld.h | 4 +- sql/sql_base.cc | 268 ++++++++++++++++++++------------------------ sql/sql_base.h | 11 +- sql/sql_class.h | 6 + sql/sql_parse.cc | 54 +++++---- sql/sql_yacc.yy | 3 +- sql/sys_vars.cc | 3 +- sql/table.cc | 257 +++++++++++++++++++++++++++++++++++++++++- sql/table.h | 51 +++++++++ 15 files changed, 606 insertions(+), 287 deletions(-) (limited to 'sql') diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc index 7cac8373bc4..e3bafe36fb7 100644 --- a/sql/ha_ndbcluster.cc +++ b/sql/ha_ndbcluster.cc @@ -679,7 +679,7 @@ int ha_ndbcluster::ndb_err(NdbTransaction *trans) bzero((char*) &table_list,sizeof(table_list)); table_list.db= m_dbname; table_list.alias= table_list.table_name= m_tabname; - close_cached_tables(thd, &table_list, FALSE, FALSE); + close_cached_tables(thd, &table_list, FALSE, FALSE, LONG_TIMEOUT); break; } default: @@ -8452,7 +8452,7 @@ int handle_trailing_share(NDB_SHARE *share) table_list.db= share->db; table_list.alias= table_list.table_name= share->table_name; mysql_mutex_assert_owner(&LOCK_open); - close_cached_tables(thd, &table_list, TRUE, FALSE); + close_cached_tables(thd, &table_list, TRUE, FALSE, LONG_TIMEOUT); mysql_mutex_lock(&ndbcluster_mutex); /* ndb_share reference temporary free */ diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index b610687496e..e7ec6d67d52 100644 --- a/sql/ha_ndbcluster_binlog.cc +++ b/sql/ha_ndbcluster_binlog.cc @@ -937,7 +937,7 @@ int ndbcluster_setup_binlog_table_shares(THD *thd) ndb_binlog_tables_inited= TRUE; if (opt_ndb_extra_logging) sql_print_information("NDB Binlog: ndb tables writable"); - close_cached_tables(NULL, NULL, TRUE, FALSE); + close_cached_tables(NULL, NULL, TRUE, FALSE, LONG_TIMEOUT); mysql_mutex_unlock(&LOCK_open); /* Signal injector thread that all is setup */ mysql_cond_signal(&injector_cond); @@ -1751,7 +1751,7 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp, bzero((char*) &table_list,sizeof(table_list)); table_list.db= (char *)dbname; table_list.alias= table_list.table_name= (char *)tabname; - close_cached_tables(thd, &table_list, TRUE, FALSE); + close_cached_tables(thd, &table_list, TRUE, FALSE, LONG_TIMEOUT); if ((error= ndbcluster_binlog_open_table(thd, share, table_share, table, 1))) @@ -1857,7 +1857,7 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp, bzero((char*) &table_list,sizeof(table_list)); table_list.db= (char *)dbname; table_list.alias= table_list.table_name= (char *)tabname; - close_cached_tables(thd, &table_list, FALSE, FALSE); + close_cached_tables(thd, &table_list, FALSE, FALSE, LONG_TIMEOUT); /* ndb_share reference create free */ DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u", share->key, share->use_count)); @@ -1978,7 +1978,7 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb, bzero((char*) &table_list,sizeof(table_list)); table_list.db= schema->db; table_list.alias= table_list.table_name= schema->name; - close_cached_tables(thd, &table_list, FALSE, FALSE); + close_cached_tables(thd, &table_list, FALSE, FALSE, LONG_TIMEOUT); } /* ndb_share reference temporary free */ if (share) @@ -2095,7 +2095,7 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb, mysql_mutex_unlock(&ndb_schema_share_mutex); /* end protect ndb_schema_share */ - close_cached_tables(NULL, NULL, FALSE, FALSE); + close_cached_tables(NULL, NULL, FALSE, FALSE, LONG_TIMEOUT); // fall through case NDBEVENT::TE_ALTER: ndb_handle_schema_change(thd, ndb, pOp, tmp_share); @@ -2252,7 +2252,7 @@ ndb_binlog_thread_handle_schema_event_post_epoch(THD *thd, bzero((char*) &table_list,sizeof(table_list)); table_list.db= schema->db; table_list.alias= table_list.table_name= schema->name; - close_cached_tables(thd, &table_list, FALSE, FALSE); + close_cached_tables(thd, &table_list, FALSE, FALSE, LONG_TIMEOUT); } if (schema_type != SOT_ALTER_TABLE) break; diff --git a/sql/lock.cc b/sql/lock.cc index 7c0acb58e7c..24566a04463 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -1298,27 +1298,19 @@ bool Global_read_lock::make_global_read_lock_block_commit(THD *thd) /** - Broadcast COND_refresh and COND_global_read_lock. - - Due to a bug in a threading library it could happen that a signal - did not reach its target. A condition for this was that the same - condition variable was used with different mutexes in - mysql_cond_wait(). Some time ago we changed LOCK_open to - LOCK_global_read_lock in global read lock handling. So COND_refresh - was used with LOCK_open and LOCK_global_read_lock. - - We did now also change from COND_refresh to COND_global_read_lock - in global read lock handling. But now it is necessary to signal - both conditions at the same time. - - @note - When signalling COND_global_read_lock within the global read lock - handling, it is not necessary to also signal COND_refresh. + 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. */ void broadcast_refresh(void) { - mysql_cond_broadcast(&COND_refresh); mysql_cond_broadcast(&COND_global_read_lock); } diff --git a/sql/mdl.cc b/sql/mdl.cc index ca66799baed..61a43c83409 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -98,70 +98,6 @@ private: }; -enum enum_deadlock_weight -{ - MDL_DEADLOCK_WEIGHT_DML= 0, - MDL_DEADLOCK_WEIGHT_DDL= 100 -}; - - -/** - A context of the recursive traversal through all contexts - in all sessions in search for deadlock. -*/ - -class Deadlock_detection_visitor -{ -public: - Deadlock_detection_visitor(MDL_context *start_node_arg) - : m_start_node(start_node_arg), - m_victim(NULL), - m_current_search_depth(0) - {} - bool enter_node(MDL_context * /* unused */); - void leave_node(MDL_context * /* unused */); - - bool inspect_edge(MDL_context *dest); - - MDL_context *get_victim() const { return m_victim; } - - /** - Change the deadlock victim to a new one if it has lower deadlock - weight. - */ - MDL_context *opt_change_victim_to(MDL_context *new_victim); -private: - /** - The context which has initiated the search. There - can be multiple searches happening in parallel at the same time. - */ - MDL_context *m_start_node; - /** If a deadlock is found, the context that identifies the victim. */ - MDL_context *m_victim; - /** Set to the 0 at start. Increased whenever - we descend into another MDL context (aka traverse to the next - wait-for graph node). When MAX_SEARCH_DEPTH is reached, we - assume that a deadlock is found, even if we have not found a - loop. - */ - uint m_current_search_depth; - /** - Maximum depth for deadlock searches. After this depth is - achieved we will unconditionally declare that there is a - deadlock. - - @note This depth should be small enough to avoid stack - being exhausted by recursive search algorithm. - - TODO: Find out what is the optimal value for this parameter. - Current value is safe, but probably sub-optimal, - as there is an anecdotal evidence that real-life - deadlocks are even shorter typically. - */ - static const uint MAX_SEARCH_DEPTH= 32; -}; - - /** Enter a node of a wait-for graph. After a node is entered, inspect_edge() will be called @@ -876,7 +812,7 @@ void MDL_ticket::destroy(MDL_ticket *ticket) uint MDL_ticket::get_deadlock_weight() const { return (m_lock->key.mdl_namespace() == MDL_key::GLOBAL || - m_type > MDL_SHARED_NO_WRITE ? + m_type >= MDL_SHARED_NO_WRITE ? MDL_DEADLOCK_WEIGHT_DDL : MDL_DEADLOCK_WEIGHT_DML); } @@ -1528,9 +1464,8 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, MDL_ticket *ticket; bool is_transactional; - DBUG_ASSERT(mdl_request->type < MDL_SHARED_NO_WRITE || - (is_lock_owner(MDL_key::GLOBAL, "", "", - MDL_INTENTION_EXCLUSIVE))); + DBUG_ASSERT(mdl_request->type != MDL_EXCLUSIVE || + is_lock_owner(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE)); DBUG_ASSERT(mdl_request->ticket == NULL); /* Don't take chances in production. */ @@ -2087,6 +2022,21 @@ end: } +/** + Traverse portion of wait-for graph which is reachable through edge + represented by this ticket in search for deadlocks. + + @retval TRUE A deadlock is found. A victim is remembered + by the visitor. + @retval FALSE +*/ + +bool MDL_ticket::find_deadlock(Deadlock_detection_visitor *dvisitor) +{ + return m_lock->find_deadlock(this, dvisitor); +} + + /** Recursively traverse the wait-for graph of MDL contexts in search for deadlocks. @@ -2105,7 +2055,7 @@ bool MDL_context::find_deadlock(Deadlock_detection_visitor *dvisitor) if (m_waiting_for) { - result= m_waiting_for->m_lock->find_deadlock(m_waiting_for, dvisitor); + result= m_waiting_for->find_deadlock(dvisitor); if (result) m_unlock_ctx= dvisitor->opt_change_victim_to(this); } diff --git a/sql/mdl.h b/sql/mdl.h index c8acd69c0f1..d7fbb14a140 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -34,7 +34,6 @@ class THD; class MDL_context; class MDL_lock; class MDL_ticket; -class Deadlock_detection_visitor; /** Type of metadata lock request. @@ -360,6 +359,96 @@ public: typedef void (*mdl_cached_object_release_hook)(void *); + +enum enum_deadlock_weight +{ + MDL_DEADLOCK_WEIGHT_DML= 0, + MDL_DEADLOCK_WEIGHT_DDL= 100 +}; + + +/** + A context of the recursive traversal through all contexts + in all sessions in search for deadlock. +*/ + +class Deadlock_detection_visitor +{ +public: + Deadlock_detection_visitor(MDL_context *start_node_arg) + : m_start_node(start_node_arg), + m_victim(NULL), + m_current_search_depth(0), + m_table_shares_visited(0) + {} + bool enter_node(MDL_context * /* unused */); + void leave_node(MDL_context * /* unused */); + + bool inspect_edge(MDL_context *dest); + + MDL_context *get_victim() const { return m_victim; } + + /** + Change the deadlock victim to a new one if it has lower deadlock + weight. + */ + MDL_context *opt_change_victim_to(MDL_context *new_victim); +private: + /** + The context which has initiated the search. There + can be multiple searches happening in parallel at the same time. + */ + MDL_context *m_start_node; + /** If a deadlock is found, the context that identifies the victim. */ + MDL_context *m_victim; + /** Set to the 0 at start. Increased whenever + we descend into another MDL context (aka traverse to the next + wait-for graph node). When MAX_SEARCH_DEPTH is reached, we + assume that a deadlock is found, even if we have not found a + loop. + */ + uint m_current_search_depth; + /** + Maximum depth for deadlock searches. After this depth is + achieved we will unconditionally declare that there is a + deadlock. + + @note This depth should be small enough to avoid stack + being exhausted by recursive search algorithm. + + TODO: Find out what is the optimal value for this parameter. + Current value is safe, but probably sub-optimal, + as there is an anecdotal evidence that real-life + deadlocks are even shorter typically. + */ + static const uint MAX_SEARCH_DEPTH= 32; + +public: + /** + Number of TABLE_SHARE objects visited by deadlock detector so far. + Used by TABLE_SHARE::find_deadlock() method to implement recursive + locking for LOCK_open mutex. + */ + uint m_table_shares_visited; +}; + + +/** + Abstract class representing edge in waiters graph to be + traversed by deadlock detection algorithm. +*/ + +class Wait_for_edge +{ +public: + virtual ~Wait_for_edge() {}; + + virtual bool find_deadlock(Deadlock_detection_visitor *dvisitor) = 0; + + virtual uint get_deadlock_weight() const = 0; +}; + + /** A granted metadata lock. @@ -380,7 +469,7 @@ typedef void (*mdl_cached_object_release_hook)(void *); threads/contexts. */ -class MDL_ticket +class MDL_ticket : public Wait_for_edge { public: /** @@ -414,6 +503,7 @@ public: bool is_incompatible_when_granted(enum_mdl_type type) const; bool is_incompatible_when_waiting(enum_mdl_type type) const; + bool find_deadlock(Deadlock_detection_visitor *dvisitor); /* A helper used to determine which lock request should be aborted. */ uint get_deadlock_weight() const; private: @@ -680,7 +770,7 @@ private: by inspecting waiting queues, but we'd very much like it to be readily available to the wait-for graph iterator. */ - MDL_ticket *m_waiting_for; + Wait_for_edge *m_waiting_for; private: MDL_ticket *find_ticket(MDL_request *mdl_req, bool *is_transactional); @@ -688,10 +778,11 @@ private: bool try_acquire_lock_impl(MDL_request *mdl_request, MDL_ticket **out_ticket); +public: void find_deadlock(); /** Inform the deadlock detector there is an edge in the wait-for graph. */ - void will_wait_for(MDL_ticket *pending_ticket) + void will_wait_for(Wait_for_edge *pending_ticket) { mysql_prlock_wrlock(&m_LOCK_waiting_for); m_waiting_for= pending_ticket; diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 375c96bdec4..2b4547a299c 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -634,7 +634,7 @@ 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_refresh, COND_global_read_lock; +mysql_cond_t COND_global_read_lock; pthread_t signal_thread; pthread_attr_t connection_attrib; mysql_mutex_t LOCK_server_started; @@ -1573,7 +1573,6 @@ static void clean_up_mutexes() mysql_mutex_destroy(&LOCK_prepared_stmt_count); mysql_mutex_destroy(&LOCK_error_messages); mysql_cond_destroy(&COND_thread_count); - mysql_cond_destroy(&COND_refresh); mysql_cond_destroy(&COND_global_read_lock); mysql_cond_destroy(&COND_thread_cache); mysql_cond_destroy(&COND_flush_thread_cache); @@ -3564,7 +3563,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_refresh, &COND_refresh, 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); @@ -7786,7 +7784,7 @@ PSI_cond_key key_PAGE_cond, key_COND_active, key_COND_pool; 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_refresh, key_COND_rpl_status, key_COND_server_started, + 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, key_master_info_start_cond, key_master_info_stop_cond, @@ -7810,7 +7808,6 @@ static PSI_cond_info all_server_conds[]= { &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_refresh, "COND_refresh", PSI_FLAG_GLOBAL}, { &key_COND_rpl_status, "COND_rpl_status", PSI_FLAG_GLOBAL}, { &key_COND_server_started, "COND_server_started", PSI_FLAG_GLOBAL}, { &key_delayed_insert_cond, "Delayed_insert::cond", 0}, diff --git a/sql/mysqld.h b/sql/mysqld.h index b07d148f507..74d840d55cb 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -255,7 +255,7 @@ extern PSI_cond_key key_PAGE_cond, key_COND_active, key_COND_pool; 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_refresh, key_COND_rpl_status, key_COND_server_started, + 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, key_master_info_start_cond, key_master_info_stop_cond, @@ -339,7 +339,7 @@ extern mysql_cond_t COND_server_started; 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_refresh, COND_manager; +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; diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 7a59fefdddd..74f03d8d3c6 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -146,9 +146,6 @@ static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables, static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry); static bool auto_repair_table(THD *thd, TABLE_LIST *table_list); static void free_cache_entry(TABLE *entry); -static bool tdc_wait_for_old_versions(THD *thd, - MDL_request_list *mdl_requests, - ulong timeout); static bool has_write_table_with_auto_increment(TABLE_LIST *tables); @@ -315,7 +312,7 @@ void table_def_start_shutdown(void) { mysql_mutex_lock(&LOCK_open); /* Free all cached but unused TABLEs and TABLE_SHAREs first. */ - close_cached_tables(NULL, NULL, TRUE, FALSE); + close_cached_tables(NULL, NULL, TRUE, FALSE, LONG_TIMEOUT); /* Ensure that TABLE and TABLE_SHARE objects which are created for tables that are open during process of plugins' shutdown are @@ -928,6 +925,7 @@ static void kill_delayed_threads_for_table(TABLE_SHARE *share) @param tables List of tables to remove from the cache @param have_lock If LOCK_open is locked @param wait_for_refresh Wait for a impending flush + @param timeout Timeout for waiting for flush to be completed. @note THD can be NULL, but then wait_for_refresh must be FALSE and tables must be NULL. @@ -941,10 +939,11 @@ static void kill_delayed_threads_for_table(TABLE_SHARE *share) */ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, - bool wait_for_refresh) + bool wait_for_refresh, ulong timeout) { bool result= FALSE; bool found= TRUE; + struct timespec abstime; DBUG_ENTER("close_cached_tables"); DBUG_ASSERT(thd || (!wait_for_refresh && !tables)); @@ -952,7 +951,16 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, mysql_mutex_lock(&LOCK_open); if (!tables) { - refresh_version++; // Force close of open tables + /* + Force close of all open tables. + + Note that code in TABLE_SHARE::wait_until_flushed() assumes that + incrementing of refresh_version and removal of unused tables and + shares from TDC happens atomically under protection of LOCK_open, + or putting it another way that TDC does not contain old shares + which don't have any tables used. + */ + refresh_version++; DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu", refresh_version)); kill_delayed_threads(); @@ -995,6 +1003,8 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, /* Code below assume that LOCK_open is released. */ DBUG_ASSERT(!have_lock); + set_timespec(abstime, timeout); + if (thd->locked_tables_mode) { /* @@ -1034,6 +1044,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, while (found && ! thd->killed) { + TABLE_SHARE *share; found= FALSE; /* To a self-deadlock or deadlocks with other FLUSH threads @@ -1044,13 +1055,11 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, mysql_mutex_lock(&LOCK_open); - thd->enter_cond(&COND_refresh, &LOCK_open, "Flushing tables"); - if (!tables) { for (uint idx=0 ; idx < table_def_cache.records ; idx++) { - TABLE_SHARE *share=(TABLE_SHARE*) my_hash_element(&table_def_cache, + share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx); if (share->needs_reopen()) { @@ -1063,7 +1072,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, { for (TABLE_LIST *table= tables; table; table= table->next_local) { - TABLE_SHARE *share= get_cached_table_share(table->db, table->table_name); + share= get_cached_table_share(table->db, table->table_name); if (share && share->needs_reopen()) { found= TRUE; @@ -1074,11 +1083,17 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, if (found) { - DBUG_PRINT("signal", ("Waiting for COND_refresh")); - mysql_cond_wait(&COND_refresh, &LOCK_open); + /* The below method will unlock LOCK_open and frees share's memory. */ + if (share->wait_until_flushed(&thd->mdl_context, &abstime, + MDL_DEADLOCK_WEIGHT_DDL)) + { + mysql_mutex_unlock(&LOCK_open); + result= TRUE; + goto err_with_reopen; + } } - thd->exit_cond(NULL); + mysql_mutex_unlock(&LOCK_open); } err_with_reopen: @@ -1149,7 +1164,7 @@ bool close_cached_connection_tables(THD *thd, bool if_wait_for_refresh, } if (tables) - result= close_cached_tables(thd, tables, TRUE, FALSE); + result= close_cached_tables(thd, tables, TRUE, FALSE, LONG_TIMEOUT); if (!have_lock) mysql_mutex_unlock(&LOCK_open); @@ -2347,7 +2362,7 @@ bool MDL_deadlock_handler::handle_condition(THD *, { /* Disable the handler to avoid infinite recursion. */ m_is_active= TRUE; - (void) m_ot_ctx->request_backoff_action(Open_table_context::OT_MDL_CONFLICT, + (void) m_ot_ctx->request_backoff_action(Open_table_context::OT_CONFLICT, NULL); m_is_active= FALSE; /* @@ -2394,6 +2409,8 @@ open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx, uint flags, MDL_ticket **mdl_ticket) { + MDL_request mdl_request_shared; + if (flags & (MYSQL_OPEN_FORCE_SHARED_MDL | MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL)) { @@ -2419,16 +2436,12 @@ open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx, DBUG_ASSERT(!(flags & MYSQL_OPEN_FORCE_SHARED_MDL) || !(flags & MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL)); - mdl_request= new (thd->mem_root) MDL_request(mdl_request); - if (mdl_request == NULL) - return TRUE; - - mdl_request->set_type((flags & MYSQL_OPEN_FORCE_SHARED_MDL) ? - MDL_SHARED : MDL_SHARED_HIGH_PRIO); + mdl_request_shared.init(&mdl_request->key, + (flags & MYSQL_OPEN_FORCE_SHARED_MDL) ? + MDL_SHARED : MDL_SHARED_HIGH_PRIO); + mdl_request= &mdl_request_shared; } - ot_ctx->add_request(mdl_request); - if (flags & MYSQL_OPEN_FAIL_ON_MDL_CONFLICT) { /* @@ -2491,6 +2504,38 @@ open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx, } +/** + Check if table's share requires flush and if yes wait until it + will be flushed. + + @param thd Thread context. + @param table_list Table which share should be checked. + @param timeout Timeout for waiting. + @param deadlock_weight Weight of this wait for deadlock detector. + + @retval FALSE - Success. Share is up to date or has been flushed. + @retval TRUE - Error (OOM, thread was killed, wait resulted in + deadlock or timeout). +*/ + +static bool tdc_wait_for_old_version(THD *thd, TABLE_LIST *table_list, + ulong timeout, uint deadlock_weight) +{ + TABLE_SHARE *share; + + if ((share= get_cached_table_share(table_list->db, + table_list->table_name)) && + share->needs_reopen()) + { + struct timespec abstime; + set_timespec(abstime, timeout); + return share->wait_until_flushed(&thd->mdl_context, &abstime, + deadlock_weight); + } + return FALSE; +} + + /* Open a table. @@ -2580,8 +2625,8 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (thd->open_tables && thd->open_tables->s->version != refresh_version) { - (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC, - NULL); + (void)ot_ctx->request_backoff_action(Open_table_context::OT_REOPEN_TABLES, + NULL); DBUG_RETURN(TRUE); } } @@ -2794,6 +2839,8 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, mysql_mutex_lock(&LOCK_open); +retry_share: + if (!(share= get_table_share_with_create(thd, table_list, key, key_length, OPEN_VIEW, &error, @@ -2849,31 +2896,50 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (table_list->i_s_requested_object & OPEN_VIEW_ONLY) goto err_unlock; - /* - If the version changes while we're opening the tables, - we have to back off, close all the tables opened-so-far, - and try to reopen them. Note: refresh_version is currently - changed only during FLUSH TABLES. - */ - if (share->needs_reopen() || - (thd->open_tables && thd->open_tables->s->version != share->version)) + if (!(flags & MYSQL_OPEN_IGNORE_FLUSH)) { - if (!(flags & MYSQL_OPEN_IGNORE_FLUSH)) + if (share->needs_reopen()) { - /* - We already have an MDL lock. But we have encountered an old - version of table in the table definition cache which is possible - when someone changes the table version directly in the cache - without acquiring a metadata lock (e.g. this can happen during - "rolling" FLUSH TABLE(S)). - Note, that to avoid a "busywait" in this case, we have to wait - separately in the caller for old table versions to go away - (see tdc_wait_for_old_versions()). - */ + /* + We already have an MDL lock. But we have encountered an old + version of table in the table definition cache which is possible + when someone changes the table version directly in the cache + without acquiring a metadata lock (e.g. this can happen during + "rolling" FLUSH TABLE(S)). + Release our reference to share, wait until old version of + share goes away and then try to get new version of table share. + */ + MDL_deadlock_handler mdl_deadlock_handler(ot_ctx); + bool wait_result; + + release_table_share(share); + + thd->push_internal_handler(&mdl_deadlock_handler); + wait_result= tdc_wait_for_old_version(thd, table_list, + ot_ctx->get_timeout(), + mdl_ticket->get_deadlock_weight()); + thd->pop_internal_handler(); + + if (wait_result) + { + mysql_mutex_unlock(&LOCK_open); + DBUG_RETURN(TRUE); + } + goto retry_share; + } + + if (thd->open_tables && thd->open_tables->s->version != share->version) + { + /* + If the version changes while we're opening the tables, + we have to back off, close all the tables opened-so-far, + and try to reopen them. Note: refresh_version is currently + changed only during FLUSH TABLES. + */ release_table_share(share); mysql_mutex_unlock(&LOCK_open); - (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC, - NULL); + (void)ot_ctx->request_backoff_action(Open_table_context::OT_REOPEN_TABLES, + NULL); DBUG_RETURN(TRUE); } } @@ -3831,7 +3897,7 @@ request_backoff_action(enum_open_table_action action_arg, Since there is no way to detect such a deadlock, we prevent it by reporting an error. */ - if (m_has_locks) + if (action_arg != OT_REOPEN_TABLES && m_has_locks) { my_error(ER_LOCK_DEADLOCK, MYF(0)); return TRUE; @@ -3877,11 +3943,9 @@ recover_from_failed_open(THD *thd) /* Execute the action. */ switch (m_action) { - case OT_MDL_CONFLICT: + case OT_CONFLICT: break; - case OT_WAIT_TDC: - result= tdc_wait_for_old_versions(thd, &m_mdl_requests, get_timeout()); - DBUG_ASSERT(thd->mysys_var->current_mutex == NULL); + case OT_REOPEN_TABLES: break; case OT_DISCOVER: { @@ -3921,8 +3985,6 @@ recover_from_failed_open(THD *thd) default: DBUG_ASSERT(0); } - /* Remove all old requests, they will be re-added. */ - m_mdl_requests.empty(); /* Reset the pointers to conflicting MDL request and the TABLE_LIST element, set when we need auto-discovery or repair, @@ -4043,8 +4105,6 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx, if (rt != (Sroutine_hash_entry*)prelocking_ctx->sroutines_list.first || mdl_type != MDL_key::PROCEDURE) { - ot_ctx->add_request(&rt->mdl_request); - /* Since we acquire only shared lock on routines we don't need to care about global intention exclusive locks. @@ -4721,6 +4781,8 @@ restart: } goto err; } + + DEBUG_SYNC(thd, "open_tables_after_open_and_process_table"); } /* @@ -8595,17 +8657,6 @@ bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use, } mysql_mutex_unlock(&in_use->LOCK_thd_data); } - /* - Wake up threads waiting in tdc_wait_for_old_versions(). - Normally such threads would already get blocked - in MDL subsystem, when trying to acquire a shared lock. - But in case a thread has an open HANDLER statement, - (and thus already grabbed a metadata lock), it gets - blocked only too late -- at the table cache level. - Starting from 5.5, this could also easily happen in - a multi-statement transaction. - */ - broadcast_refresh(); return signalled; } @@ -8680,6 +8731,13 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, /* Set share's version to zero in order to ensure that it gets automatically deleted once it is no longer referenced. + + Note that code in TABLE_SHARE::wait_until_flushed() assumes + that marking share as old and removal of its unused tables + and of the share itself from TDC happens atomically under + protection of LOCK_open, or, putting it another way, that + TDC does not contain old shares which don't have any tables + used. */ share->version= 0; @@ -8692,84 +8750,6 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, } -/** - Wait until there are no old versions of tables in the table - definition cache for the metadata locks that we try to acquire. - - @param thd Thread context - @param context Metadata locking context with locks. - @param timeout Seconds to wait before reporting ER_LOCK_WAIT_TIMEOUT. -*/ - -static bool -tdc_wait_for_old_versions(THD *thd, MDL_request_list *mdl_requests, - ulong timeout) -{ - TABLE_SHARE *share; - const char *old_msg; - MDL_request *mdl_request; - struct timespec abstime; - set_timespec(abstime, timeout); - int wait_result= 0; - - while (!thd->killed) - { - /* - We have to get rid of HANDLERs which are open by this thread - and have old TABLE versions. Otherwise we might get a deadlock - in situation when we are waiting for an old TABLE object which - corresponds to a HANDLER open by another session. And this - other session waits for our HANDLER object to get closed. - - TODO: We should also investigate in which situations we have - to broadcast on COND_refresh because of this. - */ - mysql_ha_flush(thd); - - mysql_mutex_lock(&LOCK_open); - - MDL_request_list::Iterator it(*mdl_requests); - while ((mdl_request= it++)) - { - /* Skip requests on non-TDC objects. */ - if (mdl_request->key.mdl_namespace() != MDL_key::TABLE) - continue; - - if ((share= get_cached_table_share(mdl_request->key.db_name(), - mdl_request->key.name())) && - share->needs_reopen()) - break; - } - if (!mdl_request) - { - /* - Reset wait_result here in case this was the final check - after getting a timeout from mysql_cond_timedwait(). - */ - wait_result= 0; - mysql_mutex_unlock(&LOCK_open); - break; - } - if (wait_result == ETIMEDOUT || wait_result == ETIME) - { - /* - Test for timeout here instead of right after mysql_cond_timedwait(). - This allows for a final iteration and a final check before reporting - ER_LOCK_WAIT_TIMEOUT. - */ - mysql_mutex_unlock(&LOCK_open); - my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); - break; - } - old_msg= thd->enter_cond(&COND_refresh, &LOCK_open, "Waiting for table"); - wait_result= mysql_cond_timedwait(&COND_refresh, &LOCK_open, &abstime); - /* LOCK_open mutex is unlocked by THD::exit_cond() as side-effect. */ - thd->exit_cond(old_msg); - } - return thd->killed || wait_result == ETIMEDOUT || wait_result == ETIME; -} - - int setup_ftfuncs(SELECT_LEX *select_lex) { List_iterator li(*(select_lex->ftfunc_list)), diff --git a/sql/sql_base.h b/sql/sql_base.h index b912f80d44f..7d13b69e063 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -250,7 +250,7 @@ TABLE *open_performance_schema_table(THD *thd, TABLE_LIST *one_table, void close_performance_schema_table(THD *thd, Open_tables_state *backup); bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, - bool wait_for_refresh); + bool wait_for_refresh, ulong timeout); bool close_cached_connection_tables(THD *thd, bool wait_for_refresh, LEX_STRING *connect_string, bool have_lock = FALSE); @@ -454,8 +454,8 @@ public: enum enum_open_table_action { OT_NO_ACTION= 0, - OT_MDL_CONFLICT, - OT_WAIT_TDC, + OT_CONFLICT, + OT_REOPEN_TABLES, OT_DISCOVER, OT_REPAIR }; @@ -465,9 +465,6 @@ public: bool request_backoff_action(enum_open_table_action action_arg, TABLE_LIST *table); - void add_request(MDL_request *request) - { m_mdl_requests.push_front(request); } - bool can_recover_from_failed_open() const { return m_action != OT_NO_ACTION; } @@ -489,8 +486,6 @@ public: uint get_flags() const { return m_flags; } private: - /** List of requests for all locks taken so far. Used for waiting on locks. */ - MDL_request_list m_mdl_requests; /** For OT_DISCOVER and OT_REPAIR actions, the table list element for the table which definition should be re-discovered or which diff --git a/sql/sql_class.h b/sql/sql_class.h index c095fee6232..e96c3f8dd26 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -2302,6 +2302,12 @@ public: { const char* old_msg = proc_info; mysql_mutex_assert_owner(mutex); + /* + This method should not be called with LOCK_open mutex as an + argument. Otherwise deadlocks can arise in MDL deadlock detector. + @sa TABLE_SHARE::find_deadlock(). + */ + DBUG_ASSERT(mutex != &LOCK_open); mysys_var->current_mutex = mutex; mysys_var->current_cond = cond; proc_info = msg; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 53c2ca6fa39..7cb27ff4916 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1756,6 +1756,7 @@ static bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables) { Lock_tables_prelocking_strategy lock_tables_prelocking_strategy; TABLE_LIST *table_list; + MDL_request_list mdl_requests; /* This is called from SQLCOM_FLUSH, the transaction has @@ -1774,23 +1775,27 @@ static bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables) } /* - @todo: Since lock_table_names() acquires a global IX - lock, this actually waits for a GRL in another connection. - We are thus introducing an incompatibility. - Do nothing for now, since not taking a global IX violates - current internal MDL asserts, fix after discussing with - Dmitry. + Acquire SNW locks on tables to be flushed. We can't use + lock_table_names() here as this call will also acquire global IX + and database-scope IX locks on the tables, and this will make + this statement incompatible with FLUSH TABLES WITH READ LOCK. */ - if (lock_table_names(thd, all_tables, 0, thd->variables.lock_wait_timeout, - MYSQL_OPEN_SKIP_TEMPORARY)) + for (table_list= all_tables; table_list; + table_list= table_list->next_global) + mdl_requests.push_front(&table_list->mdl_request); + + if (thd->mdl_context.acquire_locks(&mdl_requests, + thd->variables.lock_wait_timeout)) goto error; + DEBUG_SYNC(thd,"flush_tables_with_read_lock_after_acquire_locks"); + for (table_list= all_tables; table_list; table_list= table_list->next_global) { - /* Remove the table from cache. */ + /* Request removal of table from cache. */ mysql_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, + tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, table_list->db, table_list->table_name); mysql_mutex_unlock(&LOCK_open); @@ -1800,6 +1805,11 @@ static bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables) table_list->open_type= OT_BASE_ONLY; /* Ignore temporary tables. */ } + /* + Before opening and locking tables the below call also waits for old + shares to go away, so the fact that we don't pass MYSQL_LOCK_IGNORE_FLUSH + flag to it is important. + */ if (open_and_lock_tables(thd, all_tables, FALSE, MYSQL_OPEN_HAS_MDL_LOCK, &lock_tables_prelocking_strategy) || @@ -1810,17 +1820,11 @@ static bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables) thd->variables.option_bits|= OPTION_TABLE_LOCK; /* - Downgrade the exclusive locks. - Use MDL_SHARED_NO_WRITE as the intended - post effect of this call is identical - to LOCK TABLES <...> READ, and we didn't use - thd->in_lock_talbes and thd->sql_command= SQLCOM_LOCK_TABLES - hacks to enter the LTM. - @todo: release the global IX lock here!!! + We don't downgrade MDL_SHARED_NO_WRITE here as the intended + post effect of this call is identical to LOCK TABLES <...> READ, + and we didn't use thd->in_lock_talbes and + thd->sql_command= SQLCOM_LOCK_TABLES hacks to enter the LTM. */ - for (table_list= all_tables; table_list; - table_list= table_list->next_global) - table_list->mdl_request.ticket->downgrade_exclusive_lock(MDL_SHARED_NO_WRITE); return FALSE; @@ -6854,8 +6858,8 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, tmp_write_to_binlog= 0; if (thd->global_read_lock.lock_global_read_lock(thd)) return 1; // Killed - if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ? - FALSE : TRUE)) + if (close_cached_tables(thd, tables, FALSE, ((options & REFRESH_FAST) ? + FALSE : TRUE), thd->variables.lock_wait_timeout)) result= 1; if (thd->global_read_lock.make_global_read_lock_block_commit(thd)) // Killed @@ -6894,8 +6898,10 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, } } - if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ? - FALSE : TRUE)) + if (close_cached_tables(thd, tables, FALSE, ((options & REFRESH_FAST) ? + FALSE : TRUE), + (thd ? thd->variables.lock_wait_timeout : + LONG_TIMEOUT))) result= 1; } my_dbopt_cleanup(); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index ca951897055..3341ffc7a30 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -11202,9 +11202,8 @@ opt_with_read_lock: { TABLE_LIST *tables= Lex->query_tables; Lex->type|= REFRESH_READ_LOCK; - /* We acquire an X lock currently and then downgrade. */ for (; tables; tables= tables->next_global) - tables->mdl_request.set_type(MDL_EXCLUSIVE); + tables->mdl_request.set_type(MDL_SHARED_NO_WRITE); } ; diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 9e212fb95e9..cf185db0b7a 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -1488,7 +1488,8 @@ static bool fix_read_only(sys_var *self, THD *thd, enum_var_type type) can cause to wait on a read lock, it's required for the client application to unlock everything, and acceptable for the server to wait on all locks. */ - if ((result= close_cached_tables(thd, NULL, FALSE, TRUE))) + if ((result= close_cached_tables(thd, NULL, FALSE, TRUE, + thd->variables.lock_wait_timeout))) goto end_with_read_lock; if ((result= thd->global_read_lock.make_global_read_lock_block_commit(thd))) diff --git a/sql/table.cc b/sql/table.cc index a58623f0036..a8e1caa271a 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -34,6 +34,7 @@ #include #include "my_md5.h" #include "sql_select.h" +#include "mdl.h" // Deadlock_detection_visitor /* INFORMATION_SCHEMA name */ LEX_STRING INFORMATION_SCHEMA_NAME= {C_STRING_WITH_LEN("information_schema")}; @@ -325,6 +326,7 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key, share->used_tables.empty(); share->free_tables.empty(); + share->m_flush_tickets.empty(); memcpy((char*) &share->mem_root, (char*) &mem_root, sizeof(mem_root)); mysql_mutex_init(key_TABLE_SHARE_LOCK_ha_data, @@ -389,6 +391,7 @@ void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key, share->used_tables.empty(); share->free_tables.empty(); + share->m_flush_tickets.empty(); DBUG_VOID_RETURN; } @@ -432,9 +435,40 @@ void free_table_share(TABLE_SHARE *share) key_info->flags= 0; } } - /* We must copy mem_root from share because share is allocated through it */ - memcpy((char*) &mem_root, (char*) &share->mem_root, sizeof(mem_root)); - free_root(&mem_root, MYF(0)); // Free's share + + if (share->m_flush_tickets.is_empty()) + { + /* + There are no threads waiting for this share to be flushed. So + we can immediately release memory associated with it. We must + copy mem_root from share because share is allocated through it. + */ + memcpy((char*) &mem_root, (char*) &share->mem_root, sizeof(mem_root)); + free_root(&mem_root, MYF(0)); // Free's share + } + else + { + /* + If there are threads waiting for this share to be flushed we + don't free share memory here. Instead we notify waiting threads + and delegate freeing share's memory to them. + At this point a) all resources except memory associated with share + were already released b) share should have been already removed + from table definition cache. So it is OK to proceed without waiting + for these threads to finish their work. + */ + Flush_ticket_list::Iterator it(share->m_flush_tickets); + Flush_ticket *ticket; + + /* + To avoid problems due to threads being wake up concurrently modifying + flush ticket list we must hold LOCK_open here. + */ + mysql_mutex_assert_owner(&LOCK_open); + + while ((ticket= it++)) + (void) ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED); + } DBUG_VOID_RETURN; } @@ -2996,6 +3030,223 @@ Table_check_intact::check(TABLE *table, const TABLE_FIELD_DEF *table_def) } +/** + Traverse portion of wait-for graph which is reachable through edge + represented by this flush ticket in search for deadlocks. + + @retval TRUE A deadlock is found. A victim is remembered + by the visitor. + @retval FALSE +*/ + +bool Flush_ticket::find_deadlock(Deadlock_detection_visitor *dvisitor) +{ + return m_share->find_deadlock(this, dvisitor); +} + + +uint Flush_ticket::get_deadlock_weight() const +{ + return m_deadlock_weight; +} + + +/** + Traverse portion of wait-for graph which is reachable through this + table share in search for deadlocks. + + @param waiting_ticket Ticket representing wait for this share. + @param dvisitor Deadlock detection visitor. + + @retval TRUE A deadlock is found. A victim is remembered + by the visitor. + @retval FALSE +*/ + +bool TABLE_SHARE::find_deadlock(Flush_ticket *waiting_ticket, + Deadlock_detection_visitor *dvisitor) +{ + TABLE *table; + MDL_context *src_ctx= waiting_ticket->get_ctx(); + bool result= TRUE; + + /* + To protect used_tables list from being concurrently modified while we + are iterating through it we acquire LOCK_open. This should not introduce + deadlocks in deadlock detector because we support recursive acquiring of + such mutex and also because we won't try to acquire LOCK_open mutex while + holding write-lock on MDL_lock::m_rwlock. + + Here is the more elaborate proof: + + 0) Let us assume that there is a deadlock. + 1) Wait graph (the one which reflects waits for system synchronization + primitives and not the one which inspected by MDL deadlock detector) + for this deadlock should contain loop including both LOCK_open and + some of MDL synchronization primitives. Otherwise deadlock would had + already exisited before we have introduced acquiring of LOCK_open in + MDL deadlock detector. + 2) Also in this graph edge going out of LOCK_open node should go to one + of MDL synchronization primitives. Different situation would mean that + we have some non-MDL synchronization primitive besides LOCK_open under + which we try to acquire MDL lock, which is not the case. + 3) Moreover edge coming from LOCK_open should go to MDL_lock::m_rwlock + object and correspond to request for read-lock. It can't be request + for rwlock in MDL_context or mutex in MDL_wait object because they + are terminal (i.e. thread having them locked in exclusive mode won't + wait for any other resource). It can't be request for write-lock on + MDL_lock::m_rwlock as this would mean that we try to acquire metadata + lock under LOCK_open (which is not the case). + 4) Since MDL_lock::m_rwlock is rwlock which prefers readers the only + situation when it can be waited for is when some thread has it + write-locked. + 5) TODO/FIXME: + - Either prove that thread having MDL_lock::m_rwlock write-locked won't + wait for LOCK_open directly or indirectly (see notify_shared_lock()). + - Or change code to hold only read-lock on MDL_lock::m_rwlock during + notify_shared_lock() and thus make MDL_lock::m_rwlock terminal when + write-locked. + */ + if (! (dvisitor->m_table_shares_visited++)) + mysql_mutex_lock(&LOCK_open); + + I_P_List_iterator tables_it(used_tables); + + /* Not strictly necessary ? */ + if (src_ctx->m_wait.get_status() != MDL_wait::EMPTY) + { + result= FALSE; + goto end; + } + + if (dvisitor->enter_node(src_ctx)) + goto end; + + while ((table= tables_it++)) + { + if (dvisitor->inspect_edge(&table->in_use->mdl_context)) + { + goto end_leave_node; + } + } + + tables_it.rewind(); + while ((table= tables_it++)) + { + if (table->in_use->mdl_context.find_deadlock(dvisitor)) + { + goto end_leave_node; + } + } + + result= FALSE; + +end_leave_node: + dvisitor->leave_node(src_ctx); + +end: + if (! (--dvisitor->m_table_shares_visited)) + mysql_mutex_unlock(&LOCK_open); + + return result; +} + + +/** + Wait until old version of table share is removed from TDC. + + @param mdl_context MDL context for thread which is going to wait. + @param abstime Timeout for waiting as absolute time value. + @param deadlock_weight Weight of this wait for deadlock detector. + + @note This method assumes that its caller owns LOCK_open mutex. + This mutex will be unlocked temporarily during its execution. + + @retval FALSE - Success. + @retval TRUE - Error (OOM, deadlock, timeout, etc...). +*/ + +bool TABLE_SHARE::wait_until_flushed(MDL_context *mdl_context, + struct timespec *abstime, + uint deadlock_weight) +{ + Flush_ticket *ticket; + MDL_wait::enum_wait_status wait_status; + + mysql_mutex_assert_owner(&LOCK_open); + + /* + We should enter this method only then share's version is not + up to date and the share is referenced. Otherwise there is + no guarantee that our thread will be waken-up from wait. + */ + DBUG_ASSERT(version != refresh_version && ref_count != 0); + + if (! (ticket= new Flush_ticket(mdl_context, this, deadlock_weight))) + { + mysql_mutex_unlock(&LOCK_open); + return TRUE; + } + + m_flush_tickets.push_front(ticket); + + mdl_context->m_wait.reset_status(); + + mysql_mutex_unlock(&LOCK_open); + + mdl_context->will_wait_for(ticket); + + mdl_context->find_deadlock(); + + wait_status= mdl_context->m_wait.timed_wait(mdl_context->get_thd(), + abstime, TRUE); + + mdl_context->done_waiting_for(); + + mysql_mutex_lock(&LOCK_open); + + m_flush_tickets.remove(ticket); + + /* + If our thread was the last one waiting for table share to be flushed + we can finish destruction of share object by releasing its memory + (share object was allocated on share's own MEM_ROOT). + + In cases when our wait was aborted due KILL statement, deadlock or + timeout share still might be referenced, so we don't free its memory + in this case. Note that we can't rely on checking wait_status to + determine this condition as, for example, timeout can happen even + when there are no references to table share so memory should be + released. + */ + if (m_flush_tickets.is_empty() && ! ref_count) + { + MEM_ROOT mem_root_copy; + memcpy((char*) &mem_root_copy, (char*) &mem_root, sizeof(mem_root)); + free_root(&mem_root_copy, MYF(0)); + } + + delete ticket; + + switch (wait_status) + { + case MDL_wait::GRANTED: + return FALSE; + case MDL_wait::VICTIM: + my_error(ER_LOCK_DEADLOCK, MYF(0)); + return TRUE; + case MDL_wait::TIMEOUT: + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); + return TRUE; + case MDL_wait::KILLED: + return TRUE; + default: + DBUG_ASSERT(0); + return TRUE; + } +} + + /* Create Item_field for each column in the table. diff --git a/sql/table.h b/sql/table.h index 2bf390aee4d..46015f4425a 100644 --- a/sql/table.h +++ b/sql/table.h @@ -45,6 +45,7 @@ class ACL_internal_schema_access; class ACL_internal_table_access; struct TABLE_LIST; class Field; +class Deadlock_detection_visitor; /* Used to identify NESTED_JOIN structures within a join (applicable only to @@ -508,6 +509,45 @@ public: }; +/** + Class representing the fact that some thread waits for table + share to be flushed. Is used to represent information about + such waits in MDL deadlock detector. +*/ + +class Flush_ticket : public Wait_for_edge +{ + MDL_context *m_ctx; + TABLE_SHARE *m_share; + uint m_deadlock_weight; +public: + Flush_ticket(MDL_context *ctx_arg, TABLE_SHARE *share_arg, + uint deadlock_weight_arg) + : m_ctx(ctx_arg), m_share(share_arg), + m_deadlock_weight(deadlock_weight_arg) + {} + + MDL_context *get_ctx() const { return m_ctx; } + + bool find_deadlock(Deadlock_detection_visitor *dvisitor); + + uint get_deadlock_weight() const; + + /** + Pointers for participating in the list of waiters for table share. + */ + Flush_ticket *next_in_share; + Flush_ticket **prev_in_share; +}; + + +typedef I_P_List > + Flush_ticket_list; + + /* This structure is shared between different table objects. There is one instance of table share per one table in the database. @@ -662,6 +702,11 @@ struct TABLE_SHARE /** Instrumentation for this table share. */ PSI_table_share *m_psi; + /** + List of tickets representing threads waiting for the share to be flushed. + */ + Flush_ticket_list m_flush_tickets; + /* Set share's table cache key and update its db and table name appropriately. @@ -837,6 +882,12 @@ struct TABLE_SHARE return (tmp_table == SYSTEM_TMP_TABLE || is_view) ? 0 : table_map_id; } + bool find_deadlock(Flush_ticket *waiting_ticket, + Deadlock_detection_visitor *dvisitor); + + bool wait_until_flushed(MDL_context *mdl_context, + struct timespec *abstime, + uint deadlock_weight); }; -- cgit v1.2.1 From 5e13086bf88eabfbf91dce63332a450ef52f101f Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 30 Jul 2010 11:59:34 +0800 Subject: Bug #34283 mysqlbinlog leaves tmpfile after termination if binlog contains load data infile With statement- or mixed-mode logging, "LOAD DATA INFILE" queries are written to the binlog using special types of log events. When mysqlbinlog reads such events, it re-creates the file in a temporary directory with a generated filename and outputs a "LOAD DATA INFILE" query where the filename is replaced by the generated file. The temporary file is not deleted by mysqlbinlog after termination. To fix the problem, in mixed mode we go to row-based. In SBR, we document it to remind user the tmpfile is left in a temporary directory. mysql-test/suite/binlog/r/binlog_mixed_load_data.result: Test result for BUG#34283. mysql-test/suite/binlog/t/binlog_mixed_load_data.test: Added the test file to verify that 'load data infile...' statement will go to row-based in mixed mode. sql/sql_load.cc: Added code to go to row-based in mixed mode for 'load data infile ...' statement --- sql/sql_load.cc | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'sql') diff --git a/sql/sql_load.cc b/sql/sql_load.cc index a4cf46b35e8..f9386206dce 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -141,6 +141,14 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, bool transactional_table; DBUG_ENTER("mysql_load"); + /* + Bug #34283 + mysqlbinlog leaves tmpfile after termination if binlog contains + load data infile, so in mixed mode we go to row-based for + avoiding the problem. + */ + thd->set_current_stmt_binlog_row_based_if_mixed(); + #ifdef EMBEDDED_LIBRARY read_file_from_client = 0; //server is always in the same process #endif -- cgit v1.2.1 From a9538cacda199fcfd733a191c17cc68569871cd7 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Fri, 30 Jul 2010 09:17:10 -0300 Subject: Bug#54041: MySQL 5.0.92 fails when tests from Connector/C suite run Fix a regression (due to a typo) which caused spurious incorrect argument errors for long data stream parameters if all forms of logging were disabled (binary, general and slow logs). mysql-test/t/mysql_client_test.test: Save the status of the slow_log. sql/sql_prepare.cc: Add a missing logical NOT operator. tests/mysql_client_test.c: Disable all query logs when running C tests. Fixes a omission when, slow log should have been disabled too. Run test case for Bug#54041 with query logs enabled and disabled. --- sql/sql_prepare.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sql') diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index bd152866deb..d6eb90a57be 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -793,7 +793,7 @@ static bool insert_params_with_log(Prepared_statement *stmt, uchar *null_array, type (the types are supplied at execute). Check that the supplied type of placeholder can accept a data stream. */ - else if (!is_param_long_data_type(param)) + else if (! is_param_long_data_type(param)) DBUG_RETURN(1); res= param->query_val_str(&str); if (param->convert_str_value(thd)) @@ -839,7 +839,7 @@ static bool insert_params(Prepared_statement *stmt, uchar *null_array, type (the types are supplied at execute). Check that the supplied type of placeholder can accept a data stream. */ - else if (is_param_long_data_type(param)) + else if (! is_param_long_data_type(param)) DBUG_RETURN(1); if (param->convert_str_value(stmt->thd)) DBUG_RETURN(1); /* out of memory */ -- cgit v1.2.1 From de5029a4586176b9fed06ab96fb3a6e0bbcd8c54 Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Fri, 30 Jul 2010 16:35:06 +0300 Subject: Bug #55188: GROUP BY, GROUP_CONCAT and TEXT - inconsistent results In order to be able to check if the set of the grouping fields in a GROUP BY has changed (and thus to start a new group) the optimizer caches the current values of these fields in a set of Cached_item derived objects. The Cached_item_str, used for caching varchar and TEXT columns, is limited in length by the max_sort_length variable. A String buffer to store the value with an alloced length of either the max length of the string or the value of max_sort_length (whichever is smaller) in Cached_item_str's constructor. Then, at compare time the value of the string to compare to was truncated to the alloced length of the string buffer inside Cached_item_str. This is all fine and valid, but only if you're not assigning values near or equal to the alloced length of this buffer. Because when assigning values like this the alloced length is rounded up and as a result the next set of data will not match the group buffer, thus leading to wrong results because of the changed alloced_length. Fixed by preserving the original maximum length in the Cached_item_str's constructor and using this instead of the alloced_length to limit the string to compare to. Test case added. --- sql/item.h | 1 + sql/item_buff.cc | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'sql') diff --git a/sql/item.h b/sql/item.h index 174995b43e6..57abb43010e 100644 --- a/sql/item.h +++ b/sql/item.h @@ -2740,6 +2740,7 @@ public: class Cached_item_str :public Cached_item { Item *item; + uint32 value_max_length; String value,tmp_value; public: Cached_item_str(THD *thd, Item *arg); diff --git a/sql/item_buff.cc b/sql/item_buff.cc index 2f45d0a17c2..0ac4edb3656 100644 --- a/sql/item_buff.cc +++ b/sql/item_buff.cc @@ -58,7 +58,9 @@ Cached_item::~Cached_item() {} */ Cached_item_str::Cached_item_str(THD *thd, Item *arg) - :item(arg), value(min(arg->max_length, thd->variables.max_sort_length)) + :item(arg), + value_max_length(min(arg->max_length, thd->variables.max_sort_length)), + value(value_max_length) {} bool Cached_item_str::cmp(void) @@ -67,7 +69,7 @@ bool Cached_item_str::cmp(void) bool tmp; if ((res=item->val_str(&tmp_value))) - res->length(min(res->length(), value.alloced_length())); + res->length(min(res->length(), value_max_length)); if (null_value != item->null_value) { if ((null_value= item->null_value)) -- cgit v1.2.1 From 55e60e14fa4eb240194412ee162c0394751da9e1 Mon Sep 17 00:00:00 2001 From: Luis Soares Date: Fri, 30 Jul 2010 14:44:39 +0100 Subject: Revert patch for BUG#34283. Causing lots of test failures in PB2, mostly because existing test result files were not updated. --- sql/sql_load.cc | 8 -------- 1 file changed, 8 deletions(-) (limited to 'sql') diff --git a/sql/sql_load.cc b/sql/sql_load.cc index f9386206dce..a4cf46b35e8 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -141,14 +141,6 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, bool transactional_table; DBUG_ENTER("mysql_load"); - /* - Bug #34283 - mysqlbinlog leaves tmpfile after termination if binlog contains - load data infile, so in mixed mode we go to row-based for - avoiding the problem. - */ - thd->set_current_stmt_binlog_row_based_if_mixed(); - #ifdef EMBEDDED_LIBRARY read_file_from_client = 0; //server is always in the same process #endif -- cgit v1.2.1 From 9899e690f06e275e8c7b2e39dba96b46701fd949 Mon Sep 17 00:00:00 2001 From: Davi Arnaut Date: Fri, 30 Jul 2010 17:33:10 -0300 Subject: Bug#45288: pb2 returns a lot of compilation warnings on linux Fix compiler warnings. mysys/stacktrace.c: Tag unused parameters. sql/sql_lex.cc: Variable becomes unused in non-debug builds. Also, no need to assert the obvious. --- sql/sql_lex.cc | 3 --- 1 file changed, 3 deletions(-) (limited to 'sql') diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 2bff036b1f1..24c51be2512 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1303,8 +1303,6 @@ int MYSQLlex(void *arg, void *yythd) } else { - const char* version_mark= lip->get_ptr() - 1; - DBUG_ASSERT(*version_mark == '!'); /* Patch and skip the conditional comment to avoid it being propagated infinitely (eg. to a slave). @@ -1313,7 +1311,6 @@ int MYSQLlex(void *arg, void *yythd) comment_closed= ! consume_comment(lip, 1); if (! comment_closed) { - DBUG_ASSERT(pcom == version_mark); *pcom= '!'; } /* version allowed to have one level of comment inside. */ -- cgit v1.2.1 From 80aa8824971de3e5524537e30175b2390d0570db Mon Sep 17 00:00:00 2001 From: Gleb Shchepa Date: Sun, 1 Aug 2010 22:12:36 +0400 Subject: Bug #54461: crash with longblob and union or update with subquery Queries may crash, if 1) the GREATEST or the LEAST function has a mixed list of numeric and LONGBLOB arguments and 2) the result of such a function goes through an intermediate temporary table. An Item that references a LONGBLOB field has max_length of UINT_MAX32 == (2^32 - 1). The current implementation of GREATEST/LEAST returns REAL result for a mixed list of numeric and string arguments (that contradicts with the current documentation, this contradiction was discussed and it was decided to update the documentation). The max_length of such a function call was calculated as a maximum of argument max_length values (i.e. UINT_MAX32). That max_length value of UINT_MAX32 was used as a length for the intermediate temporary table Field_double to hold GREATEST/LEAST function result. The Field_double::val_str() method call on that field allocates a String value. Since an allocation of String reserves an additional byte for a zero-termination, the size of String buffer was set to (UINT_MAX32 + 1), that caused an integer overflow: actually, an empty buffer of size 0 was allocated. An initialization of the "first" byte of that zero-size buffer with '\0' caused a crash. The Item_func_min_max::fix_length_and_dec() has been modified to calculate max_length for the REAL result like we do it for arithmetical operators. ****** Bug #54461: crash with longblob and union or update with subquery Queries may crash, if 1) the GREATEST or the LEAST function has a mixed list of numeric and LONGBLOB arguments and 2) the result of such a function goes through an intermediate temporary table. An Item that references a LONGBLOB field has max_length of UINT_MAX32 == (2^32 - 1). The current implementation of GREATEST/LEAST returns REAL result for a mixed list of numeric and string arguments (that contradicts with the current documentation, this contradiction was discussed and it was decided to update the documentation). The max_length of such a function call was calculated as a maximum of argument max_length values (i.e. UINT_MAX32). That max_length value of UINT_MAX32 was used as a length for the intermediate temporary table Field_double to hold GREATEST/LEAST function result. The Field_double::val_str() method call on that field allocates a String value. Since an allocation of String reserves an additional byte for a zero-termination, the size of String buffer was set to (UINT_MAX32 + 1), that caused an integer overflow: actually, an empty buffer of size 0 was allocated. An initialization of the "first" byte of that zero-size buffer with '\0' caused a crash. The Item_func_min_max::fix_length_and_dec() has been modified to calculate max_length for the REAL result like we do it for arithmetical operators. mysql-test/r/func_misc.result: Test case for bug #54461. ****** Test case for bug #54461. mysql-test/t/func_misc.test: Test case for bug #54461. ****** Test case for bug #54461. sql/item_func.cc: Bug #54461: crash with longblob and union or update with subquery The Item_func_min_max::fix_length_and_dec() has been modified to calculate max_length for the REAL result like we do it for arithmetical operators. ****** Bug #54461: crash with longblob and union or update with subquery The Item_func_min_max::fix_length_and_dec() has been modified to calculate max_length for the REAL result like we do it for arithmetical operators. --- sql/item_func.cc | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sql') diff --git a/sql/item_func.cc b/sql/item_func.cc index 1bec4700bff..1b13297c951 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -2243,6 +2243,8 @@ void Item_func_min_max::fix_length_and_dec() max_length= my_decimal_precision_to_length_no_truncation(max_int_part + decimals, decimals, unsigned_flag); + else if (cmp_type == REAL_RESULT) + max_length= float_length(decimals); cached_field_type= agg_field_type(args, arg_count); } -- cgit v1.2.1 From f62e89fade7cda645de80e2996de69e7d980cbdd Mon Sep 17 00:00:00 2001 From: Alfranio Correia Date: Mon, 2 Aug 2010 20:48:56 +0100 Subject: BUG#55625 RBR breaks on failing 'CREATE TABLE' A CREATE...SELECT that fails is written to the binary log if a non-transactional statement is updated. If the logging format is ROW, the CREATE statement and the changes are written to the binary log as distinct events and by consequence the created table is not rolled back in the slave. In this patch, we opted to let the slave goes out of sync by not writting to the binary log the CREATE statement. We do this by simply reseting the binary log's cache. mysql-test/suite/rpl/r/rpl_drop.result: Added a test case. mysql-test/suite/rpl/t/rpl_drop.test: Added a test case. sql/log.cc: Introduced a function to clean up the cache. sql/log.h: Introduced a function to clean up the cache. sql/sql_insert.cc: Cleaned up the binary log cache if a CREATE...SELECT fails. --- sql/log.cc | 13 +++++++++++++ sql/log.h | 3 ++- sql/sql_insert.cc | 11 +++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) (limited to 'sql') diff --git a/sql/log.cc b/sql/log.cc index 614a07e6b63..3f41bf1c929 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -1628,6 +1628,19 @@ static int binlog_rollback(handlerton *hton, THD *thd, bool all) DBUG_RETURN(error); } +/** + Cleanup the cache. + + @param thd The client thread that wants to clean up the cache. +*/ +void MYSQL_BIN_LOG::reset_gathered_updates(THD *thd) +{ + binlog_trx_data *const trx_data= + (binlog_trx_data*) thd_get_ha_data(thd, binlog_hton); + + trx_data->reset(); +} + void MYSQL_BIN_LOG::set_write_error(THD *thd) { DBUG_ENTER("MYSQL_BIN_LOG::set_write_error"); diff --git a/sql/log.h b/sql/log.h index 8d3880d9171..8f1ed7ee90c 100644 --- a/sql/log.h +++ b/sql/log.h @@ -356,10 +356,11 @@ public: /* Use this to start writing a new log file */ void new_file(); + void reset_gathered_updates(THD *thd); bool write(Log_event* event_info); // binary log write bool write(THD *thd, IO_CACHE *cache, Log_event *commit_event, bool incident); - bool write_incident(THD *thd, bool lock); + bool write_incident(THD *thd, bool lock); int write_cache(IO_CACHE *cache, bool lock_log, bool flush_and_sync); void set_write_error(THD *thd); bool check_write_error(THD *thd); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 35c24e7571e..83b1834da0b 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3873,6 +3873,17 @@ void select_create::abort() if (table) { + if (thd->lex->sql_command == SQLCOM_CREATE_TABLE && + thd->current_stmt_binlog_row_based && + !(thd->lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) && + mysql_bin_log.is_open()) + { + /* + This should be removed after BUG#47899. + */ + mysql_bin_log.reset_gathered_updates(thd); + } + table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE); if (!create_info->table_existed) -- cgit v1.2.1 From bcb3170c978f3e5eb049185668de6afa172104e2 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 3 Aug 2010 10:22:19 +0800 Subject: Bug #34283 mysqlbinlog leaves tmpfile after termination if binlog contains load data infile With statement- or mixed-mode logging, "LOAD DATA INFILE" queries are written to the binlog using special types of log events. When mysqlbinlog reads such events, it re-creates the file in a temporary directory with a generated filename and outputs a "LOAD DATA INFILE" query where the filename is replaced by the generated file. The temporary file is not deleted by mysqlbinlog after termination. To fix the problem, in mixed mode we go to row-based. In SBR, we document it to remind user the tmpfile is left in a temporary directory. mysql-test/extra/rpl_tests/rpl_loaddata.test: Updated for Bug#34283 mysql-test/suite/binlog/r/binlog_mixed_load_data.result: Test result for BUG#34283. mysql-test/suite/binlog/t/binlog_killed_simulate.test: Updated for Bug#34283 mysql-test/suite/binlog/t/binlog_mixed_load_data.test: Added the test file to verify that 'load data infile...' statement will go to row-based in mixed mode. mysql-test/suite/binlog/t/binlog_stm_blackhole.test: Updated for Bug#34283 mysql-test/suite/rpl/r/rpl_innodb_mixed_dml.result: Updated for Bug#34283 mysql-test/suite/rpl/t/rpl_loaddata_fatal.test: Updated for Bug#34283 mysql-test/suite/rpl/t/rpl_loaddata_map.test: Updated for Bug#34283 mysql-test/suite/rpl/t/rpl_slave_load_remove_tmpfile.test: Updated for Bug#34283 mysql-test/suite/rpl/t/rpl_stm_log.test: Updated for Bug#34283 sql/sql_load.cc: Added code to go to row-based in mixed mode for 'load data infile ...' statement --- sql/sql_load.cc | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'sql') diff --git a/sql/sql_load.cc b/sql/sql_load.cc index a4cf46b35e8..f9386206dce 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -141,6 +141,14 @@ int mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, bool transactional_table; DBUG_ENTER("mysql_load"); + /* + Bug #34283 + mysqlbinlog leaves tmpfile after termination if binlog contains + load data infile, so in mixed mode we go to row-based for + avoiding the problem. + */ + thd->set_current_stmt_binlog_row_based_if_mixed(); + #ifdef EMBEDDED_LIBRARY read_file_from_client = 0; //server is always in the same process #endif -- cgit v1.2.1 From 5eeb6488cf3973c3821aef10d40ed221985f9190 Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Tue, 3 Aug 2010 19:01:30 +0300 Subject: Bug #42144: plugin_load fails The enum system variables were handled inconsistently as ints, unsigned int and unsigned long on various places. This caused problems on platforms on which sizeof(int) != sizeof(long). Fixed by homogenizing the type of the enum variables to unsigned int, since it's size compatible with the C enum type. Removed the test from the experimental list. --- sql/sql_plugin.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sql') diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 7383d59a1d9..6eed702e5ec 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -3030,10 +3030,10 @@ static int construct_options(MEM_ROOT *mem_root, struct st_plugin_int *tmp, Allocate temporary space for the value of the tristate. This option will have a limited lifetime and is not used beyond server initialization. - GET_ENUM value is an integer. + GET_ENUM value is unsigned integer. */ options[0].value= options[1].value= (uchar **)alloc_root(mem_root, - sizeof(int)); + sizeof(uint)); *((uint*) options[0].value)= *((uint*) options[1].value)= (uint) options[0].def_value; -- cgit v1.2.1 From b1a8b3aa6eb3f8f8c2df218e4eb25e4af710b66a Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Wed, 4 Aug 2010 15:58:09 +0300 Subject: Bug #42144: plugin_load fails Reverted the ulong->uint diff Re-applied the first diff. The original commit message follows: enum plugin system variables are ulong internally, not int. On systems where long is not the same as an int it causes problems. Fixed by correct typecasting. Removed the test from the experimental list. --- sql/sql_plugin.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'sql') diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 6eed702e5ec..a5640f5d80c 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -3030,12 +3030,12 @@ static int construct_options(MEM_ROOT *mem_root, struct st_plugin_int *tmp, Allocate temporary space for the value of the tristate. This option will have a limited lifetime and is not used beyond server initialization. - GET_ENUM value is unsigned integer. + GET_ENUM value is a unsigned long integer. */ options[0].value= options[1].value= (uchar **)alloc_root(mem_root, - sizeof(uint)); - *((uint*) options[0].value)= *((uint*) options[1].value)= - (uint) options[0].def_value; + sizeof(ulong)); + *((ulong*) options[0].value)= *((ulong*) options[1].value)= + (ulong) options[0].def_value; options+= 2; -- cgit v1.2.1 From a91a5ee3bfab12437cd9b8ec3a41598c5e5fe47b Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 4 Aug 2010 20:29:13 +0400 Subject: Cleanup: remove unused declarations from sql_base.h. sql/sql_base.cc: Update a comment to not refer to a non-existing function. sql/sql_base.h: Remove unused declarations (bad merge with the header files split worklog). sql/sql_test.cc: Cleanup: remove unused declarations. --- sql/sql_base.cc | 5 ++--- sql/sql_base.h | 27 --------------------------- sql/sql_test.cc | 4 ++-- 3 files changed, 4 insertions(+), 32 deletions(-) (limited to 'sql') diff --git a/sql/sql_base.cc b/sql/sql_base.cc index e810d5fc091..734f5658e80 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -8647,9 +8647,8 @@ bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use, @param db Name of database @param table_name Name of table - @note Unlike remove_table_from_cache() it assumes that table instances - are already not used by any (other) thread (this should be achieved - by using meta-data locks). + @note It assumes that table instances are already not used by any + (other) thread (this should be achieved by using meta-data locks). */ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, diff --git a/sql/sql_base.h b/sql/sql_base.h index 45f1408e2f5..05401a8cc6d 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -90,28 +90,10 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update, uint lock_flags); bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, Open_table_context *ot_ctx); -bool name_lock_locked_table(THD *thd, TABLE_LIST *tables); -bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in); -TABLE *table_cache_insert_placeholder(THD *thd, const char *key, - uint key_length); -bool lock_table_name_if_not_cached(THD *thd, const char *db, - const char *table_name, TABLE **table); -void detach_merge_children(TABLE *table, bool clear_refs); -bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last, - TABLE_LIST *new_child_list, TABLE_LIST **new_last); -bool reopen_table(TABLE *table); -bool reopen_tables(THD *thd,bool get_locks,bool in_refresh); -void close_data_files_and_morph_locks(THD *thd, const char *db, - const char *table_name); -void close_handle_and_leave_table_as_lock(TABLE *table); bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, uint db_stat, uint prgflag, uint ha_open_flags, TABLE *outparam, TABLE_LIST *table_desc, MEM_ROOT *mem_root); -bool wait_for_tables(THD *thd); -bool table_is_used(TABLE *table, bool wait_for_name_lock); -TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name); -void abort_locked_tables(THD *thd,const char *db, const char *table_name); bool get_key_map_from_key_list(key_map *map, TABLE *table, List *index_list); @@ -190,12 +172,9 @@ bool setup_tables_and_check_access(THD *thd, ulong want_access); bool wait_while_table_is_used(THD *thd, TABLE *table, enum ha_extra_function function); -void unlink_open_table(THD *thd, TABLE *find, bool unlock); void drop_open_table(THD *thd, TABLE *table, const char *db_name, const char *table_name); -void close_all_tables_for_name(THD *thd, TABLE_SHARE *share, - bool remove_from_locked_tables); void update_non_unique_table_error(TABLE_LIST *update, const char *operation, TABLE_LIST *duplicate); @@ -232,8 +211,6 @@ void close_temporary_table(THD *thd, TABLE *table, bool free_share, void close_temporary(TABLE *table, bool free_share, bool delete_table); bool rename_temporary_table(THD* thd, TABLE *table, const char *new_db, const char *table_name); -void mysql_wait_completed_table(ALTER_PARTITION_PARAM_TYPE *lpt, TABLE *my_table); -void remove_db_from_cache(const char *db); bool is_equal(const LEX_STRING *a, const LEX_STRING *b); /* Functions to work with system tables. */ @@ -257,8 +234,6 @@ bool close_cached_connection_tables(THD *thd, bool wait_for_refresh, void close_all_tables_for_name(THD *thd, TABLE_SHARE *share, bool remove_from_locked_tables); OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild); -bool remove_table_from_cache(THD *thd, const char *db, const char *table, - uint flags); void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, const char *db, const char *table_name); bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, @@ -271,12 +246,10 @@ TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db, void mark_tmp_table_for_reuse(TABLE *table); bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists); -extern uint table_cache_count; extern TABLE *unused_tables; extern Item **not_found_item; extern Field *not_found_field; extern Field *view_ref_found; -extern HASH open_cache; extern HASH table_def_cache; /** diff --git a/sql/sql_test.cc b/sql/sql_test.cc index 501c4cf6a94..9d0614f8529 100644 --- a/sql/sql_test.cc +++ b/sql/sql_test.cc @@ -118,7 +118,7 @@ static void print_cached_tables(void) printf("unused_links isn't linked properly\n"); return; } - } while (count++ < table_cache_count && (lnk=lnk->next) != start_link); + } while (count++ < cached_open_tables() && (lnk=lnk->next) != start_link); if (lnk != start_link) { printf("Unused_links aren't connected\n"); @@ -416,7 +416,7 @@ static void display_table_locks(void) void *saved_base; DYNAMIC_ARRAY saved_table_locks; - (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO), table_cache_count + 20,50); + (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO), cached_open_tables() + 20,50); mysql_mutex_lock(&THR_LOCK_lock); for (list= thr_lock_thread_list; list; list= list_rest(list)) { -- cgit v1.2.1 From 0c81dcf332bd9b3e2a68354efab9bf6ea96df2d3 Mon Sep 17 00:00:00 2001 From: Martin Hansson Date: Thu, 5 Aug 2010 12:42:14 +0200 Subject: Bug#54568: create view cause Assertion failed: 0, file .\item_subselect.cc, line 836 IN quantified predicates are never executed directly. They are rather wrapped inside nodes called IN Optimizers (Item_in_optimizer) which take care of the execution. However, this is not done during query preparation. Unfortunately the LIKE predicate pre-evaluates constant right-hand side arguments even during name resolution. Likely this is meant as an optimization. Fixed by not pre-evaluating LIKE arguments in view prepare mode. --- sql/item_cmpfunc.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sql') diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index e91aba63023..fe4616f64d7 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -4606,7 +4606,7 @@ bool Item_func_like::fix_fields(THD *thd, Item **ref) return TRUE; } - if (escape_item->const_item()) + if (escape_item->const_item() && !thd->lex->view_prepare_mode) { /* If we are on execution stage */ String *escape_str= escape_item->val_str(&cmp.value1); -- cgit v1.2.1 From 7b3b8ae154da00cb35c067db62a2df57f0ea978f Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Thu, 5 Aug 2010 15:10:24 +0300 Subject: Addendum to bug #42144 : fixed a wrong type conversation causing plugin tests failures on sparc64. --- sql/sql_plugin.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sql') diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index a5640f5d80c..e0fc88c3068 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -3319,7 +3319,7 @@ static int test_plugin_options(MEM_ROOT *tmp_root, struct st_plugin_int *tmp, Set plugin loading policy from option value. First element in the option list is always the option value. */ - plugin_load_policy= (enum_plugin_load_policy)*(uint*)opts[0].value; + plugin_load_policy= (enum_plugin_load_policy)*(ulong*)opts[0].value; } disable_plugin= (plugin_load_policy == PLUGIN_OFF); -- cgit v1.2.1 From 8d0dc9b58bcb5f1cf13618eebe3fc6f60b8f2926 Mon Sep 17 00:00:00 2001 From: Dmitry Lenev Date: Fri, 6 Aug 2010 15:29:37 +0400 Subject: Part of fix for bug#52044 "FLUSH TABLES WITH READ LOCK and FLUSH TABLES WITH READ LOCK are incompatible" to be pushed as separate patch. Replaced thread state name "Waiting for table", which was used by threads waiting for a metadata lock or table flush, with a set of names which better reflect types of resources being waited for. Also replaced "Table lock" thread state name, which was used by threads waiting on thr_lock.c table level lock, with more elaborate "Waiting for table level lock", to make it more consistent with other thread state names. Updated test cases and their results according to these changes. Fixed sys_vars.query_cache_wlock_invalidate_func test to not to wait for timeout of wait_condition.inc script. mysql-test/r/query_cache.result: Added test coverage for query_cache_wlock_invalidate behavior for implicitly locked tables. mysql-test/suite/sys_vars/r/query_cache_wlock_invalidate_func.result: Fixed sys_vars.query_cache_wlock_invalidate_func test to not to wait for timeout of wait_condition.inc script. Reverted changes to test which introduced timeout and replaced waiting condition with a more appropriate one. Test coverage for query_cache_wlock_invalidate behavior for implicitly locked tables was added to query_cache.test. mysql-test/suite/sys_vars/t/query_cache_wlock_invalidate_func.test: Fixed sys_vars.query_cache_wlock_invalidate_func test to not to wait for timeout of wait_condition.inc script. Reverted changes to test which introduced timeout and replaced waiting condition with a more appropriate one. Test coverage for query_cache_wlock_invalidate behavior for implicitly locked tables was added to query_cache.test. mysql-test/t/query_cache.test: Added test coverage for query_cache_wlock_invalidate behavior for implicitly locked tables. mysys/thr_lock.c: Replaced "Table lock" thread state name, which was used by threads waiting on thr_lock.c table level lock, with more elaborate "Waiting for table level lock", to make it consistent with thread state names which are used while waiting for metadata locks and table flush. sql/mdl.cc: Replaced thread state name "Waiting for table", which was used by threads waiting for a metadata lock or table flush, with a set of names which better reflect types of resources being waited for. To implement this: - Adjusted MDL_wait::timed_wait() to take thread state name as parameter. - Introduced method of MDL_key class which allows to get thread state name to be used while waiting for resource corresponding to the key and changed code to use it. Added array translating namespaces to thread state names as part of this change. sql/mdl.h: To implement this: - Adjusted MDL_wait::timed_wait() to take thread state name as parameter. - Introduced method of MDL_key class which allows to get thread state name to be used while waiting for resource corresponding to the key and changed code to use it. Added array translating namespaces to thread state names as part of this change. sql/sql_base.cc: Replaced thread state name "Waiting for table", which was used by threads waiting for table flush, with a more elaborate "Waiting for table flush". --- sql/mdl.cc | 37 ++++++++++++++++++++++++++++--------- sql/mdl.h | 16 ++++++++++++++-- sql/sql_base.cc | 3 ++- 3 files changed, 44 insertions(+), 12 deletions(-) (limited to 'sql') diff --git a/sql/mdl.cc b/sql/mdl.cc index ca66799baed..5b50b11647c 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -71,6 +71,21 @@ static void init_mdl_psi_keys(void) 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 + belonging to certain namespace. +*/ + +const char *MDL_key::m_namespace_to_wait_state_name[NAMESPACE_END]= +{ + "Waiting for global metadata lock", + "Waiting for schema metadata lock", + "Waiting for table metadata lock", + "Waiting for stored function metadata lock", + "Waiting for stored procedure metadata lock", + NULL +}; + static bool mdl_initialized= 0; @@ -946,17 +961,18 @@ void MDL_wait::reset_status() Wait for the status to be assigned to this wait slot. @param abs_timeout Absolute time after which waiting should stop. - @param set_status_on_tiemout TRUE - If in case of timeout waiting - context should close the wait slot by - sending TIMEOUT to itself. - FALSE - Otherwise. + @param set_status_on_timeout TRUE - If in case of timeout waiting + context should close the wait slot by + sending TIMEOUT to itself. + FALSE - Otherwise. + @param wait_state_name Thread state name to be set for duration of wait. @returns Signal posted. */ MDL_wait::enum_wait_status MDL_wait::timed_wait(THD *thd, struct timespec *abs_timeout, - bool set_status_on_timeout) + bool set_status_on_timeout, const char *wait_state_name) { const char *old_msg; enum_wait_status result; @@ -965,7 +981,7 @@ MDL_wait::timed_wait(THD *thd, struct timespec *abs_timeout, mysql_mutex_lock(&m_LOCK_wait_status); old_msg= thd_enter_cond(thd, &m_COND_wait_status, &m_LOCK_wait_status, - "Waiting for table"); + wait_state_name); while (!m_wait_status && !thd_killed(thd) && wait_result != ETIMEDOUT && wait_result != ETIME) @@ -1746,7 +1762,8 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) while (cmp_timespec(abs_shortwait, abs_timeout) <= 0) { /* abs_timeout is far away. Wait a short while and notify locks. */ - wait_status= m_wait.timed_wait(m_thd, &abs_shortwait, FALSE); + wait_status= m_wait.timed_wait(m_thd, &abs_shortwait, FALSE, + mdl_request->key.get_wait_state_name()); if (wait_status != MDL_wait::EMPTY) break; @@ -1757,10 +1774,12 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout) set_timespec(abs_shortwait, 1); } if (wait_status == MDL_wait::EMPTY) - wait_status= m_wait.timed_wait(m_thd, &abs_timeout, TRUE); + wait_status= m_wait.timed_wait(m_thd, &abs_timeout, TRUE, + mdl_request->key.get_wait_state_name()); } else - wait_status= m_wait.timed_wait(m_thd, &abs_timeout, TRUE); + wait_status= m_wait.timed_wait(m_thd, &abs_timeout, TRUE, + mdl_request->key.get_wait_state_name()); done_waiting_for(); diff --git a/sql/mdl.h b/sql/mdl.h index c8acd69c0f1..3b21e503369 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -184,7 +184,9 @@ public: TABLE, FUNCTION, PROCEDURE, - TRIGGER }; + TRIGGER, + /* This should be the last ! */ + NAMESPACE_END }; const uchar *ptr() const { return (uchar*) m_ptr; } uint length() const { return m_length; } @@ -251,10 +253,20 @@ public: } MDL_key() {} /* To use when part of MDL_request. */ + /** + Get thread state name to be used in case when we have to + wait on resource identified by key. + */ + const char * get_wait_state_name() const + { + return m_namespace_to_wait_state_name[(int)mdl_namespace()]; + } + private: uint16 m_length; uint16 m_db_name_length; char m_ptr[MAX_MDLKEY_LENGTH]; + static const char * m_namespace_to_wait_state_name[NAMESPACE_END]; private: MDL_key(const MDL_key &); /* not implemented */ MDL_key &operator=(const MDL_key &); /* not implemented */ @@ -462,7 +474,7 @@ public: enum_wait_status get_status(); void reset_status(); enum_wait_status timed_wait(THD *thd, struct timespec *abs_timeout, - bool signal_timeout); + bool signal_timeout, const char *wait_state_name); private: /** Condvar which is used for waiting until this context's pending diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 734f5658e80..be0ea9c0815 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -8772,7 +8772,8 @@ tdc_wait_for_old_versions(THD *thd, MDL_request_list *mdl_requests, my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); break; } - old_msg= thd->enter_cond(&COND_refresh, &LOCK_open, "Waiting for table"); + old_msg= thd->enter_cond(&COND_refresh, &LOCK_open, + "Waiting for table flush"); wait_result= mysql_cond_timedwait(&COND_refresh, &LOCK_open, &abstime); /* LOCK_open mutex is unlocked by THD::exit_cond() as side-effect. */ thd->exit_cond(old_msg); -- cgit v1.2.1 From d62bfebc7ede98df28ac75ec0d0880fd07f201db Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Mon, 9 Aug 2010 13:39:59 +0200 Subject: Bug #54106 assert in Protocol::end_statement, INSERT IGNORE ... SELECT ... UNION SELECT ... This assert was triggered by INSERT IGNORE ... SELECT. The assert checks that a statement either sends OK or an error to the client. If the bug was triggered on release builds, it caused OK to be sent to the client instead of the correct error message (in this case ER_FIELD_SPECIFIED_TWICE). The reason the assert was triggered, was that lex->no_error was set to TRUE during JOIN::optimize() because of IGNORE. This causes all errors to be ignored. However, not all errors can be ignored. Some, such as ER_FIELD_SPECIFIED_TWICE will cause the INSERT to fail no matter what. But since lex->no_error was set, the critical errors were ignored, the INSERT failed and neither OK nor the error message was sent to the client. This patch fixes the problem by temporarily turning off lex->no_error in places where errors cannot be ignored during processing of INSERT ... SELECT. Test case added to insert.test. --- sql/sql_insert.cc | 3 +++ sql/sql_select.cc | 2 ++ sql/sql_update.cc | 69 +------------------------------------------------------ 3 files changed, 6 insertions(+), 68 deletions(-) (limited to 'sql') diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 35c24e7571e..eb9e1e5b3af 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -2961,6 +2961,9 @@ select_insert::prepare(List &values, SELECT_LEX_UNIT *u) we are fixing fields from insert list. */ lex->current_select= &lex->select_lex; + + /* Errors during check_insert_fields() should not be ignored. */ + lex->current_select->no_error= FALSE; res= check_insert_fields(thd, table_list, *fields, values, !insert_into_view, &map) || setup_fields(thd, 0, values, MARK_COLUMNS_READ, 0, 0); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 2fc287bbe66..4bb0d3a9610 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -6819,6 +6819,8 @@ bool error_if_full_join(JOIN *join) { if (tab->type == JT_ALL && (!tab->select || !tab->select->quick)) { + /* This error should not be ignored. */ + join->select_lex->no_error= FALSE; my_message(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE, ER(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0)); return(1); diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 17fac877fbc..7da8a68546f 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1199,56 +1199,6 @@ reopen_tables: } -/** - Implementation of the safe update options during UPDATE IGNORE. This syntax - causes an UPDATE statement to ignore all errors. In safe update mode, - however, we must never ignore the ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE. There - is a special hook in my_message_sql that will otherwise delete all errors - when the IGNORE option is specified. - - In the future, all IGNORE handling should be used with this class and all - traces of the hack outlined below should be removed. - - - The parser detects IGNORE option and sets thd->lex->ignore= 1 - - - In JOIN::optimize, if this is set, then - thd->lex->current_select->no_error gets set. - - - In my_message_sql(), if the flag above is set then any error is - unconditionally converted to a warning. - - We are moving in the direction of using Internal_error_handler subclasses - to do all such error tweaking, please continue this effort if new bugs - appear. - */ -class Safe_dml_handler : public Internal_error_handler { - -private: - bool m_handled_error; - -public: - explicit Safe_dml_handler() : m_handled_error(FALSE) {} - - bool handle_error(uint sql_errno, - const char *message, - MYSQL_ERROR::enum_warning_level level, - THD *thd) - { - if (level == MYSQL_ERROR::WARN_LEVEL_ERROR && - sql_errno == ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE) - - { - thd->main_da.set_error_status(thd, sql_errno, message); - m_handled_error= TRUE; - return TRUE; - } - return FALSE; - } - - bool handled_error() { return m_handled_error; } - -}; - /* Setup multi-update handling and call SELECT to do the join */ @@ -1278,11 +1228,6 @@ bool mysql_multi_update(THD *thd, List total_list; - Safe_dml_handler handler; - bool using_handler= thd->options & OPTION_SAFE_UPDATES; - if (using_handler) - thd->push_internal_handler(&handler); - res= mysql_select(thd, &select_lex->ref_pointer_array, table_list, select_lex->with_wild, total_list, @@ -1292,21 +1237,9 @@ bool mysql_multi_update(THD *thd, OPTION_SETUP_TABLES_DONE, result, unit, select_lex); - if (using_handler) - { - Internal_error_handler *top_handler; - top_handler= thd->pop_internal_handler(); - DBUG_ASSERT(&handler == top_handler); - } - DBUG_PRINT("info",("res: %d report_error: %d", res, (int) thd->is_error())); res|= thd->is_error(); - /* - Todo: remove below code and make Safe_dml_handler do error processing - instead. That way we can return the actual error instead of - ER_UNKNOWN_ERROR. - */ - if (unlikely(res) && (!using_handler || !handler.handled_error())) + if (unlikely(res)) { /* If we had a another error reported earlier then this will be ignored */ result->send_error(ER_UNKNOWN_ERROR, ER(ER_UNKNOWN_ERROR)); -- cgit v1.2.1 From 523066987d6150347b3a56d403187312816cab8d Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Mon, 9 Aug 2010 22:33:47 +0400 Subject: A fix for Bug#41158 "DROP TABLE holds LOCK_open during unlink()". Remove acquisition of LOCK_open around file system operations, since such operations are now protected by metadata locks. Rework table discovery algorithm to not require LOCK_open. No new tests added since all MDL locking operations are covered in lock.test and mdl_sync.test, and as long as these tests pass despite the increased concurrency, consistency must be unaffected. mysql-test/t/disabled.def: Disable NDB tests due to Bug#55799. sql/datadict.cc: No longer necessary to protect ha_create_table() with LOCK_open. Serial execution is now ensured by metadata locks. sql/ha_ndbcluster.cc: Do not manipulate with LOCK_open in cluster code. sql/ha_ndbcluster_binlog.cc: Do not manipulate with LOCK_open in cluster code. sql/ha_ndbcluster_binlog.h: Update function signature. sql/handler.cc: Implement ha_check_if_table_exists(). @todo: some engines provide ha_table_exists_in_engine() handlerton call, for those we perhaps shouldn't call ha_discover(), to be more efficient. Since currently it's only NDB, postpone till integration with NDB. sql/handler.h: Declare ha_check_if_table_exists() function. sql/mdl.cc: Remove an obsolete comment. sql/sql_base.cc: Update to a new signature of close_cached_tables(): from now on we always call it without LOCK_open. Update comments. Remove get_table_share_with_create(), we should not attempt to create a table under LOCK_open. Introduce get_table_share_with_discover() instead, which would request a back off action if the table exists in engine. Remove acquisition of LOCK_open for data dictionary operations, such as check_if_table_exists(). Do not use get_table_share_with_create/discover for views, where it's not needed. Make tdc_remove_table() optionally acquire LOCK_open to simplify usage of this function. Use the right mutex in the partitioning code when manipulating with thd->open_tables. sql/sql_base.h: Update signatures of changes functions. sql/sql_insert.cc: Do not wrap quick_rm_table() with LOCK_open acquisition, this is unnecessary. sql/sql_parse.cc: Update to the new calling convention of tdc_remove_table(). Update to the new signature of close_cached_tables(). Update comments. sql/sql_rename.cc: Update to the new calling convention of tdc_remove_table(). Remove acquisition of LOCK_open around filesystem operations. sql/sql_show.cc: Remove get_trigger_table_impl(). Do not acquire LOCK_open for a dirty read of the trigger file. sql/sql_table.cc: Do not acquire LOCK_open for filesystem operations. sql/sql_trigger.cc: Do not require LOCK_open for trigger file I/O. sql/sql_truncate.cc: Update to the new signature of tdc_remove_table(). sql/sql_view.cc: Do not require LOCK_open for view I/O. Use tdc_remove_table() to expel view share. Update comments. sql/sys_vars.cc: Update to the new signature of close_cached_tables(). --- sql/datadict.cc | 2 - sql/ha_ndbcluster.cc | 29 ++---- sql/ha_ndbcluster_binlog.cc | 45 ++------- sql/ha_ndbcluster_binlog.h | 3 +- sql/handler.cc | 28 ++++++ sql/handler.h | 2 + sql/mdl.cc | 8 -- sql/sql_base.cc | 232 ++++++++++++++++++++++---------------------- sql/sql_base.h | 9 +- sql/sql_insert.cc | 2 - sql/sql_parse.cc | 10 +- sql/sql_rename.cc | 19 +--- sql/sql_show.cc | 35 +------ sql/sql_table.cc | 67 +++++-------- sql/sql_trigger.cc | 19 +--- sql/sql_truncate.cc | 4 +- sql/sql_view.cc | 20 +--- sql/sys_vars.cc | 2 +- 18 files changed, 217 insertions(+), 319 deletions(-) (limited to 'sql') diff --git a/sql/datadict.cc b/sql/datadict.cc index 33c3b6bc700..7eea977fd5d 100644 --- a/sql/datadict.cc +++ b/sql/datadict.cc @@ -152,9 +152,7 @@ bool dd_recreate_table(THD *thd, const char *db, const char *table_name) build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0); /* Attempt to reconstruct the table. */ - mysql_mutex_lock(&LOCK_open); error= ha_create_table(thd, path, db, table_name, &create_info, TRUE); - mysql_mutex_unlock(&LOCK_open); DBUG_RETURN(error); } diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc index d4a98265c49..0ec2e21056e 100644 --- a/sql/ha_ndbcluster.cc +++ b/sql/ha_ndbcluster.cc @@ -680,7 +680,7 @@ int ha_ndbcluster::ndb_err(NdbTransaction *trans) bzero((char*) &table_list,sizeof(table_list)); table_list.db= m_dbname; table_list.alias= table_list.table_name= m_tabname; - close_cached_tables(thd, &table_list, FALSE, FALSE); + close_cached_tables(thd, &table_list, FALSE); break; } default: @@ -5702,7 +5702,7 @@ int ha_ndbcluster::create(const char *name, m_table->getObjectVersion(), (is_truncate) ? SOT_TRUNCATE_TABLE : SOT_CREATE_TABLE, - 0, 0, 1); + 0, 0); break; } } @@ -6143,7 +6143,7 @@ int ha_ndbcluster::rename_table(const char *from, const char *to) old_dbname, m_tabname, ndb_table_id, ndb_table_version, SOT_RENAME_TABLE, - m_dbname, new_tabname, 1); + m_dbname, new_tabname); } // If we are moving tables between databases, we need to recreate @@ -6337,7 +6337,7 @@ retry_temporary_error1: thd->query(), thd->query_length(), share->db, share->table_name, ndb_table_id, ndb_table_version, - SOT_DROP_TABLE, 0, 0, 1); + SOT_DROP_TABLE, 0, 0); } else if (table_dropped && share && share->op) /* ndbcluster_log_schema_op will do a force GCP */ @@ -7019,7 +7019,6 @@ int ndbcluster_drop_database_impl(const char *path) while ((tabname=it++)) { tablename_to_filename(tabname, tmp, FN_REFLEN - (tmp - full_path)-1); - mysql_mutex_lock(&LOCK_open); if (ha_ndbcluster::delete_table(0, ndb, full_path, dbname, tabname)) { const NdbError err= dict->getNdbError(); @@ -7029,7 +7028,6 @@ int ndbcluster_drop_database_impl(const char *path) ret= ndb_to_mysql_error(&err); } } - mysql_mutex_unlock(&LOCK_open); } DBUG_RETURN(ret); } @@ -7056,7 +7054,7 @@ static void ndbcluster_drop_database(handlerton *hton, char *path) ha_ndbcluster::set_dbname(path, db); ndbcluster_log_schema_op(thd, 0, thd->query(), thd->query_length(), - db, "", 0, 0, SOT_DROP_DB, 0, 0, 0); + db, "", 0, 0, SOT_DROP_DB, 0, 0); #endif DBUG_VOID_RETURN; } @@ -7181,7 +7179,6 @@ int ndbcluster_find_all_files(THD *thd) my_free(data); my_free(pack_data); - mysql_mutex_lock(&LOCK_open); if (discover) { /* ToDo 4.1 database needs to be created if missing */ @@ -7199,7 +7196,6 @@ int ndbcluster_find_all_files(THD *thd) TRUE); } #endif - mysql_mutex_unlock(&LOCK_open); } } while (unhandled && retries); @@ -7292,19 +7288,16 @@ int ndbcluster_find_files(handlerton *hton, THD *thd, file_name->str, reg_ext, 0); if (my_access(name, F_OK)) { - mysql_mutex_lock(&LOCK_open); DBUG_PRINT("info", ("Table %s listed and need discovery", file_name->str)); if (ndb_create_table_from_engine(thd, db, file_name->str)) { - mysql_mutex_unlock(&LOCK_open); push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_TABLE_EXISTS_ERROR, "Discover of table %s.%s failed", db, file_name->str); continue; } - mysql_mutex_unlock(&LOCK_open); } DBUG_PRINT("info", ("%s existed in NDB _and_ on disk ", file_name->str)); file_on_disk= TRUE; @@ -7361,10 +7354,8 @@ int ndbcluster_find_files(handlerton *hton, THD *thd, file_name_str= (char*)my_hash_element(&ok_tables, i); end= end1 + tablename_to_filename(file_name_str, end1, sizeof(name) - (end1 - name)); - mysql_mutex_lock(&LOCK_open); ndbcluster_create_binlog_setup(ndb, name, end-name, db, file_name_str, TRUE); - mysql_mutex_unlock(&LOCK_open); } } #endif @@ -7426,7 +7417,6 @@ int ndbcluster_find_files(handlerton *hton, THD *thd, } /* Lock mutex before creating .FRM files. */ - mysql_mutex_lock(&LOCK_open); /* Create new files. */ List_iterator_fast it2(create_list); while ((file_name_str=it2++)) @@ -7441,8 +7431,6 @@ int ndbcluster_find_files(handlerton *hton, THD *thd, } } - mysql_mutex_unlock(&LOCK_open); - my_hash_free(&ok_tables); my_hash_free(&ndb_tables); @@ -8452,8 +8440,7 @@ int handle_trailing_share(NDB_SHARE *share) bzero((char*) &table_list,sizeof(table_list)); table_list.db= share->db; table_list.alias= table_list.table_name= share->table_name; - mysql_mutex_assert_owner(&LOCK_open); - close_cached_tables(thd, &table_list, TRUE, FALSE); + close_cached_tables(thd, &table_list, FALSE); mysql_mutex_lock(&ndbcluster_mutex); /* ndb_share reference temporary free */ @@ -10612,13 +10599,13 @@ int ndbcluster_alter_tablespace(handlerton *hton, thd->query(), thd->query_length(), "", alter_info->tablespace_name, 0, 0, - SOT_TABLESPACE, 0, 0, 0); + SOT_TABLESPACE, 0, 0); else ndbcluster_log_schema_op(thd, 0, thd->query(), thd->query_length(), "", alter_info->logfile_group_name, 0, 0, - SOT_LOGFILE_GROUP, 0, 0, 0); + SOT_LOGFILE_GROUP, 0, 0); #endif DBUG_RETURN(FALSE); diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc index 26fdb8e1425..72e3092f9a8 100644 --- a/sql/ha_ndbcluster_binlog.cc +++ b/sql/ha_ndbcluster_binlog.cc @@ -360,7 +360,6 @@ ndbcluster_binlog_open_table(THD *thd, NDB_SHARE *share, int error; DBUG_ENTER("ndbcluster_binlog_open_table"); - mysql_mutex_assert_owner(&LOCK_open); init_tmp_table_share(thd, table_share, share->db, 0, share->table_name, share->key); if ((error= open_table_def(thd, table_share, 0))) @@ -376,7 +375,9 @@ ndbcluster_binlog_open_table(THD *thd, NDB_SHARE *share, free_table_share(table_share); DBUG_RETURN(error); } + mysql_mutex_lock(&LOCK_open); assign_new_table_id(table_share); + mysql_mutex_unlock(&LOCK_open); if (!reopen) { @@ -625,7 +626,7 @@ ndbcluster_binlog_log_query(handlerton *hton, THD *thd, enum_binlog_command binl { ndbcluster_log_schema_op(thd, 0, query, query_length, db, table_name, 0, 0, type, - 0, 0, 0); + 0, 0); } DBUG_VOID_RETURN; } @@ -908,9 +909,7 @@ int ndbcluster_setup_binlog_table_shares(THD *thd) if (!ndb_schema_share && ndbcluster_check_ndb_schema_share() == 0) { - mysql_mutex_lock(&LOCK_open); ndb_create_table_from_engine(thd, NDB_REP_DB, NDB_SCHEMA_TABLE); - mysql_mutex_unlock(&LOCK_open); if (!ndb_schema_share) { ndbcluster_create_schema_table(thd); @@ -922,9 +921,7 @@ int ndbcluster_setup_binlog_table_shares(THD *thd) if (!ndb_apply_status_share && ndbcluster_check_ndb_apply_status_share() == 0) { - mysql_mutex_lock(&LOCK_open); ndb_create_table_from_engine(thd, NDB_REP_DB, NDB_APPLY_TABLE); - mysql_mutex_unlock(&LOCK_open); if (!ndb_apply_status_share) { ndbcluster_create_ndb_apply_status_table(thd); @@ -934,12 +931,10 @@ int ndbcluster_setup_binlog_table_shares(THD *thd) } if (!ndbcluster_find_all_files(thd)) { - mysql_mutex_lock(&LOCK_open); ndb_binlog_tables_inited= TRUE; if (opt_ndb_extra_logging) sql_print_information("NDB Binlog: ndb tables writable"); - close_cached_tables(NULL, NULL, TRUE, FALSE); - mysql_mutex_unlock(&LOCK_open); + close_cached_tables(NULL, NULL, FALSE); /* Signal injector thread that all is setup */ mysql_cond_signal(&injector_cond); } @@ -1276,8 +1271,7 @@ int ndbcluster_log_schema_op(THD *thd, NDB_SHARE *share, uint32 ndb_table_id, uint32 ndb_table_version, enum SCHEMA_OP_TYPE type, - const char *new_db, const char *new_table_name, - int have_lock_open) + const char *new_db, const char *new_table_name) { DBUG_ENTER("ndbcluster_log_schema_op"); Thd_ndb *thd_ndb= get_thd_ndb(thd); @@ -1580,11 +1574,6 @@ end: int max_timeout= DEFAULT_SYNC_TIMEOUT; mysql_mutex_lock(&ndb_schema_object->mutex); - if (have_lock_open) - { - mysql_mutex_assert_owner(&LOCK_open); - mysql_mutex_unlock(&LOCK_open); - } while (1) { struct timespec abstime; @@ -1640,10 +1629,6 @@ end: "distributing", ndb_schema_object->key); } } - if (have_lock_open) - { - mysql_mutex_lock(&LOCK_open); - } mysql_mutex_unlock(&ndb_schema_object->mutex); } @@ -1726,7 +1711,6 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp, { DBUG_DUMP("frm", (uchar*) altered_table->getFrmData(), altered_table->getFrmLength()); - mysql_mutex_lock(&LOCK_open); Ndb_table_guard ndbtab_g(dict, tabname); const NDBTAB *old= ndbtab_g.get_table(); if (!old && @@ -1752,7 +1736,7 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp, bzero((char*) &table_list,sizeof(table_list)); table_list.db= (char *)dbname; table_list.alias= table_list.table_name= (char *)tabname; - close_cached_tables(thd, &table_list, TRUE, FALSE); + close_cached_tables(thd, &table_list, FALSE); if ((error= ndbcluster_binlog_open_table(thd, share, table_share, table, 1))) @@ -1763,8 +1747,6 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp, table_share= share->table_share; dbname= table_share->db.str; tabname= table_share->table_name.str; - - mysql_mutex_unlock(&LOCK_open); } my_free(data); my_free(pack_data); @@ -1858,7 +1840,7 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp, bzero((char*) &table_list,sizeof(table_list)); table_list.db= (char *)dbname; table_list.alias= table_list.table_name= (char *)tabname; - close_cached_tables(thd, &table_list, FALSE, FALSE); + close_cached_tables(thd, &table_list, FALSE); /* ndb_share reference create free */ DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u", share->key, share->use_count)); @@ -1979,7 +1961,7 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb, bzero((char*) &table_list,sizeof(table_list)); table_list.db= schema->db; table_list.alias= table_list.table_name= schema->name; - close_cached_tables(thd, &table_list, FALSE, FALSE); + close_cached_tables(thd, &table_list, FALSE); } /* ndb_share reference temporary free */ if (share) @@ -1991,7 +1973,6 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb, } // fall through case SOT_CREATE_TABLE: - mysql_mutex_lock(&LOCK_open); if (ndbcluster_check_if_local_table(schema->db, schema->name)) { DBUG_PRINT("info", ("NDB Binlog: Skipping locally defined table '%s.%s'", @@ -2005,7 +1986,6 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb, { print_could_not_discover_error(thd, schema); } - mysql_mutex_unlock(&LOCK_open); log_query= 1; break; case SOT_DROP_DB: @@ -2096,7 +2076,7 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb, mysql_mutex_unlock(&ndb_schema_share_mutex); /* end protect ndb_schema_share */ - close_cached_tables(NULL, NULL, FALSE, FALSE); + close_cached_tables(NULL, NULL, FALSE); // fall through case NDBEVENT::TE_ALTER: ndb_handle_schema_change(thd, ndb, pOp, tmp_share); @@ -2253,7 +2233,7 @@ ndb_binlog_thread_handle_schema_event_post_epoch(THD *thd, bzero((char*) &table_list,sizeof(table_list)); table_list.db= schema->db; table_list.alias= table_list.table_name= schema->name; - close_cached_tables(thd, &table_list, FALSE, FALSE); + close_cached_tables(thd, &table_list, FALSE); } if (schema_type != SOT_ALTER_TABLE) break; @@ -2274,7 +2254,6 @@ ndb_binlog_thread_handle_schema_event_post_epoch(THD *thd, free_share(&share); share= 0; } - mysql_mutex_lock(&LOCK_open); if (ndbcluster_check_if_local_table(schema->db, schema->name)) { DBUG_PRINT("info", ("NDB Binlog: Skipping locally defined table '%s.%s'", @@ -2288,7 +2267,6 @@ ndb_binlog_thread_handle_schema_event_post_epoch(THD *thd, { print_could_not_discover_error(thd, schema); } - mysql_mutex_unlock(&LOCK_open); } break; default: @@ -3154,8 +3132,6 @@ ndbcluster_handle_drop_table(Ndb *ndb, const char *event_name, #ifdef SYNC_DROP_ thd->proc_info= "Syncing ndb table schema operation and binlog"; mysql_mutex_lock(&share->mutex); - mysql_mutex_assert_owner(&LOCK_open); - mysql_mutex_unlock(&LOCK_open); int max_timeout= DEFAULT_SYNC_TIMEOUT; while (share->op) { @@ -3181,7 +3157,6 @@ ndbcluster_handle_drop_table(Ndb *ndb, const char *event_name, type_str, share->key); } } - mysql_mutex_lock(&LOCK_open); mysql_mutex_unlock(&share->mutex); #else mysql_mutex_lock(&share->mutex); diff --git a/sql/ha_ndbcluster_binlog.h b/sql/ha_ndbcluster_binlog.h index 4d2a49588b4..5dbcf0fa43f 100644 --- a/sql/ha_ndbcluster_binlog.h +++ b/sql/ha_ndbcluster_binlog.h @@ -158,8 +158,7 @@ int ndbcluster_log_schema_op(THD *thd, NDB_SHARE *share, uint32 ndb_table_version, enum SCHEMA_OP_TYPE type, const char *new_db, - const char *new_table_name, - int have_lock_open); + const char *new_table_name); int ndbcluster_handle_drop_table(Ndb *ndb, const char *event_name, NDB_SHARE *share, const char *type_str); diff --git a/sql/handler.cc b/sql/handler.cc index 9893b3cac16..567dbe6ea49 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -3670,6 +3670,34 @@ int ha_create_table_from_engine(THD* thd, const char *db, const char *name) DBUG_RETURN(error != 0); } + +/** + Try to find a table in a storage engine. + + @param db Normalized table schema name + @param name Normalized table name. + @param[out] exists Only valid if the function succeeded. + + @retval TRUE An error is found + @retval FALSE Success, check *exists +*/ + +bool +ha_check_if_table_exists(THD* thd, const char *db, const char *name, + bool *exists) +{ + uchar *frmblob= NULL; + size_t frmlen; + DBUG_ENTER("ha_check_if_table_exists"); + + *exists= ! ha_discover(thd, db, name, &frmblob, &frmlen); + if (*exists) + my_free(frmblob); + + DBUG_RETURN(FALSE); +} + + void st_ha_check_opt::init() { flags= sql_flags= 0; diff --git a/sql/handler.h b/sql/handler.h index cad97c1f751..4229769290d 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -2120,6 +2120,8 @@ bool ha_show_status(THD *thd, handlerton *db_type, enum ha_stat_type stat); /* discovery */ int ha_create_table_from_engine(THD* thd, const char *db, const char *name); +bool ha_check_if_table_exists(THD* thd, const char *db, const char *name, + bool *exists); int ha_discover(THD* thd, const char* dbname, const char* name, uchar** frmblob, size_t* frmlen); int ha_find_files(THD *thd,const char *db,const char *path, diff --git a/sql/mdl.cc b/sql/mdl.cc index 5b50b11647c..1178428e983 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -494,14 +494,6 @@ mdl_locks_key(const uchar *record, size_t *length, the associated condition variable: LOCK_mdl and COND_mdl. These locking primitives are implementation details of the MDL subsystem and are private to it. - - Note, that even though the new implementation adds acquisition - of a new global mutex to the execution flow of almost every SQL - statement, the design capitalizes on that to later save on - look ups in the table definition cache. This leads to reduced - contention overall and on LOCK_open in particular. - Please see the description of MDL_context::acquire_lock() - for details. */ void mdl_init() diff --git a/sql/sql_base.cc b/sql/sql_base.cc index be0ea9c0815..9aea868a197 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -314,8 +314,6 @@ void table_def_start_shutdown(void) if (table_def_inited) { mysql_mutex_lock(&LOCK_open); - /* Free all cached but unused TABLEs and TABLE_SHAREs first. */ - close_cached_tables(NULL, NULL, TRUE, FALSE); /* Ensure that TABLE and TABLE_SHARE objects which are created for tables that are open during process of plugins' shutdown are @@ -324,6 +322,8 @@ void table_def_start_shutdown(void) */ table_def_shutdown_in_progress= TRUE; mysql_mutex_unlock(&LOCK_open); + /* Free all cached but unused TABLEs and TABLE_SHAREs. */ + close_cached_tables(NULL, NULL, FALSE); } } @@ -516,10 +516,10 @@ TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key, } /* - We assign a new table id under the protection of the LOCK_open and - the share's own mutex. We do this insted of creating a new mutex + We assign a new table id under the protection of the LOCK_open. + We do this instead of creating a new mutex and using it for the sole purpose of serializing accesses to a - static variable, we assign the table id here. We assign it to the + static variable, we assign the table id here. We assign it to the share before inserting it into the table_def_cache to be really sure that it cannot be read from the cache without having a table id assigned. @@ -547,7 +547,7 @@ TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key, DBUG_RETURN(share); found: - /* + /* We found an existing table definition. Return it if we didn't get an error when reading the table definition from file. */ @@ -589,21 +589,21 @@ found: } -/* +/** Get a table share. If it didn't exist, try creating it from engine - For arguments and return values, see get_table_from_share() + For arguments and return values, see get_table_share() */ -static TABLE_SHARE -*get_table_share_with_create(THD *thd, TABLE_LIST *table_list, - char *key, uint key_length, - uint db_flags, int *error, - my_hash_value_type hash_value) +static TABLE_SHARE * +get_table_share_with_discover(THD *thd, TABLE_LIST *table_list, + char *key, uint key_length, + uint db_flags, int *error, + my_hash_value_type hash_value) { TABLE_SHARE *share; - int tmp; + bool exists; DBUG_ENTER("get_table_share_with_create"); share= get_table_share(thd, table_list, key, key_length, db_flags, error, @@ -617,10 +617,15 @@ static TABLE_SHARE from the pre-locking list. In this case we still need to try auto-discover before returning a NULL share. + Or, we're inside SHOW CREATE VIEW, which + also installs a silencer for ER_NO_SUCH_TABLE error. + If share is NULL and the error is ER_NO_SUCH_TABLE, this is - the same as above, only that the error was not silenced by - pre-locking. Once again, we need to try to auto-discover - the share. + the same as above, only that the error was not silenced by + pre-locking or SHOW CREATE VIEW. + + In both these cases it won't harm to try to discover the + table. Finally, if share is still NULL, it's a real error and we need to abort. @@ -628,20 +633,25 @@ static TABLE_SHARE @todo Rework alternative ways to deal with ER_NO_SUCH TABLE. */ if (share || (thd->is_error() && thd->stmt_da->sql_errno() != ER_NO_SUCH_TABLE)) - DBUG_RETURN(share); + *error= 0; + /* Table didn't exist. Check if some engine can provide it */ - tmp= ha_create_table_from_engine(thd, table_list->db, - table_list->table_name); - if (tmp < 0) + if (ha_check_if_table_exists(thd, table_list->db, table_list->table_name, + &exists)) + { + thd->clear_error(); + /* Conventionally, the storage engine API does not report errors. */ + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + } + else if (! exists) { /* No such table in any engine. Hide "Table doesn't exist" errors if the table belongs to a view. The check for thd->is_error() is necessary to not push an - unwanted error in case of pre-locking, which silences - "no such table" errors. + unwanted error in case the error was already silenced. @todo Rework the alternative ways to deal with ER_NO_SUCH TABLE. */ if (thd->is_error()) @@ -659,27 +669,16 @@ static TABLE_SHARE view->view_db.str, view->view_name.str); } } - DBUG_RETURN(0); } - if (tmp) + else { - /* Give right error message */ thd->clear_error(); - DBUG_PRINT("error", ("Discovery of %s/%s failed", table_list->db, - table_list->table_name)); - my_printf_error(ER_UNKNOWN_ERROR, - "Failed to open '%-.64s', error while " - "unpacking from engine", - MYF(0), table_list->table_name); - DBUG_RETURN(0); + *error= 7; /* Run auto-discover. */ } - /* Table existed in engine. Let's open it */ - thd->warning_info->clear_warning_info(thd->query_id); - thd->clear_error(); // Clear error message - DBUG_RETURN(get_table_share(thd, table_list, key, key_length, - db_flags, error, hash_value)); + DBUG_RETURN(NULL); } + /** Mark that we are not using table share anymore. @@ -926,7 +925,6 @@ static void kill_delayed_threads_for_table(TABLE_SHARE *share) @param thd Thread context @param tables List of tables to remove from the cache - @param have_lock If LOCK_open is locked @param wait_for_refresh Wait for a impending flush @note THD can be NULL, but then wait_for_refresh must be FALSE @@ -940,16 +938,14 @@ static void kill_delayed_threads_for_table(TABLE_SHARE *share) lock taken by thread trying to obtain global read lock. */ -bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, - bool wait_for_refresh) +bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool wait_for_refresh) { bool result= FALSE; bool found= TRUE; DBUG_ENTER("close_cached_tables"); DBUG_ASSERT(thd || (!wait_for_refresh && !tables)); - if (!have_lock) - mysql_mutex_lock(&LOCK_open); + mysql_mutex_lock(&LOCK_open); if (!tables) { refresh_version++; // Force close of open tables @@ -978,7 +974,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, kill_delayed_threads_for_table(share); /* tdc_remove_table() also sets TABLE_SHARE::version to 0. */ tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, table->db, - table->table_name); + table->table_name, TRUE); found=1; } } @@ -986,15 +982,11 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, wait_for_refresh=0; // Nothing to wait for } - if (!have_lock) - mysql_mutex_unlock(&LOCK_open); + mysql_mutex_unlock(&LOCK_open); if (!wait_for_refresh) DBUG_RETURN(result); - /* Code below assume that LOCK_open is released. */ - DBUG_ASSERT(!have_lock); - if (thd->locked_tables_mode) { /* @@ -1108,7 +1100,7 @@ err_with_reopen: */ bool close_cached_connection_tables(THD *thd, bool if_wait_for_refresh, - LEX_STRING *connection, bool have_lock) + LEX_STRING *connection) { uint idx; TABLE_LIST tmp, *tables= NULL; @@ -1118,8 +1110,7 @@ bool close_cached_connection_tables(THD *thd, bool if_wait_for_refresh, bzero(&tmp, sizeof(TABLE_LIST)); - if (!have_lock) - mysql_mutex_lock(&LOCK_open); + mysql_mutex_lock(&LOCK_open); for (idx= 0; idx < table_def_cache.records; idx++) { @@ -1147,12 +1138,10 @@ bool close_cached_connection_tables(THD *thd, bool if_wait_for_refresh, tables= (TABLE_LIST *) memdup_root(thd->mem_root, (char*)&tmp, sizeof(TABLE_LIST)); } + mysql_mutex_unlock(&LOCK_open); if (tables) - result= close_cached_tables(thd, tables, TRUE, FALSE); - - if (!have_lock) - mysql_mutex_unlock(&LOCK_open); + result= close_cached_tables(thd, tables, FALSE); if (if_wait_for_refresh) { @@ -1355,9 +1344,8 @@ close_all_tables_for_name(THD *thd, TABLE_SHARE *share, } } /* Remove the table share from the cache. */ - mysql_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db, table_name); - mysql_mutex_unlock(&LOCK_open); + 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. @@ -2201,10 +2189,9 @@ bool wait_while_table_is_used(THD *thd, TABLE *table, table->mdl_ticket, thd->variables.lock_wait_timeout)) DBUG_RETURN(TRUE); - mysql_mutex_lock(&LOCK_open); tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN, - table->s->db.str, table->s->table_name.str); - mysql_mutex_unlock(&LOCK_open); + table->s->db.str, table->s->table_name.str, + FALSE); /* extra() call must come only after all instances above are closed */ (void) table->file->extra(function); DBUG_RETURN(FALSE); @@ -2245,9 +2232,8 @@ void drop_open_table(THD *thd, TABLE *table, const char *db_name, table->file->extra(HA_EXTRA_PREPARE_FOR_DROP); close_thread_table(thd, &thd->open_tables); /* Remove the table share from the table cache. */ - mysql_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db_name, table_name); - mysql_mutex_unlock(&LOCK_open); + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db_name, table_name, + FALSE); /* Remove the table from the storage engine and rm the .frm. */ quick_rm_table(table_type, db_name, table_name, 0); } @@ -2279,16 +2265,20 @@ void drop_open_table(THD *thd, TABLE *table, const char *db_name, bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists) { char path[FN_REFLEN + 1]; - int rc= 0; + TABLE_SHARE *share; DBUG_ENTER("check_if_table_exists"); - mysql_mutex_assert_not_owner(&LOCK_open); - *exists= TRUE; + DBUG_ASSERT(thd->mdl_context. + is_lock_owner(MDL_key::TABLE, table->db, + table->table_name, MDL_SHARED)); + mysql_mutex_lock(&LOCK_open); + share= get_cached_table_share(table->db, table->table_name); + mysql_mutex_unlock(&LOCK_open); - if (get_cached_table_share(table->db, table->table_name)) + if (share) goto end; build_table_filename(path, sizeof(path) - 1, table->db, table->table_name, @@ -2298,24 +2288,14 @@ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists) goto end; /* .FRM file doesn't exist. Check if some engine can provide it. */ - - rc= ha_create_table_from_engine(thd, table->db, table->table_name); - - if (rc < 0) - { - /* Table does not exists in engines as well. */ - *exists= FALSE; - rc= 0; - } - else if (rc) + if (ha_check_if_table_exists(thd, table->db, table->table_name, exists)) { - my_printf_error(ER_UNKNOWN_ERROR, "Failed to open '%-.64s', error while " + my_printf_error(ER_OUT_OF_RESOURCES, "Failed to open '%-.64s', error while " "unpacking from engine", MYF(0), table->table_name); + DBUG_RETURN(TRUE); } - end: - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(test(rc)); + DBUG_RETURN(FALSE); } @@ -2812,11 +2792,25 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, mysql_mutex_lock(&LOCK_open); - if (!(share= get_table_share_with_create(thd, table_list, key, - key_length, OPEN_VIEW, - &error, - hash_value))) - goto err_unlock2; + if (!(share= get_table_share_with_discover(thd, table_list, key, + key_length, OPEN_VIEW, + &error, + hash_value))) + { + mysql_mutex_unlock(&LOCK_open); + /* + If thd->is_error() is not set, we either need discover + (error == 7), or the error was silenced by the prelocking + handler (error == 0), in which case we should skip this + table. + */ + if (error == 7 && !thd->is_error()) + { + (void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER, + table_list); + } + DBUG_RETURN(TRUE); + } if (share->is_view) { @@ -3010,7 +3004,6 @@ err_lock: mysql_mutex_lock(&LOCK_open); err_unlock: release_table_share(share); -err_unlock2: mysql_mutex_unlock(&LOCK_open); DBUG_RETURN(TRUE); @@ -3633,10 +3626,10 @@ bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, cache_key_length); mysql_mutex_lock(&LOCK_open); - if (!(share= get_table_share_with_create(thd, table_list, cache_key, - cache_key_length, - OPEN_VIEW, &error, - hash_value))) + if (!(share= get_table_share(thd, table_list, cache_key, + cache_key_length, + OPEN_VIEW, &error, + hash_value))) goto err; if (share->is_view && @@ -3738,10 +3731,10 @@ static bool auto_repair_table(THD *thd, TABLE_LIST *table_list) cache_key_length); mysql_mutex_lock(&LOCK_open); - if (!(share= get_table_share_with_create(thd, table_list, cache_key, - cache_key_length, - OPEN_VIEW, ¬_used, - hash_value))) + if (!(share= get_table_share(thd, table_list, cache_key, + cache_key_length, + OPEN_VIEW, ¬_used, + hash_value))) goto end_unlock; if (share->is_view) @@ -3786,7 +3779,8 @@ static bool auto_repair_table(THD *thd, TABLE_LIST *table_list) release_table_share(share); /* Remove the repaired share from the table cache. */ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, - table_list->db, table_list->table_name); + table_list->db, table_list->table_name, + TRUE); end_unlock: mysql_mutex_unlock(&LOCK_open); return result; @@ -3908,12 +3902,10 @@ recover_from_failed_open(THD *thd) MYSQL_OPEN_SKIP_TEMPORARY))) break; - mysql_mutex_lock(&LOCK_open); tdc_remove_table(thd, TDC_RT_REMOVE_ALL, m_failed_table->db, - m_failed_table->table_name); + m_failed_table->table_name, FALSE); ha_create_table_from_engine(thd, m_failed_table->db, m_failed_table->table_name); - mysql_mutex_unlock(&LOCK_open); thd->warning_info->clear_warning_info(thd->query_id); thd->clear_error(); // Clear error message @@ -3927,10 +3919,8 @@ recover_from_failed_open(THD *thd) MYSQL_OPEN_SKIP_TEMPORARY))) break; - mysql_mutex_lock(&LOCK_open); tdc_remove_table(thd, TDC_RT_REMOVE_ALL, m_failed_table->db, - m_failed_table->table_name); - mysql_mutex_unlock(&LOCK_open); + m_failed_table->table_name, FALSE); result= auto_repair_table(thd, m_failed_table); thd->mdl_context.release_transactional_locks(); @@ -8541,7 +8531,7 @@ void tdc_flush_unused_tables() mysql_mutex_lock(&LOCK_open); while (unused_tables) free_cache_entry(unused_tables); - (void) mysql_mutex_unlock(&LOCK_open); + mysql_mutex_unlock(&LOCK_open); } @@ -8646,20 +8636,25 @@ bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use, remove TABLE_SHARE). @param db Name of database @param table_name Name of table + @param has_lock If TRUE, LOCK_open is already acquired @note It assumes that table instances are already not used by any (other) thread (this should be achieved by using meta-data locks). */ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, - const char *db, const char *table_name) + const char *db, const char *table_name, + bool has_lock) { char key[MAX_DBKEY_LENGTH]; uint key_length; TABLE *table; TABLE_SHARE *share; - mysql_mutex_assert_owner(&LOCK_open); + if (! has_lock) + mysql_mutex_lock(&LOCK_open); + else + mysql_mutex_assert_owner(&LOCK_open); DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED || thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name, @@ -8700,6 +8695,9 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, else (void) my_hash_delete(&table_def_cache, (uchar*) share); } + + if (! has_lock) + mysql_mutex_unlock(&LOCK_open); } @@ -8904,11 +8902,6 @@ static int alter_close_tables(ALTER_PARTITION_PARAM_TYPE *lpt) THD *thd= lpt->thd; TABLE *table; DBUG_ENTER("alter_close_tables"); - /* - We must keep LOCK_open while manipulating with thd->open_tables. - Another thread may be working on it. - */ - mysql_mutex_lock(&LOCK_open); /* We can safely remove locks for all tables with the same name: later they will all be closed anyway in @@ -8919,9 +8912,20 @@ static int alter_close_tables(ALTER_PARTITION_PARAM_TYPE *lpt) if (!strcmp(table->s->table_name.str, share->table_name.str) && !strcmp(table->s->db.str, share->db.str)) { + /* + No need to take LOCK_thd_data to protect mysql_lock_remove(), + since mysql_lock_abort_for_thread() only aborts waiting + locks, and our lock is already granted. + */ mysql_lock_remove(thd, thd->lock, table); + /* + Protect members of thd->open_tables concurrently used + in mysql_notify_thread_having_shared_lock(). + */ + mysql_mutex_lock(&thd->LOCK_thd_data); table->file->close(); table->db_stat= 0; // Mark file closed + mysql_mutex_unlock(&thd->LOCK_thd_data); /* Ensure that we won't end up with a crippled table instance in the table cache if an error occurs before we reach @@ -8930,10 +8934,10 @@ static int alter_close_tables(ALTER_PARTITION_PARAM_TYPE *lpt) */ tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, table->s->db.str, - table->s->table_name.str); + table->s->table_name.str, + FALSE); } } - mysql_mutex_unlock(&LOCK_open); DBUG_RETURN(0); } diff --git a/sql/sql_base.h b/sql/sql_base.h index 05401a8cc6d..379aa67f203 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -226,16 +226,15 @@ TABLE *open_performance_schema_table(THD *thd, TABLE_LIST *one_table, Open_tables_state *backup); void close_performance_schema_table(THD *thd, Open_tables_state *backup); -bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, - bool wait_for_refresh); +bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool wait_for_refresh); bool close_cached_connection_tables(THD *thd, bool wait_for_refresh, - LEX_STRING *connect_string, - bool have_lock = FALSE); + LEX_STRING *connect_string); void close_all_tables_for_name(THD *thd, TABLE_SHARE *share, bool remove_from_locked_tables); OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild); void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, - const char *db, const char *table_name); + const char *db, const char *table_name, + bool has_lock); bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias, char *cache_key, uint cache_key_length, MEM_ROOT *mem_root, uint flags); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index a0d347f48de..ce4535307c8 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3600,11 +3600,9 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, */ if (open_table(thd, create_table, thd->mem_root, &ot_ctx)) { - mysql_mutex_lock(&LOCK_open); quick_rm_table(create_info->db_type, create_table->db, table_case_name(create_info, create_table->table_name), 0); - mysql_mutex_unlock(&LOCK_open); } else table= create_table->table; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2ef8e9761b1..ba2c9d07845 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1789,11 +1789,9 @@ static bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables) table_list= table_list->next_global) { /* Remove the table from cache. */ - mysql_mutex_lock(&LOCK_open); tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_list->db, - table_list->table_name); - mysql_mutex_unlock(&LOCK_open); + table_list->table_name, FALSE); /* Skip views and temporary tables. */ table_list->required_type= FRMTYPE_TABLE; /* Don't try to flush views. */ @@ -3414,7 +3412,7 @@ end_with_restore_list: /* So that DROP TEMPORARY TABLE gets to binlog at commit/rollback */ thd->variables.option_bits|= OPTION_KEEP_LOG; } - /* DDL and binlog write order protected by LOCK_open */ + /* DDL and binlog write order are protected by metadata locks. */ res= mysql_rm_table(thd, first_table, lex->drop_if_exists, lex->drop_temporary); } @@ -6854,7 +6852,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, tmp_write_to_binlog= 0; if (thd->global_read_lock.lock_global_read_lock(thd)) return 1; // Killed - if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ? + if (close_cached_tables(thd, tables, (options & REFRESH_FAST) ? FALSE : TRUE)) result= 1; @@ -6894,7 +6892,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, } } - if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ? + if (close_cached_tables(thd, tables, (options & REFRESH_FAST) ? FALSE : TRUE)) result= 1; } diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc index 301b22bd70e..f0b53abcb03 100644 --- a/sql/sql_rename.cc +++ b/sql/sql_rename.cc @@ -147,13 +147,15 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) MYSQL_OPEN_SKIP_TEMPORARY)) goto err; - mysql_mutex_lock(&LOCK_open); - for (ren_table= table_list; ren_table; ren_table= ren_table->next_local) tdc_remove_table(thd, TDC_RT_REMOVE_ALL, ren_table->db, - ren_table->table_name); + ren_table->table_name, FALSE); error=0; + /* + An exclusive lock on table names is satisfactory to ensure + no other thread accesses this table. + */ if ((ren_table=rename_tables(thd,table_list,0))) { /* Rename didn't succeed; rename back the tables in reverse order */ @@ -175,17 +177,6 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) error= 1; } - /* - An exclusive lock on table names is satisfactory to ensure - no other thread accesses this table. - However, NDB assumes that handler::rename_tables is called under - LOCK_open. And it indeed is, from ALTER TABLE. - TODO: remove this limitation. - We still should unlock LOCK_open as early as possible, to provide - higher concurrency - query_cache_invalidate can take minutes to - complete. - */ - mysql_mutex_unlock(&LOCK_open); if (!silent && !error) { diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 4d58db2e36c..139568bb9d5 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -7657,7 +7657,7 @@ static bool show_create_trigger_impl(THD *thd, */ static -TABLE_LIST *get_trigger_table_impl(THD *thd, const sp_name *trg_name) +TABLE_LIST *get_trigger_table(THD *thd, const sp_name *trg_name) { char trn_path_buff[FN_REFLEN]; LEX_STRING trn_path= { trn_path_buff, 0 }; @@ -7694,39 +7694,6 @@ TABLE_LIST *get_trigger_table_impl(THD *thd, const sp_name *trg_name) return table; } -/** - Read TRN and TRG files to obtain base table name for the specified - trigger name and construct TABE_LIST object for the base table. Acquire - LOCK_open when doing this. - - @param thd Thread context. - @param trg_name Trigger name. - - @return TABLE_LIST object corresponding to the base table. -*/ - -static -TABLE_LIST *get_trigger_table(THD *thd, const sp_name *trg_name) -{ - /* Acquire LOCK_open (stop the server). */ - - mysql_mutex_lock(&LOCK_open); - - /* - Load base table name from the TRN-file and create TABLE_LIST object. - */ - - TABLE_LIST *lst= get_trigger_table_impl(thd, trg_name); - - /* Release LOCK_open (continue the server). */ - - mysql_mutex_unlock(&LOCK_open); - - /* That's it. */ - - return lst; -} - /** SHOW CREATE TRIGGER high-level implementation. diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 02a874ce62f..3f777411103 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1723,7 +1723,6 @@ bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags) completing this we write a new phase to the log entry that will deactivate it. */ - mysql_mutex_lock(&LOCK_open); if (mysql_file_delete(key_file_frm, frm_name, MYF(MY_WME)) || #ifdef WITH_PARTITION_STORAGE_ENGINE lpt->table->file->ha_create_handler_files(path, shadow_path, @@ -1779,7 +1778,6 @@ bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags) #endif err: - mysql_mutex_unlock(&LOCK_open); #ifdef WITH_PARTITION_STORAGE_ENGINE deactivate_ddl_log_entry(part_info->frm_log_entry->entry_pos); part_info->frm_log_entry= NULL; @@ -1955,10 +1953,11 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, if (lock_table_names(thd, tables, NULL, thd->variables.lock_wait_timeout, MYSQL_OPEN_SKIP_TEMPORARY)) DBUG_RETURN(1); - mysql_mutex_lock(&LOCK_open); for (table= tables; table; table= table->next_local) - tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name); - mysql_mutex_unlock(&LOCK_open); + { + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name, + FALSE); + } } else { @@ -2104,14 +2103,9 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, table->internal_tmp_table ? FN_IS_TMP : 0); } - /* - TODO: Investigate what should be done to remove this lock completely. - Is exclusive meta-data lock enough ? - */ DEBUG_SYNC(thd, "rm_table_part2_before_delete_table"); DBUG_EXECUTE_IF("sleep_before_part2_delete_table", my_sleep(100000);); - mysql_mutex_lock(&LOCK_open); if (drop_temporary || ((access(path, F_OK) && ha_create_table_from_engine(thd, db, alias)) || @@ -2131,8 +2125,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, char *end; /* Cannot use the db_type from the table, since that might have changed - while waiting for the exclusive name lock. We are under LOCK_open, - so reading from the frm-file is safe. + while waiting for the exclusive name lock. */ if (frm_db_type == DB_TYPE_UNKNOWN) { @@ -2173,7 +2166,6 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, error|= new_error; } } - mysql_mutex_unlock(&LOCK_open); if (error) { if (wrong_tables.length()) @@ -4053,7 +4045,6 @@ bool mysql_create_table_no_lock(THD *thd, goto err; } - mysql_mutex_lock(&LOCK_open); if (!internal_tmp_table && !(create_info->options & HA_LEX_CREATE_TMP_TABLE)) { if (!access(path,F_OK)) @@ -4061,7 +4052,7 @@ bool mysql_create_table_no_lock(THD *thd, if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) goto warn; my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name); - goto unlock_and_end; + goto err; } /* We don't assert here, but check the result, because the table could be @@ -4071,11 +4062,14 @@ bool mysql_create_table_no_lock(THD *thd, Then she could create the table. This case is pretty obscure and therefore we don't introduce a new error message only for it. */ + mysql_mutex_lock(&LOCK_open); if (get_cached_table_share(db, table_name)) { + mysql_mutex_unlock(&LOCK_open); my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name); - goto unlock_and_end; + goto err; } + mysql_mutex_unlock(&LOCK_open); } /* @@ -4083,7 +4077,7 @@ bool mysql_create_table_no_lock(THD *thd, exist in any storage engine. In such a case it should be discovered and the error ER_TABLE_EXISTS_ERROR be returned unless user specified CREATE TABLE IF EXISTS - The LOCK_open mutex has been locked to make sure no + An exclusive metadata lock ensures that no one else is attempting to discover the table. Since it's not on disk as a frm file, no one could be using it! */ @@ -4104,12 +4098,12 @@ bool mysql_create_table_no_lock(THD *thd, if (create_if_not_exists) goto warn; my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name); - goto unlock_and_end; + goto err; break; default: DBUG_PRINT("info", ("error: %u from storage engine", retcode)); my_error(retcode, MYF(0),table_name); - goto unlock_and_end; + goto err; } } @@ -4142,7 +4136,7 @@ bool mysql_create_table_no_lock(THD *thd, if (test_if_data_home_dir(dirpath)) { my_error(ER_WRONG_ARGUMENTS, MYF(0), "DATA DIRECTORY"); - goto unlock_and_end; + goto err; } } if (create_info->index_file_name) @@ -4151,7 +4145,7 @@ bool mysql_create_table_no_lock(THD *thd, if (test_if_data_home_dir(dirpath)) { my_error(ER_WRONG_ARGUMENTS, MYF(0), "INDEX DIRECTORY"); - goto unlock_and_end; + goto err; } } } @@ -4159,7 +4153,7 @@ bool mysql_create_table_no_lock(THD *thd, #ifdef WITH_PARTITION_STORAGE_ENGINE if (check_partition_dirs(thd->lex->part_info)) { - goto unlock_and_end; + goto err; } #endif /* WITH_PARTITION_STORAGE_ENGINE */ @@ -4182,7 +4176,7 @@ bool mysql_create_table_no_lock(THD *thd, if (rea_create_table(thd, path, db, table_name, create_info, alter_info->create_list, key_count, key_info_buffer, file)) - goto unlock_and_end; + goto err; if (create_info->options & HA_LEX_CREATE_TMP_TABLE) { @@ -4190,15 +4184,12 @@ bool mysql_create_table_no_lock(THD *thd, if (!(open_temporary_table(thd, path, db, table_name, 1))) { (void) rm_temporary_table(create_info->db_type, path); - goto unlock_and_end; + goto err; } thd->thread_specific_used= TRUE; } error= FALSE; -unlock_and_end: - mysql_mutex_unlock(&LOCK_open); - err: thd_proc_info(thd, "After create"); delete file; @@ -4210,7 +4201,7 @@ warn: ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), alias); create_info->table_existed= 1; // Mark that table existed - goto unlock_and_end; + goto err; } @@ -4459,20 +4450,19 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length); mysql_mutex_lock(&LOCK_open); - if (!(share= (get_table_share(thd, table_list, key, key_length, 0, - &error, hash_value)))) - { - mysql_mutex_unlock(&LOCK_open); + share= get_table_share(thd, table_list, key, key_length, 0, + &error, hash_value); + mysql_mutex_unlock(&LOCK_open); + if (share == NULL) DBUG_RETURN(0); // Can't open frm file - } if (open_table_from_share(thd, share, "", 0, 0, 0, &tmp_table, FALSE)) { + mysql_mutex_lock(&LOCK_open); release_table_share(share); mysql_mutex_unlock(&LOCK_open); DBUG_RETURN(0); // Out of memory } - mysql_mutex_unlock(&LOCK_open); table= &tmp_table; } @@ -5136,10 +5126,8 @@ send_result_message: } else if (open_for_modify || fatal_error) { - mysql_mutex_lock(&LOCK_open); tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, - table->db, table->table_name); - mysql_mutex_unlock(&LOCK_open); + table->db, table->table_name, FALSE); /* May be something modified. Consequently, we have to invalidate the query cache. @@ -6775,7 +6763,6 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, else { *fn_ext(new_name)=0; - mysql_mutex_lock(&LOCK_open); if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias, 0)) error= -1; else if (Table_triggers_list::change_table_name(thd, db, table_name, @@ -6785,7 +6772,6 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, table_name, 0); error= -1; } - mysql_mutex_unlock(&LOCK_open); } } @@ -7404,7 +7390,6 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, /* This type cannot happen in regular ALTER. */ new_db_type= old_db_type= NULL; } - mysql_mutex_lock(&LOCK_open); if (mysql_rename_table(old_db_type, db, table_name, db, old_name, FN_TO_IS_TMP)) { @@ -7431,8 +7416,6 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, if (! error) (void) quick_rm_table(old_db_type, db, old_name, FN_IS_TMP); - mysql_mutex_unlock(&LOCK_open); - if (error) { /* This shouldn't happen. But let us play it safe. */ diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index a5664b00287..b81461e8371 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -394,9 +394,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) /* 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. (Acquiring - LOCK_open is not enough because global read lock is held without holding - LOCK_open). + 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)) @@ -516,11 +514,9 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) goto end; } - mysql_mutex_lock(&LOCK_open); result= (create ? table->triggers->create_trigger(thd, tables, &stmt_query): table->triggers->drop_trigger(thd, tables, &stmt_query)); - mysql_mutex_unlock(&LOCK_open); if (result) goto end; @@ -1680,9 +1676,6 @@ bool add_table_for_trigger(THD *thd, @param db schema for table @param name name for table - @note - The calling thread should hold the LOCK_open mutex; - @retval False success @retval @@ -1912,14 +1905,10 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db, /* This method interfaces the mysql server code protected by - either LOCK_open mutex or with an exclusive metadata lock. - In the future, only an exclusive metadata lock will be enough. + an exclusive metadata lock. */ -#ifndef DBUG_OFF - if (thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, old_table, - MDL_EXCLUSIVE)) - mysql_mutex_assert_owner(&LOCK_open); -#endif + DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, old_table, + MDL_EXCLUSIVE)); DBUG_ASSERT(my_strcasecmp(table_alias_charset, db, new_db) || my_strcasecmp(table_alias_charset, old_table, new_table)); diff --git a/sql/sql_truncate.cc b/sql/sql_truncate.cc index ee5c707cd69..38e32082388 100644 --- a/sql/sql_truncate.cc +++ b/sql/sql_truncate.cc @@ -310,10 +310,8 @@ static bool open_and_lock_table_for_truncate(THD *thd, TABLE_LIST *table_ref, upgrade_shared_lock_to_exclusive(table_ref->mdl_request.ticket, timeout)) DBUG_RETURN(TRUE); - mysql_mutex_lock(&LOCK_open); tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_ref->db, - table_ref->table_name); - mysql_mutex_unlock(&LOCK_open); + table_ref->table_name, FALSE); } } else diff --git a/sql/sql_view.cc b/sql/sql_view.cc index be13349b5a1..b6671d9096b 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -658,7 +658,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, goto err; } - mysql_mutex_lock(&LOCK_open); res= mysql_register_view(thd, view, mode); if (mysql_bin_log.is_open()) @@ -705,7 +704,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, res= TRUE; } - mysql_mutex_unlock(&LOCK_open); if (mode != VIEW_CREATE_NEW) query_cache_invalidate3(thd, view, 0); thd->global_read_lock.start_waiting_global_read_lock(thd); @@ -1656,10 +1654,8 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) MYSQL_OPEN_SKIP_TEMPORARY)) DBUG_RETURN(TRUE); - mysql_mutex_lock(&LOCK_open); for (view= views; view; view= view->next_local) { - TABLE_SHARE *share; frm_type_enum type= FRMTYPE_ERROR; build_table_filename(path, sizeof(path) - 1, view->db, view->table_name, reg_ext, 0); @@ -1698,16 +1694,12 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) some_views_deleted= TRUE; /* - For a view, there is only one table_share object which should never - be used outside of LOCK_open + For a view, there is a TABLE_SHARE object, but its + ref_count never goes above 1. Remove it from the table + definition cache, in case the view was cached. */ - if ((share= get_cached_table_share(view->db, view->table_name))) - { - DBUG_ASSERT(share->ref_count == 0); - share->ref_count++; - share->version= 0; - release_table_share(share); - } + tdc_remove_table(thd, TDC_RT_REMOVE_ALL, view->db, view->table_name, + FALSE); query_cache_invalidate3(thd, view, 0); sp_cache_invalidate(); } @@ -1732,8 +1724,6 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode) something_wrong= 1; } - mysql_mutex_unlock(&LOCK_open); - if (something_wrong) { DBUG_RETURN(TRUE); diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 320e6d9253e..2285065aa13 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -1492,7 +1492,7 @@ static bool fix_read_only(sys_var *self, THD *thd, enum_var_type type) can cause to wait on a read lock, it's required for the client application to unlock everything, and acceptable for the server to wait on all locks. */ - if ((result= close_cached_tables(thd, NULL, FALSE, TRUE))) + if ((result= close_cached_tables(thd, NULL, TRUE))) goto end_with_read_lock; if ((result= thd->global_read_lock.make_global_read_lock_block_commit(thd))) -- cgit v1.2.1 From cff7f022d8d940584f4be1f831b40fda4cbd4ec2 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Tue, 10 Aug 2010 13:16:44 +0200 Subject: Followup for Bug #54360 Deadlock DROP/ALTER/CREATE DATABASE with open HANDLER This patch changes the code for table renames to not drop metadata locks. Since table renames are done as a part of ALTER DATABASE ... UPGRADE, dropping metadata locks in the middle of execution can result in wrong binlog order since it means that no locks are held when the binlog is written to. The RENAME TABLE statement is unafffected since it auto commits and therefore already drops metadata locks at the end of execution. This patch also reverts the regression test for Bug#48940 back to its original version. The test was temporarily changed due to the issue mentioned above. --- sql/sql_rename.cc | 2 -- 1 file changed, 2 deletions(-) (limited to 'sql') diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc index f0b53abcb03..97f8e46d052 100644 --- a/sql/sql_rename.cc +++ b/sql/sql_rename.cc @@ -188,8 +188,6 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent) if (!error) query_cache_invalidate3(thd, table_list, 0); - thd->mdl_context.release_transactional_locks(); - err: thd->global_read_lock.start_waiting_global_read_lock(thd); DBUG_RETURN(error || binlog_error); -- cgit v1.2.1 From 64cc044e33632b6805e13bc04cbaf7e079c66d32 Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Tue, 10 Aug 2010 14:12:11 +0200 Subject: Post-commit fix for Bug#41158 A label statement needs to be followed by at least one primary expression. If built without WITH_PARTITION_STORAGE_ENGINE set, the block would be empty. Added ';' as a dummy statement to fix it. --- sql/sql_table.cc | 1 + 1 file changed, 1 insertion(+) (limited to 'sql') diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 3f777411103..5e1133fc6d8 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1783,6 +1783,7 @@ err: part_info->frm_log_entry= NULL; (void) sync_ddl_log(); #endif + ; } end: -- cgit v1.2.1 From 265a6edd23cfdb69c6ac072bf01887f7aed1168c Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Wed, 11 Aug 2010 01:12:01 +0400 Subject: A pre-requisite patch for the fix for Bug#52044. Implement a few simple asserts in my_rwlock_t locks. include/my_pthread.h: Declare two simple assert functions. include/mysql/psi/mysql_thread.h: Add wrappers for new assert functions. mysys/thr_rwlock.c: Add asserts. sql/sql_base.cc: Silence a compiler warning for the case when SAFE_MUTEX is not ON. --- sql/sql_base.cc | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sql') diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 9aea868a197..a4b66220dd1 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -8654,7 +8654,9 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, if (! has_lock) mysql_mutex_lock(&LOCK_open); else + { mysql_mutex_assert_owner(&LOCK_open); + } DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED || thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name, -- cgit v1.2.1 From fdf855e2ef324efbbfaa083c4b5f9bb14ff4d2ff Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 12 Aug 2010 19:29:41 +0400 Subject: A follow up patch for WL#5000: add a test case and a comment for the case when a connection issuing FLUSH TABLES WITH READ LOCK has an open handler. --- sql/sql_parse.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'sql') diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 6305d2b4140..b526db2b5b2 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1750,6 +1750,17 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident, If a temporary table with such name exists, it's ignored: if there is a base table, it's used, otherwise ER_NO_SUCH_TABLE is returned. + + Implicit commit + --------------- + This statement causes an implicit commit before and + after it. + + HANDLER SQL + ----------- + If this connection has HANDLERs open against + some of the tables being FLUSHed, these handlers + are implicitly flushed (lose their position). */ static bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables) -- cgit v1.2.1 From 82759db0cc67bfb1454c5f7cf1c5aaa8f8559404 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Thu, 12 Aug 2010 20:44:41 +0400 Subject: Remove dead code. sql/sql_base.h: Remove an unused parameter. sql/sql_servers.cc: Remove an unused parameter. --- sql/sql_base.cc | 14 ++------------ sql/sql_base.h | 3 +-- sql/sql_servers.cc | 4 ++-- 3 files changed, 5 insertions(+), 16 deletions(-) (limited to 'sql') diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 33d47c0659e..d00da4be05e 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1150,13 +1150,12 @@ err_with_reopen: } -/* +/** Close all tables which match specified connection string or if specified string is NULL, then any table with a connection string. */ -bool close_cached_connection_tables(THD *thd, bool if_wait_for_refresh, - LEX_STRING *connection) +bool close_cached_connection_tables(THD *thd, LEX_STRING *connection) { uint idx; TABLE_LIST tmp, *tables= NULL; @@ -1199,15 +1198,6 @@ bool close_cached_connection_tables(THD *thd, bool if_wait_for_refresh, if (tables) result= close_cached_tables(thd, tables, FALSE, LONG_TIMEOUT); - if (if_wait_for_refresh) - { - mysql_mutex_lock(&thd->mysys_var->mutex); - thd->mysys_var->current_mutex= 0; - thd->mysys_var->current_cond= 0; - thd->proc_info=0; - mysql_mutex_unlock(&thd->mysys_var->mutex); - } - DBUG_RETURN(result); } diff --git a/sql/sql_base.h b/sql/sql_base.h index e59b20957d2..8462ef3d2aa 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -229,8 +229,7 @@ void close_performance_schema_table(THD *thd, Open_tables_state *backup); bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool wait_for_refresh, ulong timeout); -bool close_cached_connection_tables(THD *thd, bool wait_for_refresh, - LEX_STRING *connect_string); +bool close_cached_connection_tables(THD *thd, LEX_STRING *connect_string); void close_all_tables_for_name(THD *thd, TABLE_SHARE *share, bool remove_from_locked_tables); OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild); diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc index cfbf8e96719..bc845ed2cdd 100644 --- a/sql/sql_servers.cc +++ b/sql/sql_servers.cc @@ -628,7 +628,7 @@ int drop_server(THD *thd, LEX_SERVER_OPTIONS *server_options) /* close the servers table before we call closed_cached_connection_tables */ close_mysql_tables(thd); - if (close_cached_connection_tables(thd, TRUE, &name)) + if (close_cached_connection_tables(thd, &name)) { push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, "Server connection in use"); @@ -1057,7 +1057,7 @@ int alter_server(THD *thd, LEX_SERVER_OPTIONS *server_options) /* close the servers table before we call closed_cached_connection_tables */ close_mysql_tables(thd); - if (close_cached_connection_tables(thd, FALSE, &name)) + if (close_cached_connection_tables(thd, &name)) { push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, "Server connection in use"); -- cgit v1.2.1 From daf0e6b725ec0866ee79d02db26ab4b04954151f Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Fri, 13 Aug 2010 09:50:25 +0200 Subject: Bug#53676: Unexpected errors and possible table corruption on ADD PARTITION and LOCK TABLE Bug#53770: Server crash at handler.cc:2076 on LOAD DATA after timed out COALESCE PARTITION 5.5 fix for: Bug#51042: REORGANIZE PARTITION can leave table in an inconsistent state in case of crash Needs to be back-ported to 5.1 5.5 fix for: Bug#50418: DROP PARTITION does not interact with transactions Main problem was non-persistent operations done before meta-data lock was taken (53770+53676). And 53676 needed to keep the table/partitions opened and locked while copying the data to the new partitions. Also added thorough tests to spot some additional bugs in the ddl_log code, which could result in bad state between the .frm and partitions. Collapsed patch, includes all fixes required from the reviewers. mysql-test/r/partition_innodb.result: updated result with new test mysql-test/suite/parts/inc/partition_crash.inc: crash test include file mysql-test/suite/parts/inc/partition_crash_add.inc: test all states in fast_alter_partition_table ADD PARTITION branch mysql-test/suite/parts/inc/partition_crash_change.inc: test all states in fast_alter_partition_table CHANGE PARTITION branch mysql-test/suite/parts/inc/partition_crash_drop.inc: test all states in fast_alter_partition_table DROP PARTITION branch mysql-test/suite/parts/inc/partition_fail.inc: recovery test including injecting errors mysql-test/suite/parts/inc/partition_fail_add.inc: test all states in fast_alter_partition_table ADD PARTITION branch mysql-test/suite/parts/inc/partition_fail_change.inc: test all states in fast_alter_partition_table CHANGE PARTITION branch mysql-test/suite/parts/inc/partition_fail_drop.inc: test all states in fast_alter_partition_table DROP PARTITION branch mysql-test/suite/parts/inc/partition_mgm_crash.inc: include file that runs all crash and failure injection tests. mysql-test/suite/parts/r/partition_debug_innodb.result: new test result file mysql-test/suite/parts/r/partition_debug_myisam.result: new test result file mysql-test/suite/parts/r/partition_special_innodb.result: updated result mysql-test/suite/parts/r/partition_special_myisam.result: updated result mysql-test/suite/parts/t/partition_debug_innodb-master.opt: opt file for using with crashing tests of partitioned innodb mysql-test/suite/parts/t/partition_debug_innodb.test: partitioned innodb test that require debug builds mysql-test/suite/parts/t/partition_debug_myisam-master.opt: opt file for using with crashing tests of partitioned myisam mysql-test/suite/parts/t/partition_debug_myisam.test: partitioned myisam test that require debug builds mysql-test/suite/parts/t/partition_special_innodb-master.opt: added innodb-file-per-table to easier verify partition status on disk mysql-test/suite/parts/t/partition_special_innodb.test: added test case mysql-test/suite/parts/t/partition_special_myisam.test: added test case mysql-test/t/partition_innodb.test: added test case sql/sql_base.cc: Moved alter_close_tables to sql_partition.cc sql/sql_base.h: removed some non existing and duplicated functions. sql/sql_partition.cc: fast_alter_partition_table: Spletted abort_and_upgrad_lock_and_close_table to its parts (wait_while_table_is_used and alter_close_tables) and always have wait_while_table_is_used before any persistent operations (including logs, which will be executed on failure) and alter_close_tables after create/read/write operations and before drop operations. moved alter_close_tables here from sql_base.cc Added error injections for better test coverage. write_log_final_change_partition: fixed a log_entry linking bug (delete_frm was not linked to change/drop partition) and drop partition must be executed before change partition (change partition can rename a partition to an old name, like REORG p1 INTO (p1,p2). write_log_add_change_partition: need to use drop_frm first, and relinking that entry and reusing its execute entry. sql/sql_table.cc: added initialization of next_active_log_entry. sql/table.h: removed a duplicate declaration. --- sql/sql_base.cc | 81 -------------- sql/sql_base.h | 8 -- sql/sql_partition.cc | 307 +++++++++++++++++++++++++++++++++++++-------------- sql/sql_table.cc | 1 + sql/table.h | 1 - 5 files changed, 227 insertions(+), 171 deletions(-) (limited to 'sql') diff --git a/sql/sql_base.cc b/sql/sql_base.cc index e810d5fc091..e091c26592e 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -8889,87 +8889,6 @@ bool is_equal(const LEX_STRING *a, const LEX_STRING *b) } -/* - Unlock and close table before renaming and dropping partitions - SYNOPSIS - alter_close_tables() - lpt Struct carrying parameters - RETURN VALUES - 0 -*/ - -static int alter_close_tables(ALTER_PARTITION_PARAM_TYPE *lpt) -{ - TABLE_SHARE *share= lpt->table->s; - THD *thd= lpt->thd; - TABLE *table; - DBUG_ENTER("alter_close_tables"); - /* - We must keep LOCK_open while manipulating with thd->open_tables. - Another thread may be working on it. - */ - mysql_mutex_lock(&LOCK_open); - /* - We can safely remove locks for all tables with the same name: - later they will all be closed anyway in - alter_partition_lock_handling(). - */ - for (table= thd->open_tables; table ; table= table->next) - { - if (!strcmp(table->s->table_name.str, share->table_name.str) && - !strcmp(table->s->db.str, share->db.str)) - { - mysql_lock_remove(thd, thd->lock, table); - table->file->close(); - table->db_stat= 0; // Mark file closed - /* - Ensure that we won't end up with a crippled table instance - in the table cache if an error occurs before we reach - alter_partition_lock_handling() and the table is closed - by close_thread_tables() instead. - */ - tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, - table->s->db.str, - table->s->table_name.str); - } - } - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(0); -} - - -/* - SYNOPSIS - abort_and_upgrade_lock_and_close_table() - lpt Parameter passing struct - All parameters passed through the ALTER_PARTITION_PARAM_TYPE object - RETURN VALUE - 0 - DESCRIPTION - Remember old lock level (for possible downgrade later on), abort all - waiting threads and ensure that all keeping locks currently are - completed such that we own the lock exclusively and no other interaction - is ongoing. Close the table and hold the name lock. - - thd Thread object - table Table object - db Database name - table_name Table name - old_lock_level Old lock level -*/ - -int abort_and_upgrade_lock_and_close_table(ALTER_PARTITION_PARAM_TYPE *lpt) -{ - DBUG_ENTER("abort_and_upgrade_lock_and_close_table"); - - if (wait_while_table_is_used(lpt->thd, lpt->table, HA_EXTRA_FORCE_REOPEN)) - DBUG_RETURN(1); - if (alter_close_tables(lpt)) - DBUG_RETURN(1); - DBUG_RETURN(0); -} - - /* Tells if two (or more) tables have auto_increment columns and we want to lock those tables with a write lock. diff --git a/sql/sql_base.h b/sql/sql_base.h index 45f1408e2f5..2b08d8fa40e 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -103,15 +103,10 @@ bool reopen_table(TABLE *table); bool reopen_tables(THD *thd,bool get_locks,bool in_refresh); void close_data_files_and_morph_locks(THD *thd, const char *db, const char *table_name); -void close_handle_and_leave_table_as_lock(TABLE *table); bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, uint db_stat, uint prgflag, uint ha_open_flags, TABLE *outparam, TABLE_LIST *table_desc, MEM_ROOT *mem_root); -bool wait_for_tables(THD *thd); -bool table_is_used(TABLE *table, bool wait_for_name_lock); -TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name); -void abort_locked_tables(THD *thd,const char *db, const char *table_name); bool get_key_map_from_key_list(key_map *map, TABLE *table, List *index_list); @@ -218,7 +213,6 @@ TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l, thr_lock_type lock_type, uint flags); bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags); bool lock_tables(THD *thd, TABLE_LIST *tables, uint counter, uint flags); -int abort_and_upgrade_lock_and_close_table(ALTER_PARTITION_PARAM_TYPE *lpt); int decide_logging_format(THD *thd, TABLE_LIST *tables); void free_io_cache(TABLE *entry); void intern_close_table(TABLE *entry); @@ -254,8 +248,6 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock, bool close_cached_connection_tables(THD *thd, bool wait_for_refresh, LEX_STRING *connect_string, bool have_lock = FALSE); -void close_all_tables_for_name(THD *thd, TABLE_SHARE *share, - bool remove_from_locked_tables); OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild); bool remove_table_from_cache(THD *thd, const char *db, const char *table, uint flags); diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index fb880cce8d3..0107a4d8144 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -71,6 +71,8 @@ #define ERROR_INJECT_CRASH(code) \ DBUG_EVALUATE_IF(code, (abort(), 0), 0) +#define ERROR_INJECT_ERROR(code) \ + DBUG_EVALUATE_IF(code, (my_error(ER_UNKNOWN_ERROR, MYF(0)), TRUE), 0) /* Partition related functions declarations and some static constants; @@ -6109,25 +6111,32 @@ static bool write_log_add_change_partition(ALTER_PARTITION_PARAM_TYPE *lpt) { partition_info *part_info= lpt->part_info; DDL_LOG_MEMORY_ENTRY *log_entry; - DDL_LOG_MEMORY_ENTRY *exec_log_entry= NULL; + DDL_LOG_MEMORY_ENTRY *exec_log_entry= part_info->exec_log_entry; char tmp_path[FN_REFLEN + 1]; char path[FN_REFLEN + 1]; uint next_entry= 0; + DDL_LOG_MEMORY_ENTRY *old_first_log_entry= part_info->first_log_entry; + /* write_log_drop_shadow_frm(lpt) must have been run first */ + DBUG_ASSERT(old_first_log_entry); DBUG_ENTER("write_log_add_change_partition"); build_table_filename(path, sizeof(path) - 1, lpt->db, lpt->table_name, "", 0); build_table_shadow_filename(tmp_path, sizeof(tmp_path) - 1, lpt); mysql_mutex_lock(&LOCK_gdl); + + /* Relink the previous drop shadow frm entry */ + if (old_first_log_entry) + next_entry= old_first_log_entry->entry_pos; if (write_log_dropped_partitions(lpt, &next_entry, (const char*)path, FALSE)) goto error; - if (write_log_replace_delete_frm(lpt, next_entry, NULL, tmp_path, - FALSE)) - goto error; log_entry= part_info->first_log_entry; + if (write_execute_ddl_log_entry(log_entry->entry_pos, - FALSE, &exec_log_entry)) + FALSE, + /* Reuse the old execute ddl_log_entry */ + &exec_log_entry)) goto error; mysql_mutex_unlock(&LOCK_gdl); set_part_info_exec_log_entry(part_info, exec_log_entry); @@ -6136,7 +6145,7 @@ static bool write_log_add_change_partition(ALTER_PARTITION_PARAM_TYPE *lpt) error: release_part_info_log_entries(part_info->first_log_entry); mysql_mutex_unlock(&LOCK_gdl); - part_info->first_log_entry= NULL; + part_info->first_log_entry= old_first_log_entry; my_error(ER_DDL_LOG_ERROR, MYF(0)); DBUG_RETURN(TRUE); } @@ -6153,9 +6162,15 @@ error: TRUE Error FALSE Success DESCRIPTION - We will write log entries that specify to remove all partitions reorganised, - to rename others to reflect the new naming scheme and to install the shadow - frm file. + We will write log entries that specify to + 1) Install the shadow frm file. + 2) Remove all partitions reorganized. (To be able to reorganize a partition + to the same name. Like in REORGANIZE p0 INTO (p0, p1), + so that the later rename from the new p0-temporary name to p0 don't + fail because the partition already exists. + 3) Rename others to reflect the new naming scheme. + + Note that it is written in the ddl log in reverse. */ static bool write_log_final_change_partition(ALTER_PARTITION_PARAM_TYPE *lpt) @@ -6169,20 +6184,25 @@ static bool write_log_final_change_partition(ALTER_PARTITION_PARAM_TYPE *lpt) uint next_entry= 0; DBUG_ENTER("write_log_final_change_partition"); + /* + Do not link any previous log entry. + Replace the revert operations with forced retry operations. + */ part_info->first_log_entry= NULL; build_table_filename(path, sizeof(path) - 1, lpt->db, lpt->table_name, "", 0); build_table_shadow_filename(shadow_path, sizeof(shadow_path) - 1, lpt); mysql_mutex_lock(&LOCK_gdl); + if (write_log_changed_partitions(lpt, &next_entry, (const char*)path)) + goto error; if (write_log_dropped_partitions(lpt, &next_entry, (const char*)path, lpt->alter_info->flags & ALTER_REORGANIZE_PARTITION)) goto error; - if (write_log_changed_partitions(lpt, &next_entry, (const char*)path)) - goto error; - if (write_log_replace_delete_frm(lpt, 0UL, shadow_path, path, TRUE)) + if (write_log_replace_delete_frm(lpt, next_entry, shadow_path, path, TRUE)) goto error; log_entry= part_info->first_log_entry; part_info->frm_log_entry= log_entry; + /* Overwrite the revert execute log entry with this retry execute entry */ if (write_execute_ddl_log_entry(log_entry->entry_pos, FALSE, &exec_log_entry)) goto error; @@ -6281,6 +6301,55 @@ static void alter_partition_lock_handling(ALTER_PARTITION_PARAM_TYPE *lpt) } +/* + Unlock and close table before renaming and dropping partitions + SYNOPSIS + alter_close_tables() + lpt Struct carrying parameters + RETURN VALUES + 0 +*/ + +static int alter_close_tables(ALTER_PARTITION_PARAM_TYPE *lpt) +{ + TABLE_SHARE *share= lpt->table->s; + THD *thd= lpt->thd; + TABLE *table; + DBUG_ENTER("alter_close_tables"); + /* + We must keep LOCK_open while manipulating with thd->open_tables. + Another thread may be working on it. + */ + mysql_mutex_lock(&LOCK_open); + /* + We can safely remove locks for all tables with the same name: + later they will all be closed anyway in + alter_partition_lock_handling(). + */ + for (table= thd->open_tables; table ; table= table->next) + { + if (!strcmp(table->s->table_name.str, share->table_name.str) && + !strcmp(table->s->db.str, share->db.str)) + { + mysql_lock_remove(thd, thd->lock, table); + table->file->close(); + table->db_stat= 0; // Mark file closed + /* + Ensure that we won't end up with a crippled table instance + in the table cache if an error occurs before we reach + alter_partition_lock_handling() and the table is closed + by close_thread_tables() instead. + */ + tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, + table->s->db.str, + table->s->table_name.str); + } + } + mysql_mutex_unlock(&LOCK_open); + DBUG_RETURN(0); +} + + /* Handle errors for ALTER TABLE for partitioning SYNOPSIS @@ -6294,13 +6363,26 @@ static void alter_partition_lock_handling(ALTER_PARTITION_PARAM_TYPE *lpt) void handle_alter_part_error(ALTER_PARTITION_PARAM_TYPE *lpt, bool not_completed, bool drop_partition, - bool frm_install) + bool frm_install, + bool close_table) { partition_info *part_info= lpt->part_info; DBUG_ENTER("handle_alter_part_error"); + if (close_table) + { + /* + Since the error handling (ddl_log) needs to drop newly created + partitions they must be closed first to not issue errors. + But we still need some information from the part_info object, + so we clone it first to have a copy. + */ + part_info= lpt->part_info->get_clone(); + alter_close_tables(lpt); + } + if (part_info->first_log_entry && - execute_ddl_log_entry(current_thd, + execute_ddl_log_entry(lpt->thd, part_info->first_log_entry->entry_pos)) { /* @@ -6401,6 +6483,22 @@ void handle_alter_part_error(ALTER_PARTITION_PARAM_TYPE *lpt, } +/** + Downgrade an exclusive MDL lock if under LOCK TABLE. + + If we don't downgrade the lock, it will not be downgraded or released + until the table is unlocked, resulting in blocking other threads using + the table. +*/ + +static void downgrade_mdl_if_lock_tables_mode(THD *thd, MDL_ticket *ticket, + enum_mdl_type type) +{ + if (thd->locked_tables_mode) + ticket->downgrade_exclusive_lock(type); +} + + /* Actually perform the change requested by ALTER TABLE of partitions previously prepared. @@ -6438,7 +6536,9 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, ALTER_PARTITION_PARAM_TYPE *lpt= &lpt_obj; bool written_bin_log= TRUE; bool not_completed= TRUE; + bool close_table_on_failure= FALSE; bool frm_install= FALSE; + MDL_ticket *mdl_ticket= table->mdl_ticket; DBUG_ENTER("fast_alter_partition_table"); lpt->thd= thd; @@ -6537,20 +6637,18 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, 0) Write an entry that removes the shadow frm file if crash occurs 1) Write the new frm file as a shadow frm - 2) Write the ddl log to ensure that the operation is completed - even in the presence of a MySQL Server crash - 3) Lock the table in TL_WRITE_ONLY to ensure all other accesses to - the table have completed. This ensures that other threads can not - execute on the table in parallel. - 4) Get an exclusive metadata lock on the table. This ensures that we + 2) Get an exclusive metadata lock on the table (waits for all active + transactions using this table). This ensures that we can release all other locks on the table and since no one can open the table, there can be no new threads accessing the table. They will be hanging on this exclusive lock. - 5) Close all tables that have already been opened but didn't stumble on + 3) Write the ddl log to ensure that the operation is completed + even in the presence of a MySQL Server crash (the log is executed + before any other threads are started, so there are no locking issues). + 4) Close all tables that have already been opened but didn't stumble on the abort locked previously. This is done as part of the - close_data_files_and_morph_locks call. - 6) We are now ready to release all locks we got in this thread. - 7) Write the bin log + alter_close_tables call. + 5) Write the bin log Unfortunately the writing of the binlog is not synchronised with other logging activities. So no matter in which order the binlog is written compared to other activities there will always be cases @@ -6561,40 +6659,54 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, require writing the statement first in the ddl log and then when recovering from the crash read the binlog and insert it into the binlog if not written already. - 8) Install the previously written shadow frm file - 9) Prepare handlers for drop of partitions - 10) Drop the partitions - 11) Remove entries from ddl log - 12) Reopen table if under lock tables - 13) Complete query + 6) Install the previously written shadow frm file + 7) Prepare handlers for drop of partitions + 8) Drop the partitions + 9) Remove entries from ddl log + 10) Reopen table if under lock tables + 11) Complete query We insert Error injections at all places where it could be interesting to test if recovery is properly done. */ if (write_log_drop_shadow_frm(lpt) || ERROR_INJECT_CRASH("crash_drop_partition_1") || + ERROR_INJECT_ERROR("fail_drop_partition_1") || mysql_write_frm(lpt, WFRM_WRITE_SHADOW) || ERROR_INJECT_CRASH("crash_drop_partition_2") || - write_log_drop_partition(lpt) || + ERROR_INJECT_ERROR("fail_drop_partition_2") || + wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN) || ERROR_INJECT_CRASH("crash_drop_partition_3") || - (not_completed= FALSE) || - abort_and_upgrade_lock_and_close_table(lpt) || + ERROR_INJECT_ERROR("fail_drop_partition_3") || + (close_table_on_failure= TRUE, FALSE) || + write_log_drop_partition(lpt) || + ERROR_INJECT_CRASH("crash_drop_partition_4") || + ERROR_INJECT_ERROR("fail_drop_partition_4") || + (close_table_on_failure= FALSE, FALSE) || + (not_completed= FALSE, FALSE) || + alter_close_tables(lpt) || ERROR_INJECT_CRASH("crash_drop_partition_5") || + ERROR_INJECT_ERROR("fail_drop_partition_5") || ((!thd->lex->no_write_to_binlog) && (write_bin_log(thd, FALSE, thd->query(), thd->query_length()), FALSE)) || ERROR_INJECT_CRASH("crash_drop_partition_6") || - ((frm_install= TRUE), FALSE) || + ERROR_INJECT_ERROR("fail_drop_partition_6") || + (frm_install= TRUE, FALSE) || mysql_write_frm(lpt, WFRM_INSTALL_SHADOW) || - ((frm_install= FALSE), FALSE) || + (frm_install= FALSE, FALSE) || ERROR_INJECT_CRASH("crash_drop_partition_7") || + ERROR_INJECT_ERROR("fail_drop_partition_7") || mysql_drop_partitions(lpt) || ERROR_INJECT_CRASH("crash_drop_partition_8") || + ERROR_INJECT_ERROR("fail_drop_partition_8") || (write_log_completed(lpt, FALSE), FALSE) || ERROR_INJECT_CRASH("crash_drop_partition_9") || + ERROR_INJECT_ERROR("fail_drop_partition_9") || (alter_partition_lock_handling(lpt), FALSE)) { - handle_alter_part_error(lpt, not_completed, TRUE, frm_install); + handle_alter_part_error(lpt, not_completed, TRUE, frm_install, + close_table_on_failure); goto err; } } @@ -6613,54 +6725,64 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, 0) Write an entry that removes the shadow frm file if crash occurs 1) Write the new frm file as a shadow frm file - 2) Log the changes to happen in ddl log - 2) Add the new partitions - 3) Lock all partitions in TL_WRITE_ONLY to ensure that no users - are still using the old partitioning scheme. Wait until all - ongoing users have completed before progressing. - 4) Get an exclusive metadata lock on the table. This ensures that we + 2) Get an exclusive metadata lock on the table (waits for all active + transactions using this table). This ensures that we can release all other locks on the table and since no one can open the table, there can be no new threads accessing the table. They will be hanging on this exclusive lock. - 5) Close all tables that have already been opened but didn't stumble on - the abort locked previously. This is done as part of the - close_data_files_and_morph_locks call. - 6) Close all table handlers and unlock all handlers but retain - metadata lock. - 7) Write binlog - 8) Now the change is completed except for the installation of the + 3) Write an entry to remove the new parttions if crash occurs + 4) Add the new partitions. + 5) Close all instances of the table and remove them from the table cache. + 6) Write binlog + 7) Now the change is completed except for the installation of the new frm file. We thus write an action in the log to change to the shadow frm file - 9) Install the new frm file of the table where the partitions are + 8) Install the new frm file of the table where the partitions are added to the table. - 10)Wait until all accesses using the old frm file has completed - 11)Remove entries from ddl log - 12)Reopen tables if under lock tables - 13)Complete query + 9) Remove entries from ddl log + 10)Reopen tables if under lock tables + 11)Complete query */ - if (write_log_add_change_partition(lpt) || + if (write_log_drop_shadow_frm(lpt) || ERROR_INJECT_CRASH("crash_add_partition_1") || + ERROR_INJECT_ERROR("fail_add_partition_1") || mysql_write_frm(lpt, WFRM_WRITE_SHADOW) || ERROR_INJECT_CRASH("crash_add_partition_2") || - mysql_change_partitions(lpt) || + ERROR_INJECT_ERROR("fail_add_partition_2") || + wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN) || ERROR_INJECT_CRASH("crash_add_partition_3") || - abort_and_upgrade_lock_and_close_table(lpt) || + ERROR_INJECT_ERROR("fail_add_partition_3") || + (close_table_on_failure= TRUE, FALSE) || + write_log_add_change_partition(lpt) || + ERROR_INJECT_CRASH("crash_add_partition_4") || + ERROR_INJECT_ERROR("fail_add_partition_4") || + mysql_change_partitions(lpt) || ERROR_INJECT_CRASH("crash_add_partition_5") || + ERROR_INJECT_ERROR("fail_add_partition_5") || + (close_table_on_failure= FALSE, FALSE) || + alter_close_tables(lpt) || + ERROR_INJECT_CRASH("crash_add_partition_6") || + ERROR_INJECT_ERROR("fail_add_partition_6") || ((!thd->lex->no_write_to_binlog) && (write_bin_log(thd, FALSE, thd->query(), thd->query_length()), FALSE)) || - ERROR_INJECT_CRASH("crash_add_partition_6") || - write_log_rename_frm(lpt) || - (not_completed= FALSE) || ERROR_INJECT_CRASH("crash_add_partition_7") || - ((frm_install= TRUE), FALSE) || - mysql_write_frm(lpt, WFRM_INSTALL_SHADOW) || + ERROR_INJECT_ERROR("fail_add_partition_7") || + write_log_rename_frm(lpt) || + (frm_install= TRUE, FALSE) || + (not_completed= FALSE, FALSE) || ERROR_INJECT_CRASH("crash_add_partition_8") || - (write_log_completed(lpt, FALSE), FALSE) || + ERROR_INJECT_ERROR("fail_add_partition_8") || + mysql_write_frm(lpt, WFRM_INSTALL_SHADOW) || ERROR_INJECT_CRASH("crash_add_partition_9") || + ERROR_INJECT_ERROR("fail_add_partition_9") || + (write_log_completed(lpt, FALSE), FALSE) || + ERROR_INJECT_CRASH("crash_add_partition_10") || + ERROR_INJECT_ERROR("fail_add_partition_10") || (alter_partition_lock_handling(lpt), FALSE)) { - handle_alter_part_error(lpt, not_completed, FALSE, frm_install); + handle_alter_part_error(lpt, not_completed, FALSE, frm_install, + close_table_on_failure); goto err; } } @@ -6703,13 +6825,14 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, removed in a crash situation 3) Add the new partitions Copy from the reorganised partitions to the new partitions - 4) Log that operation is completed and log all complete actions + 4) Get an exclusive metadata lock on the table (waits for all active + transactions using this table). This ensures that we + can release all other locks on the table and since no one can open + the table, there can be no new threads accessing the table. They + will be hanging on this exclusive lock. + 5) Close all instances of the table and remove them from the table cache. + 6) Log that operation is completed and log all complete actions needed to complete operation from here - 5) Upgrade shared metadata lock on the table to an exclusive one. - After this we can be sure that there is no other connection - using this table (they will be waiting for metadata lock). - 6) Close all table instances opened by this thread, but retain - exclusive metadata lock. 7) Write bin log 8) Prepare handlers for rename and delete of partitions 9) Rename and drop the reorged partitions such that they are no @@ -6718,36 +6841,56 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, 11) Reopen the table if under lock tables 12) Complete query */ - if (write_log_add_change_partition(lpt) || + if (write_log_drop_shadow_frm(lpt) || ERROR_INJECT_CRASH("crash_change_partition_1") || + ERROR_INJECT_ERROR("fail_change_partition_1") || mysql_write_frm(lpt, WFRM_WRITE_SHADOW) || ERROR_INJECT_CRASH("crash_change_partition_2") || - mysql_change_partitions(lpt) || + ERROR_INJECT_ERROR("fail_change_partition_2") || + (close_table_on_failure= TRUE, FALSE) || + write_log_add_change_partition(lpt) || ERROR_INJECT_CRASH("crash_change_partition_3") || - write_log_final_change_partition(lpt) || + ERROR_INJECT_ERROR("fail_change_partition_3") || + mysql_change_partitions(lpt) || ERROR_INJECT_CRASH("crash_change_partition_4") || - (not_completed= FALSE) || - abort_and_upgrade_lock_and_close_table(lpt) || + ERROR_INJECT_ERROR("fail_change_partition_4") || + wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN) || + ERROR_INJECT_CRASH("crash_change_partition_5") || + ERROR_INJECT_ERROR("fail_change_partition_5") || + alter_close_tables(lpt) || + (close_table_on_failure= FALSE) || ERROR_INJECT_CRASH("crash_change_partition_6") || + ERROR_INJECT_ERROR("fail_change_partition_6") || + write_log_final_change_partition(lpt) || + (not_completed= FALSE) || + ERROR_INJECT_CRASH("crash_change_partition_7") || + ERROR_INJECT_ERROR("fail_change_partition_7") || ((!thd->lex->no_write_to_binlog) && (write_bin_log(thd, FALSE, thd->query(), thd->query_length()), FALSE)) || - ERROR_INJECT_CRASH("crash_change_partition_7") || - mysql_write_frm(lpt, WFRM_INSTALL_SHADOW) || ERROR_INJECT_CRASH("crash_change_partition_8") || - mysql_drop_partitions(lpt) || + ERROR_INJECT_ERROR("fail_change_partition_8") || + mysql_write_frm(lpt, WFRM_INSTALL_SHADOW) || ERROR_INJECT_CRASH("crash_change_partition_9") || + ERROR_INJECT_ERROR("fail_change_partition_9") || + mysql_drop_partitions(lpt) || + ERROR_INJECT_CRASH("crash_change_partition_10") || + ERROR_INJECT_ERROR("fail_change_partition_10") || mysql_rename_partitions(lpt) || ((frm_install= TRUE), FALSE) || - ERROR_INJECT_CRASH("crash_change_partition_10") || - (write_log_completed(lpt, FALSE), FALSE) || ERROR_INJECT_CRASH("crash_change_partition_11") || + ERROR_INJECT_ERROR("fail_change_partition_11") || + (write_log_completed(lpt, FALSE), FALSE) || + ERROR_INJECT_CRASH("crash_change_partition_12") || + ERROR_INJECT_ERROR("fail_change_partition_12") || (alter_partition_lock_handling(lpt), FALSE)) { - handle_alter_part_error(lpt, not_completed, FALSE, frm_install); + handle_alter_part_error(lpt, not_completed, FALSE, frm_install, + close_table_on_failure); goto err; } } + downgrade_mdl_if_lock_tables_mode(thd, mdl_ticket, MDL_SHARED_NO_READ_WRITE); /* A final step is to write the query to the binlog and send ok to the user @@ -6756,6 +6899,8 @@ uint fast_alter_partition_table(THD *thd, TABLE *table, table_list, FALSE, NULL, written_bin_log)); err: + downgrade_mdl_if_lock_tables_mode(thd, mdl_ticket, MDL_SHARED_NO_READ_WRITE); + table->m_needs_reopen= TRUE; DBUG_RETURN(TRUE); } #endif diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 02a874ce62f..7f8e4f68ec5 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1086,6 +1086,7 @@ static bool get_free_ddl_log_entry(DDL_LOG_MEMORY_ENTRY **active_entry, */ used_entry->next_log_entry= first_used; used_entry->prev_log_entry= NULL; + used_entry->next_active_log_entry= NULL; global_ddl_log.first_used= used_entry; if (first_used) first_used->prev_log_entry= used_entry; diff --git a/sql/table.h b/sql/table.h index 7579a8a6df3..52ac92299a6 100644 --- a/sql/table.h +++ b/sql/table.h @@ -43,7 +43,6 @@ class Security_context; struct TABLE_LIST; class ACL_internal_schema_access; class ACL_internal_table_access; -struct TABLE_LIST; class Field; /* -- cgit v1.2.1 From 99b1d5ea24860396ef564e5a20620ee6f91f07fd Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Fri, 13 Aug 2010 10:02:37 +0200 Subject: Bug #54105 assert in MDL_context::release_locks_stored_before The problem was that SHOW CREATE EVENT released all metadata locks held by the current transaction. This made any exisiting savepoints invalid, triggering the assert when ROLLBACK TO SAVEPOINT later was executed. This patch fixes the problem by making sure SHOW CREATE EVENT only releases metadata locks acquired by the statement itself. Test case added to event_trans.test. --- sql/event_db_repository.cc | 19 ++++++++++++++----- sql/events.cc | 3 --- 2 files changed, 14 insertions(+), 8 deletions(-) (limited to 'sql') diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc index d47f1641bb0..db508e4ea41 100644 --- a/sql/event_db_repository.cc +++ b/sql/event_db_repository.cc @@ -996,24 +996,33 @@ Event_db_repository::load_named_event(THD *thd, LEX_STRING dbname, LEX_STRING name, Event_basic *etn) { bool ret; - TABLE *table= NULL; ulong saved_mode= thd->variables.sql_mode; + Open_tables_backup open_tables_backup; + TABLE_LIST event_table; DBUG_ENTER("Event_db_repository::load_named_event"); DBUG_PRINT("enter",("thd: 0x%lx name: %*s", (long) thd, (int) name.length, name.str)); + event_table.init_one_table("mysql", 5, "event", 5, "event", TL_READ); + /* Reset sql_mode during data dictionary operations. */ thd->variables.sql_mode= 0; - if (!(ret= open_event_table(thd, TL_READ, &table))) + /* + We don't use open_event_table() here to make sure that SHOW + CREATE EVENT works properly in transactional context, and + does not release transactional metadata locks when the + event table is closed. + */ + if (!(ret= open_system_tables_for_read(thd, &event_table, &open_tables_backup))) { - if ((ret= find_named_event(dbname, name, table))) + if ((ret= find_named_event(dbname, name, event_table.table))) my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name.str); - else if ((ret= etn->load_from_row(thd, table))) + else if ((ret= etn->load_from_row(thd, event_table.table))) my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0), "event"); - close_mysql_tables(thd); + close_system_tables(thd, &open_tables_backup); } thd->variables.sql_mode= saved_mode; diff --git a/sql/events.cc b/sql/events.cc index 5379ec2c9eb..3dc6a46bb66 100644 --- a/sql/events.cc +++ b/sql/events.cc @@ -699,7 +699,6 @@ send_show_create_event(THD *thd, Event_timed *et, Protocol *protocol) bool Events::show_create_event(THD *thd, LEX_STRING dbname, LEX_STRING name) { - Open_tables_backup open_tables_backup; Event_timed et; bool ret; @@ -722,9 +721,7 @@ Events::show_create_event(THD *thd, LEX_STRING dbname, LEX_STRING name) deadlock can occur please refer to the description of 'system table' flag. */ - thd->reset_n_backup_open_tables_state(&open_tables_backup); ret= db_repository->load_named_event(thd, dbname, name, &et); - thd->restore_backup_open_tables_state(&open_tables_backup); if (!ret) ret= send_show_create_event(thd, &et, thd->protocol); -- cgit v1.2.1 From 8b25c0e4dc6cb18de7ce4be25eb49c44eeab35cf Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Fri, 13 Aug 2010 16:05:46 +0300 Subject: Bug #55565: debug assertion when ordering by expressions with user variable assignments The assert() that is firing is checking if expressions that can't be null return a NULL when evaluated. MAKEDATE() function can return NULL if the second argument is less then or equal to 0. Thus its nullability depends not only on the nullability of its arguments but also on their values. Fixed by (overoptimistically) setting MAKEDATE() to be nullable despite the nullability of its arguments. Test added. Had to update one test result to reflect the metadata change. --- sql/item_timefunc.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sql') diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h index a7a64090f6c..11eed70f399 100644 --- a/sql/item_timefunc.h +++ b/sql/item_timefunc.h @@ -881,6 +881,8 @@ public: { decimals=0; max_length=MAX_DATE_WIDTH*MY_CHARSET_BIN_MB_MAXLEN; + /* It returns NULL when the second argument is less or equal to 0 */ + maybe_null= 1; } longlong val_int(); }; -- cgit v1.2.1 From 790852c0c91df8bf104687753c019ceefaed6622 Mon Sep 17 00:00:00 2001 From: Georgi Kodinov Date: Fri, 13 Aug 2010 11:07:39 +0300 Subject: Bug #55580 : segfault in read_view_sees_trx_id The server was not checking for errors generated during the execution of Item::val_xxx() methods when copying data to the group, order, or distinct temp table's row. Fixed by extending the copy_funcs() to return an error code and by checking for that error code on the places copy_funcs() is called. Test case added. --- sql/item_sum.cc | 6 ++++-- sql/sql_select.cc | 44 +++++++++++++++++++++++++++++++++++++------- sql/sql_select.h | 2 +- 3 files changed, 42 insertions(+), 10 deletions(-) (limited to 'sql') diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 228e36fc9f9..f92bde9ce87 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -2556,7 +2556,8 @@ bool Item_sum_count_distinct::add() if (always_null) return 0; copy_fields(tmp_table_param); - copy_funcs(tmp_table_param->items_to_copy); + if (copy_funcs(tmp_table_param->items_to_copy, table->in_use)) + return TRUE; for (Field **field=table->field ; *field ; field++) if ((*field)->is_real_null(0)) @@ -3128,7 +3129,8 @@ bool Item_func_group_concat::add() if (always_null) return 0; copy_fields(tmp_table_param); - copy_funcs(tmp_table_param->items_to_copy); + if (copy_funcs(tmp_table_param->items_to_copy, table->in_use)) + return TRUE; for (uint i= 0; i < arg_count_field; i++) { diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 4bb0d3a9610..7ee1762295f 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -12487,7 +12487,9 @@ end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), if (!end_of_records) { copy_fields(&join->tmp_table_param); - copy_funcs(join->tmp_table_param.items_to_copy); + if (copy_funcs(join->tmp_table_param.items_to_copy, join->thd)) + DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */ + #ifdef TO_BE_DELETED if (!table->uniques) // If not unique handling { @@ -12593,7 +12595,8 @@ end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), memcpy(table->record[0]+key_part->offset, group->buff, 1); } init_tmptable_sum_functions(join->sum_funcs); - copy_funcs(join->tmp_table_param.items_to_copy); + if (copy_funcs(join->tmp_table_param.items_to_copy, join->thd)) + DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */ if ((error=table->file->ha_write_row(table->record[0]))) { if (create_myisam_from_heap(join->thd, table, &join->tmp_table_param, @@ -12628,7 +12631,8 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), init_tmptable_sum_functions(join->sum_funcs); copy_fields(&join->tmp_table_param); // Groups are copied twice. - copy_funcs(join->tmp_table_param.items_to_copy); + if (copy_funcs(join->tmp_table_param.items_to_copy, join->thd)) + DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */ if (!(error=table->file->ha_write_row(table->record[0]))) join->send_records++; // New group @@ -12715,7 +12719,8 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), if (idx < (int) join->send_group_parts) { copy_fields(&join->tmp_table_param); - copy_funcs(join->tmp_table_param.items_to_copy); + if (copy_funcs(join->tmp_table_param.items_to_copy, join->thd)) + DBUG_RETURN(NESTED_LOOP_ERROR); if (init_sum_functions(join->sum_funcs, join->sum_funcs_end[idx+1])) DBUG_RETURN(NESTED_LOOP_ERROR); if (join->procedure) @@ -15775,14 +15780,39 @@ update_sum_func(Item_sum **func_ptr) return 0; } -/** Copy result of functions to record in tmp_table. */ +/** + Copy result of functions to record in tmp_table. -void -copy_funcs(Item **func_ptr) + Uses the thread pointer to check for errors in + some of the val_xxx() methods called by the + save_in_result_field() function. + TODO: make the Item::val_xxx() return error code + + @param func_ptr array of the function Items to copy to the tmp table + @param thd pointer to the current thread for error checking + @retval + FALSE if OK + @retval + TRUE on error +*/ + +bool +copy_funcs(Item **func_ptr, const THD *thd) { Item *func; for (; (func = *func_ptr) ; func_ptr++) + { func->save_in_result_field(1); + /* + Need to check the THD error state because Item::val_xxx() don't + return error code, but can generate errors + TODO: change it for a real status check when Item::val_xxx() + are extended to return status code. + */ + if (thd->is_error()) + return TRUE; + } + return FALSE; } diff --git a/sql/sql_select.h b/sql/sql_select.h index b39827ef61b..fbe23bbd8fe 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -601,7 +601,7 @@ bool setup_copy_fields(THD *thd, TMP_TABLE_PARAM *param, List &new_list1, List &new_list2, uint elements, List &fields); void copy_fields(TMP_TABLE_PARAM *param); -void copy_funcs(Item **func_ptr); +bool copy_funcs(Item **func_ptr, const THD *thd); bool create_myisam_from_heap(THD *thd, TABLE *table, TMP_TABLE_PARAM *param, int error, bool ignore_last_dupp_error); uint find_shortest_key(TABLE *table, const key_map *usable_keys); -- cgit v1.2.1 From 8ce6e98aa335590d18dbb4f9d453ad4c67bd1a2b Mon Sep 17 00:00:00 2001 From: Konstantin Osipov Date: Fri, 13 Aug 2010 13:51:48 +0400 Subject: Extract reload_acl_and_cache() and flush_tables_with_read_lock() into an own implementation file. --- sql/CMakeLists.txt | 1 + sql/Makefile.am | 6 +- sql/lock.h | 42 ------ sql/mysqld.cc | 1 + sql/sql_base.h | 43 ++++++ sql/sql_parse.cc | 407 +------------------------------------------------- sql/sql_parse.h | 2 - sql/sql_reload.cc | 427 +++++++++++++++++++++++++++++++++++++++++++++++++++++ sql/sql_reload.h | 26 ++++ 9 files changed, 507 insertions(+), 448 deletions(-) create mode 100644 sql/sql_reload.cc create mode 100644 sql/sql_reload.h (limited to 'sql') diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 943d6b2eece..68753c092e2 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -75,6 +75,7 @@ SET (SQL_SOURCE sql_profile.cc event_parse_data.cc sql_signal.cc rpl_handler.cc mdl.cc transaction.cc sys_vars.cc sql_truncate.cc datadict.cc + sql_reload.cc ${GEN_SOURCES} ${MYSYS_LIBWRAP_SOURCE}) diff --git a/sql/Makefile.am b/sql/Makefile.am index 7fed55f3cd6..1c75ead9516 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -40,6 +40,7 @@ DTRACEFILES = filesort.o \ sql_cursor.o \ sql_delete.o \ sql_truncate.o \ + sql_reload.o \ sql_insert.o \ datadict.o \ sql_parse.o \ @@ -59,6 +60,7 @@ DTRACEFILES_DEPEND = filesort.o \ sql_cursor.o \ sql_delete.o \ sql_truncate.o \ + sql_reload.o \ sql_insert.o \ datadict.o \ sql_parse.o \ @@ -126,7 +128,7 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.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 \ - datadict.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 \ @@ -140,7 +142,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.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 \ - datadict.cc sql_profile.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_truncate.cc \ diff --git a/sql/lock.h b/sql/lock.h index 0083dd3ba18..c097c8d269e 100644 --- a/sql/lock.h +++ b/sql/lock.h @@ -9,48 +9,6 @@ struct TABLE_LIST; class THD; typedef struct st_mysql_lock MYSQL_LOCK; -/* mysql_lock_tables() and open_table() flags bits */ -#define MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK 0x0001 -#define MYSQL_OPEN_IGNORE_FLUSH 0x0002 -#define MYSQL_OPEN_TEMPORARY_ONLY 0x0004 -#define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY 0x0008 -#define MYSQL_LOCK_LOG_TABLE 0x0010 -/** - Do not try to acquire a metadata lock on the table: we - already have one. -*/ -#define MYSQL_OPEN_HAS_MDL_LOCK 0x0020 -/** - If in locked tables mode, ignore the locked tables and get - a new instance of the table. -*/ -#define MYSQL_OPEN_GET_NEW_TABLE 0x0040 -/** Don't look up the table in the list of temporary tables. */ -#define MYSQL_OPEN_SKIP_TEMPORARY 0x0080 -/** Fail instead of waiting when conficting metadata lock is discovered. */ -#define MYSQL_OPEN_FAIL_ON_MDL_CONFLICT 0x0100 -/** Open tables using MDL_SHARED lock instead of one specified in parser. */ -#define MYSQL_OPEN_FORCE_SHARED_MDL 0x0200 -/** - Open tables using MDL_SHARED_HIGH_PRIO lock instead of one specified - in parser. -*/ -#define MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL 0x0400 -/** - When opening or locking the table, use the maximum timeout - (LONG_TIMEOUT = 1 year) rather than the user-supplied timeout value. -*/ -#define MYSQL_LOCK_IGNORE_TIMEOUT 0x0800 - -/** Please refer to the internals manual. */ -#define MYSQL_OPEN_REOPEN (MYSQL_OPEN_IGNORE_FLUSH |\ - MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK |\ - MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY |\ - MYSQL_LOCK_IGNORE_TIMEOUT |\ - MYSQL_OPEN_GET_NEW_TABLE |\ - MYSQL_OPEN_SKIP_TEMPORARY |\ - MYSQL_OPEN_HAS_MDL_LOCK) - MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, uint flags); void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 278d1688c21..e0dc3ef0211 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -87,6 +87,7 @@ #include #include "sp_rcontext.h" #include "sp_cache.h" +#include "sql_reload.h" // reload_acl_and_cache #ifdef HAVE_POLL_H #include diff --git a/sql/sql_base.h b/sql/sql_base.h index 8462ef3d2aa..ca5ca31ab6a 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -89,6 +89,49 @@ TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name); TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update, uint lock_flags); + +/* mysql_lock_tables() and open_table() flags bits */ +#define MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK 0x0001 +#define MYSQL_OPEN_IGNORE_FLUSH 0x0002 +#define MYSQL_OPEN_TEMPORARY_ONLY 0x0004 +#define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY 0x0008 +#define MYSQL_LOCK_LOG_TABLE 0x0010 +/** + Do not try to acquire a metadata lock on the table: we + already have one. +*/ +#define MYSQL_OPEN_HAS_MDL_LOCK 0x0020 +/** + If in locked tables mode, ignore the locked tables and get + a new instance of the table. +*/ +#define MYSQL_OPEN_GET_NEW_TABLE 0x0040 +/** Don't look up the table in the list of temporary tables. */ +#define MYSQL_OPEN_SKIP_TEMPORARY 0x0080 +/** Fail instead of waiting when conficting metadata lock is discovered. */ +#define MYSQL_OPEN_FAIL_ON_MDL_CONFLICT 0x0100 +/** Open tables using MDL_SHARED lock instead of one specified in parser. */ +#define MYSQL_OPEN_FORCE_SHARED_MDL 0x0200 +/** + Open tables using MDL_SHARED_HIGH_PRIO lock instead of one specified + in parser. +*/ +#define MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL 0x0400 +/** + When opening or locking the table, use the maximum timeout + (LONG_TIMEOUT = 1 year) rather than the user-supplied timeout value. +*/ +#define MYSQL_LOCK_IGNORE_TIMEOUT 0x0800 + +/** Please refer to the internals manual. */ +#define MYSQL_OPEN_REOPEN (MYSQL_OPEN_IGNORE_FLUSH |\ + MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK |\ + MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY |\ + MYSQL_LOCK_IGNORE_TIMEOUT |\ + MYSQL_OPEN_GET_NEW_TABLE |\ + MYSQL_OPEN_SKIP_TEMPORARY |\ + MYSQL_OPEN_HAS_MDL_LOCK) + bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, Open_table_context *ot_ctx); bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias, diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index b72bd3ac7f3..18aa4a09478 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -50,6 +50,7 @@ // mysql_backup_table, // mysql_restore_table #include "sql_truncate.h" // mysql_truncate_table +#include "sql_reload.h" // reload_acl_and_cache #include "sql_connect.h" // check_user, // decrease_user_connections, // thd_init_client_charset, check_mqh, @@ -1695,153 +1696,6 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident, } -/** - Implementation of FLUSH TABLES WITH READ LOCK. - - In brief: take exclusive locks, expel tables from the table - cache, reopen the tables, enter the 'LOCKED TABLES' mode, - downgrade the locks. - Note: the function is written to be called from - mysql_execute_command(), it is not reusable in arbitrary - execution context. - - Required privileges - ------------------- - Since the statement implicitly enters LOCK TABLES mode, - it requires LOCK TABLES privilege on every table. - But since the rest of FLUSH commands require - the global RELOAD_ACL, it also requires RELOAD_ACL. - - Compatibility with the global read lock - --------------------------------------- - We don't wait for the GRL, since neither the - 5.1 combination that this new statement is intended to - replace (LOCK TABLE WRITE; FLUSH TABLES;), - nor FLUSH TABLES WITH READ LOCK do. - @todo: this is not implemented, Dmitry disagrees. - Currently we wait for GRL in another connection, - but are compatible with a GRL in our own connection. - - Behaviour under LOCK TABLES - --------------------------- - Bail out: i.e. don't perform an implicit UNLOCK TABLES. - This is not consistent with LOCK TABLES statement, but is - in line with behaviour of FLUSH TABLES WITH READ LOCK, and we - try to not introduce any new statements with implicit - semantics. - - Compatibility with parallel updates - ----------------------------------- - As a result, we will wait for all open transactions - against the tables to complete. After the lock downgrade, - new transactions will be able to read the tables, but not - write to them. - - Differences from FLUSH TABLES - ------------------------------------- - - you can't flush WITH READ LOCK a non-existent table - - you can't flush WITH READ LOCK under LOCK TABLES - - currently incompatible with the GRL (@todo: fix) - - Effect on views and temporary tables. - ------------------------------------ - You can only apply this command to existing base tables. - If a view with such name exists, ER_WRONG_OBJECT is returned. - If a temporary table with such name exists, it's ignored: - if there is a base table, it's used, otherwise ER_NO_SUCH_TABLE - is returned. - - Implicit commit - --------------- - This statement causes an implicit commit before and - after it. - - HANDLER SQL - ----------- - If this connection has HANDLERs open against - some of the tables being FLUSHed, these handlers - are implicitly flushed (lose their position). -*/ - -static bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables) -{ - Lock_tables_prelocking_strategy lock_tables_prelocking_strategy; - TABLE_LIST *table_list; - MDL_request_list mdl_requests; - - /* - This is called from SQLCOM_FLUSH, the transaction has - been committed implicitly. - */ - - /* RELOAD_ACL is checked by the caller. Check table-level privileges. */ - if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables, - FALSE, UINT_MAX, FALSE)) - goto error; - - if (thd->locked_tables_mode) - { - my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); - goto error; - } - - /* - Acquire SNW locks on tables to be flushed. We can't use - lock_table_names() here as this call will also acquire global IX - and database-scope IX locks on the tables, and this will make - this statement incompatible with FLUSH TABLES WITH READ LOCK. - */ - for (table_list= all_tables; table_list; - table_list= table_list->next_global) - mdl_requests.push_front(&table_list->mdl_request); - - if (thd->mdl_context.acquire_locks(&mdl_requests, - thd->variables.lock_wait_timeout)) - goto error; - - DEBUG_SYNC(thd,"flush_tables_with_read_lock_after_acquire_locks"); - - for (table_list= all_tables; table_list; - table_list= table_list->next_global) - { - /* Request removal of table from cache. */ - tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, - table_list->db, - table_list->table_name, FALSE); - - /* Skip views and temporary tables. */ - table_list->required_type= FRMTYPE_TABLE; /* Don't try to flush views. */ - table_list->open_type= OT_BASE_ONLY; /* Ignore temporary tables. */ - } - - /* - Before opening and locking tables the below call also waits - for old shares to go away, so the fact that we don't pass - MYSQL_LOCK_IGNORE_FLUSH flag to it is important. - */ - if (open_and_lock_tables(thd, all_tables, FALSE, - MYSQL_OPEN_HAS_MDL_LOCK, - &lock_tables_prelocking_strategy) || - thd->locked_tables_list.init_locked_tables(thd)) - { - goto error; - } - thd->variables.option_bits|= OPTION_TABLE_LOCK; - - /* - We don't downgrade MDL_SHARED_NO_WRITE here as the intended - post effect of this call is identical to LOCK TABLES <...> READ, - and we didn't use thd->in_lock_talbes and - thd->sql_command= SQLCOM_LOCK_TABLES hacks to enter the LTM. - */ - - return FALSE; - -error: - return TRUE; -} - - /** Read query from packet and store in thd->query. Used in COM_QUERY and COM_STMT_PREPARE. @@ -3972,6 +3826,10 @@ end_with_restore_list: if (first_table && lex->type & REFRESH_READ_LOCK) { + /* Check table-level privileges. */ + if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables, + FALSE, UINT_MAX, FALSE)) + goto error; if (flush_tables_with_read_lock(thd, all_tables)) goto error; my_ok(thd); @@ -6704,261 +6562,6 @@ void add_join_natural(TABLE_LIST *a, TABLE_LIST *b, List *using_fields, } -/** - Reload/resets privileges and the different caches. - - @param thd Thread handler (can be NULL!) - @param options What should be reset/reloaded (tables, privileges, slave...) - @param tables Tables to flush (if any) - @param write_to_binlog True if we can write to the binlog. - - @note Depending on 'options', it may be very bad to write the - query to the binlog (e.g. FLUSH SLAVE); this is a - pointer where reload_acl_and_cache() will put 0 if - it thinks we really should not write to the binlog. - Otherwise it will put 1. - - @return Error status code - @retval 0 Ok - @retval !=0 Error; thd->killed is set or thd->is_error() is true -*/ - -bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, - bool *write_to_binlog) -{ - bool result=0; - select_errors=0; /* Write if more errors */ - bool tmp_write_to_binlog= 1; - - DBUG_ASSERT(!thd || !thd->in_sub_stmt); - -#ifndef NO_EMBEDDED_ACCESS_CHECKS - if (options & REFRESH_GRANT) - { - THD *tmp_thd= 0; - /* - If reload_acl_and_cache() is called from SIGHUP handler we have to - allocate temporary THD for execution of acl_reload()/grant_reload(). - */ - if (!thd && (thd= (tmp_thd= new THD))) - { - thd->thread_stack= (char*) &tmp_thd; - thd->store_globals(); - } - - if (thd) - { - bool reload_acl_failed= acl_reload(thd); - bool reload_grants_failed= grant_reload(thd); - bool reload_servers_failed= servers_reload(thd); - - if (reload_acl_failed || reload_grants_failed || reload_servers_failed) - { - result= 1; - /* - When an error is returned, my_message may have not been called and - the client will hang waiting for a response. - */ - my_error(ER_UNKNOWN_ERROR, MYF(0), "FLUSH PRIVILEGES failed"); - } - } - - if (tmp_thd) - { - delete tmp_thd; - /* Remember that we don't have a THD */ - my_pthread_setspecific_ptr(THR_THD, 0); - thd= 0; - } - reset_mqh((LEX_USER *)NULL, TRUE); - } -#endif - if (options & REFRESH_LOG) - { - /* - Flush the normal query log, the update log, the binary log, - the slow query log, the relay log (if it exists) and the log - tables. - */ - - options|= REFRESH_BINARY_LOG; - options|= REFRESH_RELAY_LOG; - options|= REFRESH_SLOW_LOG; - options|= REFRESH_GENERAL_LOG; - options|= REFRESH_ENGINE_LOG; - options|= REFRESH_ERROR_LOG; - } - - if (options & REFRESH_ERROR_LOG) - if (flush_error_log()) - result= 1; - - if ((options & REFRESH_SLOW_LOG) && opt_slow_log) - logger.flush_slow_log(); - - if ((options & REFRESH_GENERAL_LOG) && opt_log) - logger.flush_general_log(); - - if (options & REFRESH_ENGINE_LOG) - if (ha_flush_logs(NULL)) - result= 1; - - if (options & REFRESH_BINARY_LOG) - { - /* - Writing this command to the binlog may result in infinite loops - when doing mysqlbinlog|mysql, and anyway it does not really make - sense to log it automatically (would cause more trouble to users - than it would help them) - */ - tmp_write_to_binlog= 0; - if (mysql_bin_log.is_open()) - mysql_bin_log.rotate_and_purge(RP_FORCE_ROTATE); - } - if (options & REFRESH_RELAY_LOG) - { -#ifdef HAVE_REPLICATION - mysql_mutex_lock(&LOCK_active_mi); - rotate_relay_log(active_mi); - mysql_mutex_unlock(&LOCK_active_mi); -#endif - } -#ifdef HAVE_QUERY_CACHE - if (options & REFRESH_QUERY_CACHE_FREE) - { - query_cache.pack(); // FLUSH QUERY CACHE - options &= ~REFRESH_QUERY_CACHE; // Don't flush cache, just free memory - } - if (options & (REFRESH_TABLES | REFRESH_QUERY_CACHE)) - { - query_cache.flush(); // RESET QUERY CACHE - } -#endif /*HAVE_QUERY_CACHE*/ - - DBUG_ASSERT(!thd || thd->locked_tables_mode || - !thd->mdl_context.has_locks() || - thd->handler_tables_hash.records || - thd->global_read_lock.is_acquired()); - - /* - Note that if REFRESH_READ_LOCK bit is set then REFRESH_TABLES is set too - (see sql_yacc.yy) - */ - if (options & (REFRESH_TABLES | REFRESH_READ_LOCK)) - { - if ((options & REFRESH_READ_LOCK) && thd) - { - /* - On the first hand we need write lock on the tables to be flushed, - on the other hand we must not try to aspire a global read lock - if we have a write locked table as this would lead to a deadlock - when trying to reopen (and re-lock) the table after the flush. - */ - if (thd->locked_tables_mode) - { - my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); - return 1; - } - /* - Writing to the binlog could cause deadlocks, as we don't log - UNLOCK TABLES - */ - tmp_write_to_binlog= 0; - if (thd->global_read_lock.lock_global_read_lock(thd)) - return 1; // Killed - if (close_cached_tables(thd, tables, - ((options & REFRESH_FAST) ? FALSE : TRUE), - thd->variables.lock_wait_timeout)) - result= 1; - - if (thd->global_read_lock.make_global_read_lock_block_commit(thd)) // Killed - { - /* Don't leave things in a half-locked state */ - thd->global_read_lock.unlock_global_read_lock(thd); - return 1; - } - } - else - { - if (thd && thd->locked_tables_mode) - { - /* - If we are under LOCK TABLES we should have a write - lock on tables which we are going to flush. - */ - if (tables) - { - for (TABLE_LIST *t= tables; t; t= t->next_local) - if (!find_table_for_mdl_upgrade(thd->open_tables, t->db, - t->table_name, FALSE)) - return 1; - } - else - { - for (TABLE *tab= thd->open_tables; tab; tab= tab->next) - { - if (! tab->mdl_ticket->is_upgradable_or_exclusive()) - { - my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), - tab->s->table_name.str); - return 1; - } - } - } - } - - if (close_cached_tables(thd, tables, - ((options & REFRESH_FAST) ? FALSE : TRUE), - (thd ? thd->variables.lock_wait_timeout : - LONG_TIMEOUT))) - result= 1; - } - my_dbopt_cleanup(); - } - if (options & REFRESH_HOSTS) - hostname_cache_refresh(); - if (thd && (options & REFRESH_STATUS)) - refresh_status(thd); - if (options & REFRESH_THREADS) - flush_thread_cache(); -#ifdef HAVE_REPLICATION - if (options & REFRESH_MASTER) - { - DBUG_ASSERT(thd); - tmp_write_to_binlog= 0; - if (reset_master(thd)) - { - result=1; - } - } -#endif -#ifdef OPENSSL - if (options & REFRESH_DES_KEY_FILE) - { - if (des_key_file && load_des_key_file(des_key_file)) - result= 1; - } -#endif -#ifdef HAVE_REPLICATION - if (options & REFRESH_SLAVE) - { - tmp_write_to_binlog= 0; - mysql_mutex_lock(&LOCK_active_mi); - if (reset_slave(thd, active_mi)) - result=1; - mysql_mutex_unlock(&LOCK_active_mi); - } -#endif - if (options & REFRESH_USER_RESOURCES) - reset_mqh((LEX_USER *) NULL, 0); /* purecov: inspected */ - *write_to_binlog= tmp_write_to_binlog; - /* - If the query was killed then this function must fail. - */ - return result || (thd ? thd->killed : 0); -} - - /** kill on thread. diff --git a/sql/sql_parse.h b/sql/sql_parse.h index 8b7fe8f7b83..fe7fbd9482e 100644 --- a/sql/sql_parse.h +++ b/sql/sql_parse.h @@ -93,8 +93,6 @@ void mysql_init_multi_delete(LEX *lex); bool multi_delete_set_locks_and_link_aux_tables(LEX *lex); void create_table_set_open_action_and_adjust_tables(LEX *lex); pthread_handler_t handle_bootstrap(void *arg); -bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, - bool *write_to_binlog); int mysql_execute_command(THD *thd); bool do_command(THD *thd); void do_handle_bootstrap(THD *thd); diff --git a/sql/sql_reload.cc b/sql/sql_reload.cc new file mode 100644 index 00000000000..bf38af78536 --- /dev/null +++ b/sql/sql_reload.cc @@ -0,0 +1,427 @@ +/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "sql_reload.h" +#include "sql_priv.h" +#include "mysqld.h" // select_errors +#include "sql_class.h" // THD +#include "sql_acl.h" // acl_reload +#include "sql_servers.h" // servers_reload +#include "sql_connect.h" // reset_mqh +#include "sql_base.h" // close_cached_tables +#include "sql_db.h" // my_dbopt_cleanup +#include "hostname.h" // hostname_cache_refresh +#include "sql_repl.h" // reset_master, reset_slave +#include "debug_sync.h" + + +/** + Reload/resets privileges and the different caches. + + @param thd Thread handler (can be NULL!) + @param options What should be reset/reloaded (tables, privileges, slave...) + @param tables Tables to flush (if any) + @param write_to_binlog True if we can write to the binlog. + + @note Depending on 'options', it may be very bad to write the + query to the binlog (e.g. FLUSH SLAVE); this is a + pointer where reload_acl_and_cache() will put 0 if + it thinks we really should not write to the binlog. + Otherwise it will put 1. + + @return Error status code + @retval 0 Ok + @retval !=0 Error; thd->killed is set or thd->is_error() is true +*/ + +bool reload_acl_and_cache(THD *thd, unsigned long options, + TABLE_LIST *tables, bool *write_to_binlog) +{ + bool result=0; + select_errors=0; /* Write if more errors */ + bool tmp_write_to_binlog= 1; + + DBUG_ASSERT(!thd || !thd->in_sub_stmt); + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (options & REFRESH_GRANT) + { + THD *tmp_thd= 0; + /* + If reload_acl_and_cache() is called from SIGHUP handler we have to + allocate temporary THD for execution of acl_reload()/grant_reload(). + */ + if (!thd && (thd= (tmp_thd= new THD))) + { + thd->thread_stack= (char*) &tmp_thd; + thd->store_globals(); + } + + if (thd) + { + bool reload_acl_failed= acl_reload(thd); + bool reload_grants_failed= grant_reload(thd); + bool reload_servers_failed= servers_reload(thd); + + if (reload_acl_failed || reload_grants_failed || reload_servers_failed) + { + result= 1; + /* + When an error is returned, my_message may have not been called and + the client will hang waiting for a response. + */ + my_error(ER_UNKNOWN_ERROR, MYF(0), "FLUSH PRIVILEGES failed"); + } + } + + if (tmp_thd) + { + delete tmp_thd; + /* Remember that we don't have a THD */ + my_pthread_setspecific_ptr(THR_THD, 0); + thd= 0; + } + reset_mqh((LEX_USER *)NULL, TRUE); + } +#endif + if (options & REFRESH_LOG) + { + /* + Flush the normal query log, the update log, the binary log, + the slow query log, the relay log (if it exists) and the log + tables. + */ + + options|= REFRESH_BINARY_LOG; + options|= REFRESH_RELAY_LOG; + options|= REFRESH_SLOW_LOG; + options|= REFRESH_GENERAL_LOG; + options|= REFRESH_ENGINE_LOG; + options|= REFRESH_ERROR_LOG; + } + + if (options & REFRESH_ERROR_LOG) + if (flush_error_log()) + result= 1; + + if ((options & REFRESH_SLOW_LOG) && opt_slow_log) + logger.flush_slow_log(); + + if ((options & REFRESH_GENERAL_LOG) && opt_log) + logger.flush_general_log(); + + if (options & REFRESH_ENGINE_LOG) + if (ha_flush_logs(NULL)) + result= 1; + + if (options & REFRESH_BINARY_LOG) + { + /* + Writing this command to the binlog may result in infinite loops + when doing mysqlbinlog|mysql, and anyway it does not really make + sense to log it automatically (would cause more trouble to users + than it would help them) + */ + tmp_write_to_binlog= 0; + if (mysql_bin_log.is_open()) + mysql_bin_log.rotate_and_purge(RP_FORCE_ROTATE); + } + if (options & REFRESH_RELAY_LOG) + { +#ifdef HAVE_REPLICATION + mysql_mutex_lock(&LOCK_active_mi); + rotate_relay_log(active_mi); + mysql_mutex_unlock(&LOCK_active_mi); +#endif + } +#ifdef HAVE_QUERY_CACHE + if (options & REFRESH_QUERY_CACHE_FREE) + { + query_cache.pack(); // FLUSH QUERY CACHE + options &= ~REFRESH_QUERY_CACHE; // Don't flush cache, just free memory + } + if (options & (REFRESH_TABLES | REFRESH_QUERY_CACHE)) + { + query_cache.flush(); // RESET QUERY CACHE + } +#endif /*HAVE_QUERY_CACHE*/ + + DBUG_ASSERT(!thd || thd->locked_tables_mode || + !thd->mdl_context.has_locks() || + thd->handler_tables_hash.records || + thd->global_read_lock.is_acquired()); + + /* + Note that if REFRESH_READ_LOCK bit is set then REFRESH_TABLES is set too + (see sql_yacc.yy) + */ + if (options & (REFRESH_TABLES | REFRESH_READ_LOCK)) + { + if ((options & REFRESH_READ_LOCK) && thd) + { + /* + On the first hand we need write lock on the tables to be flushed, + on the other hand we must not try to aspire a global read lock + if we have a write locked table as this would lead to a deadlock + when trying to reopen (and re-lock) the table after the flush. + */ + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + return 1; + } + /* + Writing to the binlog could cause deadlocks, as we don't log + UNLOCK TABLES + */ + tmp_write_to_binlog= 0; + if (thd->global_read_lock.lock_global_read_lock(thd)) + return 1; // Killed + if (close_cached_tables(thd, tables, + ((options & REFRESH_FAST) ? FALSE : TRUE), + thd->variables.lock_wait_timeout)) + result= 1; + + if (thd->global_read_lock.make_global_read_lock_block_commit(thd)) // Killed + { + /* Don't leave things in a half-locked state */ + thd->global_read_lock.unlock_global_read_lock(thd); + return 1; + } + } + else + { + if (thd && thd->locked_tables_mode) + { + /* + If we are under LOCK TABLES we should have a write + lock on tables which we are going to flush. + */ + if (tables) + { + for (TABLE_LIST *t= tables; t; t= t->next_local) + if (!find_table_for_mdl_upgrade(thd->open_tables, t->db, + t->table_name, FALSE)) + return 1; + } + else + { + for (TABLE *tab= thd->open_tables; tab; tab= tab->next) + { + if (! tab->mdl_ticket->is_upgradable_or_exclusive()) + { + my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), + tab->s->table_name.str); + return 1; + } + } + } + } + + if (close_cached_tables(thd, tables, + ((options & REFRESH_FAST) ? FALSE : TRUE), + (thd ? thd->variables.lock_wait_timeout : + LONG_TIMEOUT))) + result= 1; + } + my_dbopt_cleanup(); + } + if (options & REFRESH_HOSTS) + hostname_cache_refresh(); + if (thd && (options & REFRESH_STATUS)) + refresh_status(thd); + if (options & REFRESH_THREADS) + flush_thread_cache(); +#ifdef HAVE_REPLICATION + if (options & REFRESH_MASTER) + { + DBUG_ASSERT(thd); + tmp_write_to_binlog= 0; + if (reset_master(thd)) + { + result=1; + } + } +#endif +#ifdef OPENSSL + if (options & REFRESH_DES_KEY_FILE) + { + if (des_key_file && load_des_key_file(des_key_file)) + result= 1; + } +#endif +#ifdef HAVE_REPLICATION + if (options & REFRESH_SLAVE) + { + tmp_write_to_binlog= 0; + mysql_mutex_lock(&LOCK_active_mi); + if (reset_slave(thd, active_mi)) + result=1; + mysql_mutex_unlock(&LOCK_active_mi); + } +#endif + if (options & REFRESH_USER_RESOURCES) + reset_mqh((LEX_USER *) NULL, 0); /* purecov: inspected */ + *write_to_binlog= tmp_write_to_binlog; + /* + If the query was killed then this function must fail. + */ + return result || (thd ? thd->killed : 0); +} + + +/** + Implementation of FLUSH TABLES WITH READ LOCK. + + In brief: take exclusive locks, expel tables from the table + cache, reopen the tables, enter the 'LOCKED TABLES' mode, + downgrade the locks. + Note: the function is written to be called from + mysql_execute_command(), it is not reusable in arbitrary + execution context. + + Required privileges + ------------------- + Since the statement implicitly enters LOCK TABLES mode, + it requires LOCK TABLES privilege on every table. + But since the rest of FLUSH commands require + the global RELOAD_ACL, it also requires RELOAD_ACL. + + Compatibility with the global read lock + --------------------------------------- + We don't wait for the GRL, since neither the + 5.1 combination that this new statement is intended to + replace (LOCK TABLE WRITE; FLUSH TABLES;), + nor FLUSH TABLES WITH READ LOCK do. + @todo: this is not implemented, Dmitry disagrees. + Currently we wait for GRL in another connection, + but are compatible with a GRL in our own connection. + + Behaviour under LOCK TABLES + --------------------------- + Bail out: i.e. don't perform an implicit UNLOCK TABLES. + This is not consistent with LOCK TABLES statement, but is + in line with behaviour of FLUSH TABLES WITH READ LOCK, and we + try to not introduce any new statements with implicit + semantics. + + Compatibility with parallel updates + ----------------------------------- + As a result, we will wait for all open transactions + against the tables to complete. After the lock downgrade, + new transactions will be able to read the tables, but not + write to them. + + Differences from FLUSH TABLES + ------------------------------------- + - you can't flush WITH READ LOCK a non-existent table + - you can't flush WITH READ LOCK under LOCK TABLES + - currently incompatible with the GRL (@todo: fix) + + Effect on views and temporary tables. + ------------------------------------ + You can only apply this command to existing base tables. + If a view with such name exists, ER_WRONG_OBJECT is returned. + If a temporary table with such name exists, it's ignored: + if there is a base table, it's used, otherwise ER_NO_SUCH_TABLE + is returned. + + Implicit commit + --------------- + This statement causes an implicit commit before and + after it. + + HANDLER SQL + ----------- + If this connection has HANDLERs open against + some of the tables being FLUSHed, these handlers + are implicitly flushed (lose their position). +*/ + +bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables) +{ + Lock_tables_prelocking_strategy lock_tables_prelocking_strategy; + TABLE_LIST *table_list; + MDL_request_list mdl_requests; + + /* + This is called from SQLCOM_FLUSH, the transaction has + been committed implicitly. + */ + + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + goto error; + } + + /* + Acquire SNW locks on tables to be flushed. We can't use + lock_table_names() here as this call will also acquire global IX + and database-scope IX locks on the tables, and this will make + this statement incompatible with FLUSH TABLES WITH READ LOCK. + */ + for (table_list= all_tables; table_list; + table_list= table_list->next_global) + mdl_requests.push_front(&table_list->mdl_request); + + if (thd->mdl_context.acquire_locks(&mdl_requests, + thd->variables.lock_wait_timeout)) + goto error; + + DEBUG_SYNC(thd,"flush_tables_with_read_lock_after_acquire_locks"); + + for (table_list= all_tables; table_list; + table_list= table_list->next_global) + { + /* Request removal of table from cache. */ + tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, + table_list->db, + table_list->table_name, FALSE); + + /* Skip views and temporary tables. */ + table_list->required_type= FRMTYPE_TABLE; /* Don't try to flush views. */ + table_list->open_type= OT_BASE_ONLY; /* Ignore temporary tables. */ + } + + /* + Before opening and locking tables the below call also waits + for old shares to go away, so the fact that we don't pass + MYSQL_LOCK_IGNORE_FLUSH flag to it is important. + */ + if (open_and_lock_tables(thd, all_tables, FALSE, + MYSQL_OPEN_HAS_MDL_LOCK, + &lock_tables_prelocking_strategy) || + thd->locked_tables_list.init_locked_tables(thd)) + { + goto error; + } + thd->variables.option_bits|= OPTION_TABLE_LOCK; + + /* + We don't downgrade MDL_SHARED_NO_WRITE here as the intended + post effect of this call is identical to LOCK TABLES <...> READ, + and we didn't use thd->in_lock_talbes and + thd->sql_command= SQLCOM_LOCK_TABLES hacks to enter the LTM. + */ + + return FALSE; + +error: + return TRUE; +} + + + diff --git a/sql/sql_reload.h b/sql/sql_reload.h new file mode 100644 index 00000000000..0df5485c907 --- /dev/null +++ b/sql/sql_reload.h @@ -0,0 +1,26 @@ +#ifndef SQL_RELOAD_INCLUDED +#define SQL_RELOAD_INCLUDED +/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +class THD; +struct TABLE_LIST; + +bool reload_acl_and_cache(THD *thd, unsigned long options, + TABLE_LIST *tables, bool *write_to_binlog); + +bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables); + +#endif -- cgit v1.2.1 From 827a89996a06255b471ae9f24d3f3d3cbf9f99fd Mon Sep 17 00:00:00 2001 From: Evgeny Potemkin Date: Sat, 14 Aug 2010 13:11:33 +0400 Subject: Bug#49746: Const expression caching led to NDB not using engine condition pushdown. NDB supports only a limited set of item nodes for use in engine condition pushdown. Because of this adding cache for const expression effectively disabled this optimization. The ndb_serialize_cond function is extended to support Item_cache and treat it as a constant values. A helper function called ndb_serialize_const is added. It is used to create Ndb_cond value node from given const item. mysql-test/suite/ndb/t/disabled.def: Bug#49746: Const expression caching led to NDB not using engine condition pushdown. Enabled ndb_condition_pushdown test after fixing appropriate bug. sql/ha_ndbcluster_cond.cc: Bug#49746: Const expression caching led to NDB not using engine condition pushdown. The ndb_serialize_cond function is extended to support Item_cache and treat it as a constant values. A helper function called ndb_serialize_const is added. It is used to create Ndb_cond value node from given const item. sql/item.cc: Bug#49746: Const expression caching led to NDB not using engine condition pushdown. The Item::cache_const_expr_analyzer function is adjusted to not create cache for Item_int_with_ref objects. sql/item.h: Bug#49746: Const expression caching led to NDB not using engine condition pushdown. The result_type() method is added to Item_cache class. The Item_cache_str now initializes its collation. --- sql/ha_ndbcluster_cond.cc | 227 +++++++++++++++++++++++++--------------------- sql/item.cc | 3 +- sql/item.h | 10 +- 3 files changed, 135 insertions(+), 105 deletions(-) (limited to 'sql') diff --git a/sql/ha_ndbcluster_cond.cc b/sql/ha_ndbcluster_cond.cc index 6df1f4881c3..8a96ae41453 100644 --- a/sql/ha_ndbcluster_cond.cc +++ b/sql/ha_ndbcluster_cond.cc @@ -35,6 +35,110 @@ typedef NdbDictionary::Column NDBCOL; typedef NdbDictionary::Table NDBTAB; + +/** + Serialize a constant item into a Ndb_cond node. + + @param const_type item's result type + @param item item to be serialized + @param curr_cond Ndb_cond node the item to be serialized into + @param context Traverse context +*/ + +static void ndb_serialize_const(Item_result const_type, const Item *item, + Ndb_cond *curr_cond, + Ndb_cond_traverse_context *context) +{ + DBUG_ASSERT(item->const_item()); + switch (const_type) { + case STRING_RESULT: + { + NDB_ITEM_QUALIFICATION q; + q.value_type= Item::STRING_ITEM; + curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); + if (! context->expecting_no_field_result()) + { + // We have not seen the field argument yet + context->expect_only(Item::FIELD_ITEM); + context->expect_only_field_result(STRING_RESULT); + context->expect_collation(item->collation.collation); + } + else + { + // Expect another logical expression + context->expect_only(Item::FUNC_ITEM); + context->expect(Item::COND_ITEM); + // Check that string result have correct collation + if (!context->expecting_collation(item->collation.collation)) + { + DBUG_PRINT("info", ("Found non-matching collation %s", + item->collation.collation->name)); + context->supported= FALSE; + } + } + break; + } + case REAL_RESULT: + { + NDB_ITEM_QUALIFICATION q; + q.value_type= Item::REAL_ITEM; + curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); + if (! context->expecting_no_field_result()) + { + // We have not seen the field argument yet + context->expect_only(Item::FIELD_ITEM); + context->expect_only_field_result(REAL_RESULT); + } + else + { + // Expect another logical expression + context->expect_only(Item::FUNC_ITEM); + context->expect(Item::COND_ITEM); + } + break; + } + case INT_RESULT: + { + NDB_ITEM_QUALIFICATION q; + q.value_type= Item::INT_ITEM; + curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); + if (! context->expecting_no_field_result()) + { + // We have not seen the field argument yet + context->expect_only(Item::FIELD_ITEM); + context->expect_only_field_result(INT_RESULT); + } + else + { + // Expect another logical expression + context->expect_only(Item::FUNC_ITEM); + context->expect(Item::COND_ITEM); + } + break; + } + case DECIMAL_RESULT: + { + NDB_ITEM_QUALIFICATION q; + q.value_type= Item::DECIMAL_ITEM; + curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); + if (! context->expecting_no_field_result()) + { + // We have not seen the field argument yet + context->expect_only(Item::FIELD_ITEM); + context->expect_only_field_result(DECIMAL_RESULT); + } + else + { + // Expect another logical expression + context->expect_only(Item::FUNC_ITEM); + context->expect(Item::COND_ITEM); + } + break; + } + default: + break; + } +} /* Serialize the item tree into a linked list represented by Ndb_cond for fast generation of NbdScanFilter. Adds information such as @@ -113,7 +217,7 @@ void ndb_serialize_cond(const Item *item, void *arg) to ndb_serialize_cond and end of rewrite statement is wrapped in end of ndb_serialize_cond */ - if (context->expecting(item->type())) + if (context->expecting(item->type()) || item->const_item()) { // This is the | item, save it in the rewrite context rewrite_context2->left_hand_item= item; @@ -597,108 +701,12 @@ void ndb_serialize_cond(const Item *item, void *arg) DBUG_PRINT("info", ("result type %d", func_item->result_type())); if (func_item->const_item()) { - switch (func_item->result_type()) { - case STRING_RESULT: - { - NDB_ITEM_QUALIFICATION q; - q.value_type= Item::STRING_ITEM; - curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); - if (! context->expecting_no_field_result()) - { - // We have not seen the field argument yet - context->expect_only(Item::FIELD_ITEM); - context->expect_only_field_result(STRING_RESULT); - context->expect_collation(func_item->collation.collation); - } - else - { - // Expect another logical expression - context->expect_only(Item::FUNC_ITEM); - context->expect(Item::COND_ITEM); - // Check that string result have correct collation - if (!context->expecting_collation(item->collation.collation)) - { - DBUG_PRINT("info", ("Found non-matching collation %s", - item->collation.collation->name)); - context->supported= FALSE; - } - } - // Skip any arguments since we will evaluate function instead - DBUG_PRINT("info", ("Skip until end of arguments marker")); - context->skip= func_item->argument_count(); - break; - } - case REAL_RESULT: - { - NDB_ITEM_QUALIFICATION q; - q.value_type= Item::REAL_ITEM; - curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); - if (! context->expecting_no_field_result()) - { - // We have not seen the field argument yet - context->expect_only(Item::FIELD_ITEM); - context->expect_only_field_result(REAL_RESULT); - } - else - { - // Expect another logical expression - context->expect_only(Item::FUNC_ITEM); - context->expect(Item::COND_ITEM); - } - - // Skip any arguments since we will evaluate function instead - DBUG_PRINT("info", ("Skip until end of arguments marker")); - context->skip= func_item->argument_count(); - break; - } - case INT_RESULT: - { - NDB_ITEM_QUALIFICATION q; - q.value_type= Item::INT_ITEM; - curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); - if (! context->expecting_no_field_result()) - { - // We have not seen the field argument yet - context->expect_only(Item::FIELD_ITEM); - context->expect_only_field_result(INT_RESULT); - } - else - { - // Expect another logical expression - context->expect_only(Item::FUNC_ITEM); - context->expect(Item::COND_ITEM); - } - - // Skip any arguments since we will evaluate function instead - DBUG_PRINT("info", ("Skip until end of arguments marker")); - context->skip= func_item->argument_count(); - break; - } - case DECIMAL_RESULT: - { - NDB_ITEM_QUALIFICATION q; - q.value_type= Item::DECIMAL_ITEM; - curr_cond->ndb_item= new Ndb_item(NDB_VALUE, q, item); - if (! context->expecting_no_field_result()) - { - // We have not seen the field argument yet - context->expect_only(Item::FIELD_ITEM); - context->expect_only_field_result(DECIMAL_RESULT); - } - else - { - // Expect another logical expression - context->expect_only(Item::FUNC_ITEM); - context->expect(Item::COND_ITEM); - } - // Skip any arguments since we will evaluate function instead - DBUG_PRINT("info", ("Skip until end of arguments marker")); - context->skip= func_item->argument_count(); - break; - } - default: - break; - } + ndb_serialize_const(func_item->result_type(), item, curr_cond, + context); + + // Skip any arguments since we will evaluate function instead + DBUG_PRINT("info", ("Skip until end of arguments marker")); + context->skip= func_item->argument_count(); } else // Function does not return constant expression @@ -883,6 +891,19 @@ void ndb_serialize_cond(const Item *item, void *arg) } break; } + case Item::CACHE_ITEM: + { + DBUG_PRINT("info", ("CACHE_ITEM")); + if (item->const_item()) + { + ndb_serialize_const(((Item_cache*)item)->result_type(), item, + curr_cond, context); + } + else + context->supported= FALSE; + + break; + } default: { DBUG_PRINT("info", ("Found item of type %d", item->type())); diff --git a/sql/item.cc b/sql/item.cc index 1decb5ec426..24f57342668 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -5931,7 +5931,8 @@ bool Item::cache_const_expr_analyzer(uchar **arg) a subselect (they use their own cache). */ if (const_item() && - !(item->basic_const_item() || item->type() == Item::FIELD_ITEM || + !(basic_const_item() || item->basic_const_item() || + item->type() == Item::FIELD_ITEM || item->type() == SUBSELECT_ITEM || /* Do not cache GET_USER_VAR() function as its const_item() may diff --git a/sql/item.h b/sql/item.h index c7a97ca716a..d1957c46d25 100644 --- a/sql/item.h +++ b/sql/item.h @@ -3266,6 +3266,12 @@ public: bool basic_const_item() const { return test(example && example->basic_const_item());} virtual void clear() { null_value= TRUE; value_cached= FALSE; } + Item_result result_type() const + { + if (!example) + return INT_RESULT; + return Field::result_merge_type(example->field_type()); + } }; @@ -3335,7 +3341,9 @@ public: is_varbinary(item->type() == FIELD_ITEM && cached_field_type == MYSQL_TYPE_VARCHAR && !((const Item_field *) item)->field->has_charset()) - {} + { + collation.set(const_cast(item->collation)); + } double val_real(); longlong val_int(); String* val_str(String *); -- cgit v1.2.1 From 4b20ccafaa3bacfd11c5fcc61e1647dbbefe7bad Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Mon, 16 Aug 2010 14:53:30 +0200 Subject: Bug#49907: ALTER TABLE ... TRUNCATE PARTITION does not wait for locks on the table Fixing the partitioning specifics after TRUNCATE TABLE in bug-42643 was fixed. Reorganize of code to decrease the size of the giant switch in mysql_execute_command, and to prepare for future parser reengineering. Moved code into Sql_statement objects. Updated patch according to davi's review comments. libmysqld/CMakeLists.txt: Added new files. libmysqld/Makefile.am: Added new files. mysql-test/r/not_partition.result: now returning error on partitioning commands if partitioning is not enabled. mysql-test/r/partition_disabled.result: There is no partition handlerton, so it cannot find the specified engine in the .frm file. mysql-test/r/partition_truncate.result: Updated test results. mysql-test/suite/parts/inc/partition_mgm.inc: Added check that TRUNCATE PARTITION does not delete on failure. mysql-test/suite/parts/r/partition_debug_sync_innodb.result: updated results. mysql-test/suite/parts/r/partition_mgm_lc0_archive.result: updated results. mysql-test/suite/parts/r/partition_mgm_lc1_archive.result: updated results. mysql-test/suite/parts/r/partition_mgm_lc2_archive.result: updated results. mysql-test/suite/parts/t/partition_debug_sync_innodb.test: Test case for this bug. mysql-test/t/not_partition.test: Added check for TRUNCATE PARTITION without partitioning. mysql-test/t/partition_truncate.test: Added test of TRUNCATE PARTITION on non partitioned table. sql/CMakeLists.txt: Added new files. sql/Makefile.am: Added new files. sql/datadict.cc: Moved out the storage engine check into an own function, including assert for lock. sql/datadict.h: added dd_frm_storage_engine. sql/sql_alter_table.cc: moved the code for SQLCOM_ALTER_TABLE in mysql_execute_command into its own file, and using the Sql_statement object to prepare for future parser reengineering. sql/sql_alter_table.h: Created Sql_statement object for ALTER TABLE. sql/sql_lex.cc: resetting m_stmt. sql/sql_lex.h: Temporary hack for forward declaration of enum_alter_table_change_level. sql/sql_parse.cc: Moved out ALTER/ANALYZE/CHECK/OPTIMIZE/REPAIR TABLE from the giant switch into their own Sql_statement objects. sql/sql_parse.h: Exporting check_merge_table_access. sql/sql_partition_admin.cc: created Sql_statement for ALTER TABLE t ANALYZE/CHECK/OPTIMIZE/REPAIR/TRUNCATE PARTITION. To be able to reuse the TABLE equivalents. sql/sql_partition_admin.h: Added Sql_statement of partition admin statements. sql/sql_table.cc: Moved table maintenance code into sql_table_maintenance.cc sql/sql_table.h: Moved table maintenance code into sql_table_maintenance.h exporting functions used by sql_table_maintenance. sql/sql_table_maintenance.cc: Moved table maintenance code from sql_table.cc sql/sql_table_maintenance.h: Sql_statement objects for ANALYZE/CHECK/OPTIMIZE/REPAIR TABLE. Also declaring the keycache functions. sql/sql_truncate.cc: Moved code from SQLCOM_TRUNCATE in mysql_execute_command into Truncate_statement::execute. Added check for partitioned table on TRUNCATE PARTITION. Moved locking fix for partitioned table into Alter_table_truncate_partition::execute. sql/sql_truncate.h: Truncate_statement declaration (sub class of Sql_statement). sql/sql_yacc.yy: Using the new Sql_statment objects. --- sql/CMakeLists.txt | 6 +- sql/Makefile.am | 9 +- sql/datadict.cc | 67 ++- sql/datadict.h | 2 + sql/sql_alter_table.cc | 106 +++++ sql/sql_alter_table.h | 66 +++ sql/sql_lex.cc | 1 + sql/sql_lex.h | 18 + sql/sql_parse.cc | 176 +------- sql/sql_parse.h | 1 + sql/sql_partition_admin.cc | 139 ++++++ sql/sql_partition_admin.h | 236 ++++++++++ sql/sql_table.cc | 953 +-------------------------------------- sql/sql_table.h | 30 +- sql/sql_table_maintenance.cc | 1004 ++++++++++++++++++++++++++++++++++++++++++ sql/sql_table_maintenance.h | 132 ++++++ sql/sql_truncate.cc | 50 ++- sql/sql_truncate.h | 26 ++ sql/sql_yacc.yy | 114 ++++- 19 files changed, 1970 insertions(+), 1166 deletions(-) create mode 100644 sql/sql_alter_table.cc create mode 100644 sql/sql_alter_table.h create mode 100644 sql/sql_partition_admin.cc create mode 100644 sql/sql_partition_admin.h create mode 100644 sql/sql_table_maintenance.cc create mode 100644 sql/sql_table_maintenance.h (limited to 'sql') diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 943d6b2eece..1a162d39e0b 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -71,9 +71,9 @@ SET (SQL_SOURCE sql_tablespace.cc events.cc ../sql-common/my_user.c partition_info.cc rpl_utility.cc rpl_injector.cc sql_locale.cc rpl_rli.cc rpl_mi.cc sql_servers.cc sql_audit.cc - sql_connect.cc scheduler.cc - sql_profile.cc event_parse_data.cc - sql_signal.cc rpl_handler.cc mdl.cc + sql_connect.cc scheduler.cc sql_partition_admin.cc + sql_profile.cc event_parse_data.cc sql_alter_table.cc + sql_signal.cc rpl_handler.cc mdl.cc sql_table_maintenance.cc transaction.cc sys_vars.cc sql_truncate.cc datadict.cc ${GEN_SOURCES} ${MYSYS_LIBWRAP_SOURCE}) diff --git a/sql/Makefile.am b/sql/Makefile.am index 7fed55f3cd6..b99afcbdbeb 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -122,11 +122,11 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.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_audit.h sql_alter_table.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 \ - datadict.h + sql_table_maintenance.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 \ @@ -143,7 +143,8 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.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_truncate.cc \ + procedure.cc sql_test.cc sql_table_maintenance.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 \ @@ -171,7 +172,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.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 \ - sha2.cc + sql_alter_table.cc sql_partition_admin.cc sha2.cc nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c diff --git a/sql/datadict.cc b/sql/datadict.cc index 33c3b6bc700..362435a94db 100644 --- a/sql/datadict.cc +++ b/sql/datadict.cc @@ -22,7 +22,9 @@ /** Check type of .frm if we are not going to parse it. - @param path path to FRM file + @param[in] thd The current session. + @param[in] path path to FRM file. + @param[out] dbt db_type of the table if FRMTYPE_TABLE, otherwise undefined. @retval FRMTYPE_ERROR error @retval FRMTYPE_TABLE table @@ -66,29 +68,28 @@ frm_type_enum dd_frm_type(THD *thd, char *path, enum legacy_db_type *dbt) /** - Given a table name, check if the storage engine for the - table referred by this name supports an option 'flag'. - Return an error if the table does not exist or is not a - base table. + Given a table name, check type of .frm and legacy table type. - @pre Any metadata lock on the table. + @param[in] thd The current session. + @param[in] db Table schema. + @param[in] table_name Table database. + @param[out] table_type handlerton of the table if FRMTYPE_TABLE, + otherwise undefined. - @param[in] thd The current session. - @param[in] db Table schema. - @param[in] table_name Table database. - @param[in] flag The option to check. - @param[out] yes_no The result. Undefined if error. + @return FALSE if FRMTYPE_TABLE and storage engine found. TRUE otherwise. */ -bool dd_check_storage_engine_flag(THD *thd, - const char *db, const char *table_name, - uint32 flag, bool *yes_no) +bool dd_frm_storage_engine(THD *thd, const char *db, const char *table_name, + handlerton **table_type) { char path[FN_REFLEN + 1]; enum legacy_db_type db_type; - handlerton *table_type; LEX_STRING db_name = {(char *) db, strlen(db)}; + /* There should be at least some lock on the table. */ + DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, + table_name, MDL_SHARED)); + if (check_db_name(&db_name)) { my_error(ER_WRONG_DB_NAME, MYF(0), db_name.str); @@ -101,23 +102,47 @@ bool dd_check_storage_engine_flag(THD *thd, return TRUE; } - /* There should be at least some lock on the table. */ - DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, - table_name, MDL_SHARED)); - (void) build_table_filename(path, sizeof(path) - 1, db, table_name, reg_ext, 0); dd_frm_type(thd, path, &db_type); /* Type is unknown if the object is not found or is not a table. */ - if (db_type == DB_TYPE_UNKNOWN) + if (db_type == DB_TYPE_UNKNOWN || + !(*table_type= ha_resolve_by_legacy_type(thd, db_type))) { my_error(ER_NO_SUCH_TABLE, MYF(0), db, table_name); return TRUE; } - table_type= ha_resolve_by_legacy_type(thd, db_type); + return FALSE; +} + + +/** + Given a table name, check if the storage engine for the + table referred by this name supports an option 'flag'. + Return an error if the table does not exist or is not a + base table. + + @pre Any metadata lock on the table. + + @param[in] thd The current session. + @param[in] db Table schema. + @param[in] table_name Table database. + @param[in] flag The option to check. + @param[out] yes_no The result. Undefined if error. +*/ + +bool dd_check_storage_engine_flag(THD *thd, + const char *db, const char *table_name, + uint32 flag, bool *yes_no) +{ + handlerton *table_type; + + if (dd_frm_storage_engine(thd, db, table_name, &table_type)) + return TRUE; + *yes_no= ha_check_storage_engine_flag(table_type, flag); return FALSE; diff --git a/sql/datadict.h b/sql/datadict.h index 05b5a9bba4b..65e40998929 100644 --- a/sql/datadict.h +++ b/sql/datadict.h @@ -31,6 +31,8 @@ enum frm_type_enum frm_type_enum dd_frm_type(THD *thd, char *path, enum legacy_db_type *dbt); +bool dd_frm_storage_engine(THD *thd, const char *db, const char *table_name, + handlerton **table_type); bool dd_check_storage_engine_flag(THD *thd, const char *db, const char *table_name, uint32 flag, diff --git a/sql/sql_alter_table.cc b/sql/sql_alter_table.cc new file mode 100644 index 00000000000..607a8d5f5df --- /dev/null +++ b/sql/sql_alter_table.cc @@ -0,0 +1,106 @@ +/* Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "sql_parse.h" // check_access, + // check_merge_table_access +#include "sql_table.h" // mysql_alter_table, + // mysql_exchange_partition +#include "sql_alter_table.h" + +bool Alter_table_statement::execute(THD *thd) +{ + LEX *lex= thd->lex; + /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ + SELECT_LEX *select_lex= &lex->select_lex; + /* first table of first SELECT_LEX */ + TABLE_LIST *first_table= (TABLE_LIST*) select_lex->table_list.first; + /* + Code in mysql_alter_table() may modify its HA_CREATE_INFO argument, + so we have to use a copy of this structure to make execution + prepared statement- safe. A shallow copy is enough as no memory + referenced from this structure will be modified. + @todo move these into constructor... + */ + HA_CREATE_INFO create_info(lex->create_info); + Alter_info alter_info(lex->alter_info, thd->mem_root); + ulong priv=0; + ulong priv_needed= ALTER_ACL; + bool result; + + DBUG_ENTER("Alter_table_statement::execute"); + + if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */ + DBUG_RETURN(TRUE); + /* + We also require DROP priv for ALTER TABLE ... DROP PARTITION, as well + as for RENAME TO, as being done by SQLCOM_RENAME_TABLE + */ + if (alter_info.flags & (ALTER_DROP_PARTITION | ALTER_RENAME)) + priv_needed|= DROP_ACL; + + /* Must be set in the parser */ + DBUG_ASSERT(select_lex->db); + DBUG_ASSERT(!(alter_info.flags & ALTER_ADMIN_PARTITION)); + if (check_access(thd, priv_needed, first_table->db, + &first_table->grant.privilege, + &first_table->grant.m_internal, + 0, 0) || + check_access(thd, INSERT_ACL | CREATE_ACL, select_lex->db, + &priv, + NULL, /* Don't use first_tab->grant with sel_lex->db */ + 0, 0) || + check_merge_table_access(thd, first_table->db, + create_info.merge_list.first)) + DBUG_RETURN(TRUE); /* purecov: inspected */ + + if (check_grant(thd, priv_needed, first_table, FALSE, UINT_MAX, FALSE)) + DBUG_RETURN(TRUE); /* purecov: inspected */ + + if (lex->name.str && !test_all_bits(priv, INSERT_ACL | CREATE_ACL)) + { + // Rename of table + TABLE_LIST tmp_table; + bzero((char*) &tmp_table,sizeof(tmp_table)); + tmp_table.table_name= lex->name.str; + tmp_table.db= select_lex->db; + tmp_table.grant.privilege= priv; + if (check_grant(thd, INSERT_ACL | CREATE_ACL, &tmp_table, FALSE, + UINT_MAX, FALSE)) + DBUG_RETURN(TRUE); /* purecov: inspected */ + } + + /* Don't yet allow changing of symlinks with ALTER TABLE */ + if (create_info.data_file_name) + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), + "DATA DIRECTORY"); + if (create_info.index_file_name) + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), + "INDEX DIRECTORY"); + create_info.data_file_name= create_info.index_file_name= NULL; + + thd->enable_slow_log= opt_log_slow_admin_statements; + + result= mysql_alter_table(thd, select_lex->db, lex->name.str, + &create_info, + first_table, + &alter_info, + select_lex->order_list.elements, + select_lex->order_list.first, + lex->ignore); + + DBUG_RETURN(result); +} diff --git a/sql/sql_alter_table.h b/sql/sql_alter_table.h new file mode 100644 index 00000000000..6a17f87f5a4 --- /dev/null +++ b/sql/sql_alter_table.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifndef SQL_ALTER_TABLE_H +#define SQL_ALTER_TABLE_H + +/** + Alter_table_common represents the common properties of the ALTER TABLE + statements. + @todo move Alter_info and other ALTER generic structures from Lex here. +*/ +class Alter_table_common : public Sql_statement +{ +protected: + /** + Constructor. + @param lex the LEX structure for this statement. + */ + Alter_table_common(LEX *lex) + : Sql_statement(lex) + {} + + virtual ~Alter_table_common() + {} + +}; + +/** + Alter_table_statement represents the generic ALTER TABLE statement. + @todo move Alter_info and other ALTER specific structures from Lex here. +*/ +class Alter_table_statement : public Alter_table_common +{ +public: + /** + Constructor, used to represent a ALTER TABLE statement. + @param lex the LEX structure for this statement. + */ + Alter_table_statement(LEX *lex) + : Alter_table_common(lex) + {} + + ~Alter_table_statement() + {} + + /** + Execute a ALTER TABLE statement at runtime. + @param thd the current thread. + @return false on success. + */ + bool execute(THD *thd); +}; + +#endif diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index b70f33a6c3f..140cdb21eef 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -401,6 +401,7 @@ void lex_start(THD *thd) lex->spname= NULL; lex->sphead= NULL; lex->spcont= NULL; + lex->m_stmt= NULL; lex->proc_list.first= 0; lex->escape_used= FALSE; lex->query_tables= 0; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index e6c4e69d1a6..d0f20d4e997 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -919,6 +919,24 @@ enum enum_alter_table_change_level ALTER_TABLE_INDEX_CHANGED= 2 }; + +/** + Temporary hack to enable a class bound forward declaration + of the enum_alter_table_change_level enumeration. To be + removed once Alter_info is moved to the sql_alter_table.h + header. +*/ +class Alter_table_change_level +{ +private: + typedef enum enum_alter_table_change_level enum_type; + enum_type value; +public: + void operator = (enum_type v) { value = v; } + operator enum_type () { return value; } +}; + + /** @brief Parsing data for CREATE or ALTER TABLE. diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index fbcfb3f49c2..2f02188d122 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -50,6 +50,7 @@ // mysql_backup_table, // mysql_restore_table #include "sql_truncate.h" // mysql_truncate_table +#include "sql_table_maintenance.h" // mysql_assign_to_keycache #include "sql_connect.h" // check_user, // decrease_user_connections, // thd_init_client_charset, check_mqh, @@ -659,8 +660,7 @@ end: every child. Set 'db' for every child if not present. */ #ifndef NO_EMBEDDED_ACCESS_CHECKS -static bool check_merge_table_access(THD *thd, char *db, - TABLE_LIST *table_list) +bool check_merge_table_access(THD *thd, char *db, TABLE_LIST *table_list) { int error= 0; @@ -2831,77 +2831,6 @@ end_with_restore_list: } #endif /* HAVE_REPLICATION */ - case SQLCOM_ALTER_TABLE: - DBUG_ASSERT(first_table == all_tables && first_table != 0); - { - ulong priv=0; - ulong priv_needed= ALTER_ACL; - /* - Code in mysql_alter_table() may modify its HA_CREATE_INFO argument, - so we have to use a copy of this structure to make execution - prepared statement- safe. A shallow copy is enough as no memory - referenced from this structure will be modified. - */ - HA_CREATE_INFO create_info(lex->create_info); - Alter_info alter_info(lex->alter_info, thd->mem_root); - - if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */ - goto error; - /* - We also require DROP priv for ALTER TABLE ... DROP PARTITION, as well - as for RENAME TO, as being done by SQLCOM_RENAME_TABLE - */ - if (alter_info.flags & (ALTER_DROP_PARTITION | ALTER_RENAME)) - priv_needed|= DROP_ACL; - - /* Must be set in the parser */ - DBUG_ASSERT(select_lex->db); - if (check_access(thd, priv_needed, first_table->db, - &first_table->grant.privilege, - &first_table->grant.m_internal, - 0, 0) || - check_access(thd, INSERT_ACL | CREATE_ACL, select_lex->db, - &priv, - NULL, /* Do not use first_table->grant with select_lex->db */ - 0, 0) || - check_merge_table_access(thd, first_table->db, - create_info.merge_list.first)) - goto error; /* purecov: inspected */ - if (check_grant(thd, priv_needed, all_tables, FALSE, UINT_MAX, FALSE)) - goto error; - if (lex->name.str && !test_all_bits(priv,INSERT_ACL | CREATE_ACL)) - { // Rename of table - TABLE_LIST tmp_table; - bzero((char*) &tmp_table,sizeof(tmp_table)); - tmp_table.table_name= lex->name.str; - tmp_table.db=select_lex->db; - tmp_table.grant.privilege=priv; - if (check_grant(thd, INSERT_ACL | CREATE_ACL, &tmp_table, FALSE, - UINT_MAX, FALSE)) - goto error; - } - - /* Don't yet allow changing of symlinks with ALTER TABLE */ - if (create_info.data_file_name) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), - "DATA DIRECTORY"); - if (create_info.index_file_name) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), - "INDEX DIRECTORY"); - create_info.data_file_name= create_info.index_file_name= NULL; - - thd->enable_slow_log= opt_log_slow_admin_statements; - res= mysql_alter_table(thd, select_lex->db, lex->name.str, - &create_info, - first_table, - &alter_info, - select_lex->order_list.elements, - select_lex->order_list.first, - lex->ignore); - break; - } case SQLCOM_RENAME_TABLE: { DBUG_ASSERT(first_table == all_tables && first_table != 0); @@ -3021,81 +2950,6 @@ end_with_restore_list: res = mysql_checksum_table(thd, first_table, &lex->check_opt); break; } - case SQLCOM_REPAIR: - { - DBUG_ASSERT(first_table == all_tables && first_table != 0); - if (check_table_access(thd, SELECT_ACL | INSERT_ACL, all_tables, - FALSE, UINT_MAX, FALSE)) - goto error; /* purecov: inspected */ - thd->enable_slow_log= opt_log_slow_admin_statements; - res= mysql_repair_table(thd, first_table, &lex->check_opt); - /* ! we write after unlocking the table */ - if (!res && !lex->no_write_to_binlog) - { - /* - Presumably, REPAIR and binlog writing doesn't require synchronization - */ - res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - } - select_lex->table_list.first= first_table; - lex->query_tables=all_tables; - break; - } - case SQLCOM_CHECK: - { - DBUG_ASSERT(first_table == all_tables && first_table != 0); - if (check_table_access(thd, SELECT_ACL, all_tables, - TRUE, UINT_MAX, FALSE)) - goto error; /* purecov: inspected */ - thd->enable_slow_log= opt_log_slow_admin_statements; - res = mysql_check_table(thd, first_table, &lex->check_opt); - select_lex->table_list.first= first_table; - lex->query_tables=all_tables; - break; - } - case SQLCOM_ANALYZE: - { - DBUG_ASSERT(first_table == all_tables && first_table != 0); - if (check_table_access(thd, SELECT_ACL | INSERT_ACL, all_tables, - FALSE, UINT_MAX, FALSE)) - goto error; /* purecov: inspected */ - thd->enable_slow_log= opt_log_slow_admin_statements; - res= mysql_analyze_table(thd, first_table, &lex->check_opt); - /* ! we write after unlocking the table */ - if (!res && !lex->no_write_to_binlog) - { - /* - Presumably, ANALYZE and binlog writing doesn't require synchronization - */ - res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - } - select_lex->table_list.first= first_table; - lex->query_tables=all_tables; - break; - } - - case SQLCOM_OPTIMIZE: - { - DBUG_ASSERT(first_table == all_tables && first_table != 0); - if (check_table_access(thd, SELECT_ACL | INSERT_ACL, all_tables, - FALSE, UINT_MAX, FALSE)) - goto error; /* purecov: inspected */ - thd->enable_slow_log= opt_log_slow_admin_statements; - res= (specialflag & (SPECIAL_SAFE_MODE | SPECIAL_NO_NEW_FUNC)) ? - mysql_recreate_table(thd, first_table) : - mysql_optimize_table(thd, first_table, &lex->check_opt); - /* ! we write after unlocking the table */ - if (!res && !lex->no_write_to_binlog) - { - /* - Presumably, OPTIMIZE and binlog writing doesn't require synchronization - */ - res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - } - select_lex->table_list.first= first_table; - lex->query_tables=all_tables; - break; - } case SQLCOM_UPDATE: { ha_rows found= 0, updated= 0; @@ -3316,23 +3170,6 @@ end_with_restore_list: break; } - case SQLCOM_TRUNCATE: - DBUG_ASSERT(first_table == all_tables && first_table != 0); - if (check_one_table_access(thd, DROP_ACL, all_tables)) - goto error; - /* - Don't allow this within a transaction because we want to use - re-generate table - */ - if (thd->in_active_multi_stmt_transaction()) - { - my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, - ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); - goto error; - } - if (! (res= mysql_truncate_table(thd, first_table))) - my_ok(thd); - break; case SQLCOM_DELETE: { DBUG_ASSERT(first_table == all_tables && first_table != 0); @@ -4718,6 +4555,14 @@ create_sp_error: my_ok(thd, 1); break; } + case SQLCOM_ANALYZE: + case SQLCOM_CHECK: + case SQLCOM_OPTIMIZE: + case SQLCOM_REPAIR: + case SQLCOM_TRUNCATE: + case SQLCOM_ALTER_TABLE: + DBUG_ASSERT(first_table == all_tables && first_table != 0); + /* fall through */ case SQLCOM_SIGNAL: case SQLCOM_RESIGNAL: DBUG_ASSERT(lex->m_stmt != NULL); @@ -7853,6 +7698,7 @@ bool parse_sql(THD *thd, { bool ret_value; DBUG_ASSERT(thd->m_parser_state == NULL); + DBUG_ASSERT(thd->lex->m_stmt == NULL); MYSQL_QUERY_PARSE_START(thd->query()); /* Backup creation context. */ diff --git a/sql/sql_parse.h b/sql/sql_parse.h index 8b7fe8f7b83..dd5b8aacc0a 100644 --- a/sql/sql_parse.h +++ b/sql/sql_parse.h @@ -155,6 +155,7 @@ bool check_single_table_access(THD *thd, ulong privilege, bool check_routine_access(THD *thd,ulong want_access,char *db,char *name, bool is_proc, bool no_errors); bool check_some_access(THD *thd, ulong want_access, TABLE_LIST *table); +bool check_merge_table_access(THD *thd, char *db, TABLE_LIST *table_list); bool check_some_routine_access(THD *thd, const char *db, const char *name, bool is_proc); bool check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, GRANT_INTERNAL_INFO *grant_internal_info, diff --git a/sql/sql_partition_admin.cc b/sql/sql_partition_admin.cc new file mode 100644 index 00000000000..fc0183e9b3d --- /dev/null +++ b/sql/sql_partition_admin.cc @@ -0,0 +1,139 @@ +/* Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "sql_parse.h" // check_one_table_access +#include "sql_table.h" // mysql_alter_table, etc. +#include "sql_lex.h" // Sql_statement +#include "sql_truncate.h" // mysql_truncate_table, + // Truncate_statement +#include "sql_table_maintenance.h" // Analyze/Check/.._table_statement +#include "sql_partition_admin.h" // Alter_table_*_partition + +#ifndef WITH_PARTITION_STORAGE_ENGINE + +bool Partition_statement_unsupported::execute(THD *) +{ + DBUG_ENTER("Partition_statement_unsupported::execute"); + /* error, partitioning support not compiled in... */ + my_error(ER_FEATURE_DISABLED, MYF(0), "partitioning", + "--with-plugin-partition"); + DBUG_RETURN(TRUE); +} + +#else + +bool Alter_table_analyze_partition_statement::execute(THD *thd) +{ + bool res; + DBUG_ENTER("Alter_table_analyze_partition_statement::execute"); + + /* + Flag that it is an ALTER command which administrates partitions, used + by ha_partition + */ + m_lex->alter_info.flags|= ALTER_ADMIN_PARTITION; + + res= Analyze_table_statement::execute(thd); + + DBUG_RETURN(res); +} + + +bool Alter_table_check_partition_statement::execute(THD *thd) +{ + bool res; + DBUG_ENTER("Alter_table_check_partition_statement::execute"); + + /* + Flag that it is an ALTER command which administrates partitions, used + by ha_partition + */ + m_lex->alter_info.flags|= ALTER_ADMIN_PARTITION; + + res= Check_table_statement::execute(thd); + + DBUG_RETURN(res); +} + + +bool Alter_table_optimize_partition_statement::execute(THD *thd) +{ + bool res; + DBUG_ENTER("Alter_table_optimize_partition_statement::execute"); + + /* + Flag that it is an ALTER command which administrates partitions, used + by ha_partition + */ + m_lex->alter_info.flags|= ALTER_ADMIN_PARTITION; + + res= Optimize_table_statement::execute(thd); + + DBUG_RETURN(res); +} + + +bool Alter_table_repair_partition_statement::execute(THD *thd) +{ + bool res; + DBUG_ENTER("Alter_table_repair_partition_statement::execute"); + + /* + Flag that it is an ALTER command which administrates partitions, used + by ha_partition + */ + m_lex->alter_info.flags|= ALTER_ADMIN_PARTITION; + + res= Repair_table_statement::execute(thd); + + DBUG_RETURN(res); +} + + +bool Alter_table_truncate_partition_statement::execute(THD *thd) +{ + TABLE_LIST *first_table= thd->lex->select_lex.table_list.first; + bool res; + enum_sql_command original_sql_command; + DBUG_ENTER("Alter_table_truncate_partition_statement::execute"); + + /* + Execute TRUNCATE PARTITION just like TRUNCATE TABLE. + Some storage engines (InnoDB, partition) checks thd_sql_command, + so we set it to SQLCOM_TRUNCATE during the execution. + */ + original_sql_command= m_lex->sql_command; + m_lex->sql_command= SQLCOM_TRUNCATE; + + /* + Flag that it is an ALTER command which administrates partitions, used + by ha_partition. + */ + m_lex->alter_info.flags|= ALTER_ADMIN_PARTITION; + + /* + Fix the lock types (not the same as ordinary ALTER TABLE). + */ + first_table->lock_type= TL_WRITE; + first_table->mdl_request.set_type(MDL_SHARED_NO_READ_WRITE); + + /* execute as a TRUNCATE TABLE */ + res= Truncate_statement::execute(thd); + + m_lex->sql_command= original_sql_command; + DBUG_RETURN(res); +} + +#endif /* WITH_PARTITION_STORAGE_ENGINE */ diff --git a/sql/sql_partition_admin.h b/sql/sql_partition_admin.h new file mode 100644 index 00000000000..36bafec4202 --- /dev/null +++ b/sql/sql_partition_admin.h @@ -0,0 +1,236 @@ +/* Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifndef SQL_PARTITION_ADMIN_H +#define SQL_PARTITION_ADMIN_H + +#ifndef WITH_PARTITION_STORAGE_ENGINE + +/** + Stub class that returns a error if the partition storage engine is + not supported. +*/ +class Partition_statement_unsupported : public Sql_statement +{ +public: + Partition_statement_unsupported(LEX *lex) + : Sql_statement(lex) + {} + + ~Partition_statement_unsupported() + {} + + bool execute(THD *thd); +}; + + +class Alter_table_analyze_partition_statement : + public Partition_statement_unsupported +{ +public: + Alter_table_analyze_partition_statement(LEX *lex) + : Partition_statement_unsupported(lex) + {} + + ~Alter_table_analyze_partition_statement() + {} +}; + + +class Alter_table_check_partition_statement : + public Partition_statement_unsupported +{ +public: + Alter_table_check_partition_statement(LEX *lex) + : Partition_statement_unsupported(lex) + {} + + ~Alter_table_check_partition_statement() + {} +}; + + +class Alter_table_optimize_partition_statement : + public Partition_statement_unsupported +{ +public: + Alter_table_optimize_partition_statement(LEX *lex) + : Partition_statement_unsupported(lex) + {} + + ~Alter_table_optimize_partition_statement() + {} +}; + + +class Alter_table_repair_partition_statement : + public Partition_statement_unsupported +{ +public: + Alter_table_repair_partition_statement(LEX *lex) + : Partition_statement_unsupported(lex) + {} + + ~Alter_table_repair_partition_statement() + {} +}; + + +class Alter_table_truncate_partition_statement : + public Partition_statement_unsupported +{ +public: + Alter_table_truncate_partition_statement(LEX *lex) + : Partition_statement_unsupported(lex) + {} + + ~Alter_table_truncate_partition_statement() + {} +}; + + +#else + +/** + Class that represents the ALTER TABLE t1 ANALYZE PARTITION p statement. +*/ +class Alter_table_analyze_partition_statement : public Analyze_table_statement +{ +public: + /** + Constructor, used to represent a ALTER TABLE ANALYZE PARTITION statement. + @param lex the LEX structure for this statement. + */ + Alter_table_analyze_partition_statement(LEX *lex) + : Analyze_table_statement(lex) + {} + + ~Alter_table_analyze_partition_statement() + {} + + /** + Execute a ALTER TABLE ANALYZE PARTITION statement at runtime. + @param thd the current thread. + @return false on success. + */ + bool execute(THD *thd); +}; + + +/** + Class that represents the ALTER TABLE t1 CHECK PARTITION p statement. +*/ +class Alter_table_check_partition_statement : public Check_table_statement +{ +public: + /** + Constructor, used to represent a ALTER TABLE CHECK PARTITION statement. + @param lex the LEX structure for this statement. + */ + Alter_table_check_partition_statement(LEX *lex) + : Check_table_statement(lex) + {} + + ~Alter_table_check_partition_statement() + {} + + /** + Execute a ALTER TABLE CHECK PARTITION statement at runtime. + @param thd the current thread. + @return false on success. + */ + bool execute(THD *thd); +}; + + +/** + Class that represents the ALTER TABLE t1 OPTIMIZE PARTITION p statement. +*/ +class Alter_table_optimize_partition_statement : public Optimize_table_statement +{ +public: + /** + Constructor, used to represent a ALTER TABLE OPTIMIZE PARTITION statement. + @param lex the LEX structure for this statement. + */ + Alter_table_optimize_partition_statement(LEX *lex) + : Optimize_table_statement(lex) + {} + + ~Alter_table_optimize_partition_statement() + {} + + /** + Execute a ALTER TABLE OPTIMIZE PARTITION statement at runtime. + @param thd the current thread. + @return false on success. + */ + bool execute(THD *thd); +}; + + +/** + Class that represents the ALTER TABLE t1 REPAIR PARTITION p statement. +*/ +class Alter_table_repair_partition_statement : public Repair_table_statement +{ +public: + /** + Constructor, used to represent a ALTER TABLE REPAIR PARTITION statement. + @param lex the LEX structure for this statement. + */ + Alter_table_repair_partition_statement(LEX *lex) + : Repair_table_statement(lex) + {} + + ~Alter_table_repair_partition_statement() + {} + + /** + Execute a ALTER TABLE REPAIR PARTITION statement at runtime. + @param thd the current thread. + @return false on success. + */ + bool execute(THD *thd); +}; + + +/** + Class that represents the ALTER TABLE t1 TRUNCATE PARTITION p statement. +*/ +class Alter_table_truncate_partition_statement : public Truncate_statement +{ +public: + /** + Constructor, used to represent a ALTER TABLE TRUNCATE PARTITION statement. + @param lex the LEX structure for this statement. + */ + Alter_table_truncate_partition_statement(LEX *lex) + : Truncate_statement(lex) + {} + + ~Alter_table_truncate_partition_statement() + {} + + /** + Execute a ALTER TABLE TRUNCATE PARTITION statement at runtime. + @param thd the current thread. + @return false on success. + */ + bool execute(THD *thd); +}; + +#endif /* WITH_PARTITION_STORAGE_ENGINE */ +#endif /* SQL_PARTITION_ADMIN_H */ diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 7f8e4f68ec5..1db7aa55792 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -51,7 +51,6 @@ #include "sql_parse.h" #include "sql_show.h" #include "transaction.h" -#include "keycaches.h" #include "datadict.h" // dd_frm_type() #ifdef __WIN__ @@ -78,10 +77,6 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, uint *db_options, handler *file, KEY **key_info_buffer, uint *key_count, int select_field_count); -static bool -mysql_prepare_alter_table(THD *thd, TABLE *table, - HA_CREATE_INFO *create_info, - Alter_info *alter_info); /** @@ -4394,885 +4389,6 @@ mysql_rename_table(handlerton *base, const char *old_db, } -static int send_check_errmsg(THD *thd, TABLE_LIST* table, - const char* operator_name, const char* errmsg) - -{ - Protocol *protocol= thd->protocol; - protocol->prepare_for_resend(); - protocol->store(table->alias, system_charset_info); - protocol->store((char*) operator_name, system_charset_info); - protocol->store(STRING_WITH_LEN("error"), system_charset_info); - protocol->store(errmsg, system_charset_info); - thd->clear_error(); - if (protocol->write()) - return -1; - return 1; -} - - -static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, - HA_CHECK_OPT *check_opt) -{ - int error= 0; - TABLE tmp_table, *table; - TABLE_SHARE *share; - bool has_mdl_lock= FALSE; - char from[FN_REFLEN],tmp[FN_REFLEN+32]; - const char **ext; - MY_STAT stat_info; - Open_table_context ot_ctx(thd, (MYSQL_OPEN_IGNORE_FLUSH | - MYSQL_OPEN_HAS_MDL_LOCK | - MYSQL_LOCK_IGNORE_TIMEOUT)); - DBUG_ENTER("prepare_for_repair"); - - if (!(check_opt->sql_flags & TT_USEFRM)) - DBUG_RETURN(0); - - if (!(table= table_list->table)) - { - char key[MAX_DBKEY_LENGTH]; - uint key_length; - /* - If the table didn't exist, we have a shared metadata lock - on it that is left from mysql_admin_table()'s attempt to - open it. Release the shared metadata lock before trying to - acquire the exclusive lock to satisfy MDL asserts and avoid - deadlocks. - */ - thd->mdl_context.release_transactional_locks(); - /* - Attempt to do full-blown table open in mysql_admin_table() has failed. - Let us try to open at least a .FRM for this table. - */ - my_hash_value_type hash_value; - - key_length= create_table_def_key(thd, key, table_list, 0); - table_list->mdl_request.init(MDL_key::TABLE, - table_list->db, table_list->table_name, - MDL_EXCLUSIVE); - - if (lock_table_names(thd, table_list, table_list->next_global, - thd->variables.lock_wait_timeout, - MYSQL_OPEN_SKIP_TEMPORARY)) - DBUG_RETURN(0); - has_mdl_lock= TRUE; - - hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length); - mysql_mutex_lock(&LOCK_open); - if (!(share= (get_table_share(thd, table_list, key, key_length, 0, - &error, hash_value)))) - { - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(0); // Can't open frm file - } - - if (open_table_from_share(thd, share, "", 0, 0, 0, &tmp_table, FALSE)) - { - release_table_share(share); - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(0); // Out of memory - } - mysql_mutex_unlock(&LOCK_open); - table= &tmp_table; - } - - /* A MERGE table must not come here. */ - DBUG_ASSERT(table->file->ht->db_type != DB_TYPE_MRG_MYISAM); - - /* - REPAIR TABLE ... USE_FRM for temporary tables makes little sense. - */ - if (table->s->tmp_table) - { - error= send_check_errmsg(thd, table_list, "repair", - "Cannot repair temporary table from .frm file"); - goto end; - } - - /* - User gave us USE_FRM which means that the header in the index file is - trashed. - In this case we will try to fix the table the following way: - - Rename the data file to a temporary name - - Truncate the table - - Replace the new data file with the old one - - Run a normal repair using the new index file and the old data file - */ - - if (table->s->frm_version != FRM_VER_TRUE_VARCHAR) - { - error= send_check_errmsg(thd, table_list, "repair", - "Failed repairing incompatible .frm file"); - goto end; - } - - /* - Check if this is a table type that stores index and data separately, - like ISAM or MyISAM. We assume fixed order of engine file name - extentions array. First element of engine file name extentions array - is meta/index file extention. Second element - data file extention. - */ - ext= table->file->bas_ext(); - if (!ext[0] || !ext[1]) - goto end; // No data file - - // Name of data file - strxmov(from, table->s->normalized_path.str, ext[1], NullS); - if (!mysql_file_stat(key_file_misc, from, &stat_info, MYF(0))) - goto end; // Can't use USE_FRM flag - - my_snprintf(tmp, sizeof(tmp), "%s-%lx_%lx", - from, current_pid, thd->thread_id); - - if (table_list->table) - { - /* - Table was successfully open in mysql_admin_table(). Now we need - to close it, but leave it protected by exclusive metadata lock. - */ - if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) - goto end; - close_all_tables_for_name(thd, table_list->table->s, FALSE); - table_list->table= 0; - } - /* - After this point we have an exclusive metadata lock on our table - in both cases when table was successfully open in mysql_admin_table() - and when it was open in prepare_for_repair(). - */ - - if (my_rename(from, tmp, MYF(MY_WME))) - { - error= send_check_errmsg(thd, table_list, "repair", - "Failed renaming data file"); - goto end; - } - if (dd_recreate_table(thd, table_list->db, table_list->table_name)) - { - error= send_check_errmsg(thd, table_list, "repair", - "Failed generating table from .frm file"); - goto end; - } - /* - 'FALSE' for 'using_transactions' means don't postpone - invalidation till the end of a transaction, but do it - immediately. - */ - query_cache_invalidate3(thd, table_list, FALSE); - if (mysql_file_rename(key_file_misc, tmp, from, MYF(MY_WME))) - { - error= send_check_errmsg(thd, table_list, "repair", - "Failed restoring .MYD file"); - goto end; - } - - if (thd->locked_tables_list.reopen_tables(thd)) - goto end; - - /* - Now we should be able to open the partially repaired table - to finish the repair in the handler later on. - */ - if (open_table(thd, table_list, thd->mem_root, &ot_ctx)) - { - error= send_check_errmsg(thd, table_list, "repair", - "Failed to open partially repaired table"); - goto end; - } - -end: - thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); - if (table == &tmp_table) - { - mysql_mutex_lock(&LOCK_open); - closefrm(table, 1); // Free allocated memory - mysql_mutex_unlock(&LOCK_open); - } - /* In case of a temporary table there will be no metadata lock. */ - if (error && has_mdl_lock) - thd->mdl_context.release_transactional_locks(); - - DBUG_RETURN(error); -} - - - -/* - RETURN VALUES - FALSE Message sent to net (admin operation went ok) - TRUE Message should be sent by caller - (admin operation or network communication failed) -*/ -static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, - HA_CHECK_OPT* check_opt, - const char *operator_name, - thr_lock_type lock_type, - bool open_for_modify, - bool no_warnings_for_error, - uint extra_open_options, - int (*prepare_func)(THD *, TABLE_LIST *, - HA_CHECK_OPT *), - int (handler::*operator_func)(THD *, - HA_CHECK_OPT *), - int (view_operator_func)(THD *, TABLE_LIST*)) -{ - TABLE_LIST *table; - SELECT_LEX *select= &thd->lex->select_lex; - List field_list; - Item *item; - Protocol *protocol= thd->protocol; - LEX *lex= thd->lex; - int result_code; - DBUG_ENTER("mysql_admin_table"); - - field_list.push_back(item = new Item_empty_string("Table", NAME_CHAR_LEN*2)); - item->maybe_null = 1; - field_list.push_back(item = new Item_empty_string("Op", 10)); - item->maybe_null = 1; - field_list.push_back(item = new Item_empty_string("Msg_type", 10)); - item->maybe_null = 1; - field_list.push_back(item = new Item_empty_string("Msg_text", 255)); - item->maybe_null = 1; - if (protocol->send_result_set_metadata(&field_list, - Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) - DBUG_RETURN(TRUE); - - mysql_ha_rm_tables(thd, tables); - - for (table= tables; table; table= table->next_local) - { - char table_name[NAME_LEN*2+2]; - char* db = table->db; - bool fatal_error=0; - bool open_error; - - DBUG_PRINT("admin", ("table: '%s'.'%s'", table->db, table->table_name)); - DBUG_PRINT("admin", ("extra_open_options: %u", extra_open_options)); - strxmov(table_name, db, ".", table->table_name, NullS); - thd->open_options|= extra_open_options; - table->lock_type= lock_type; - /* - To make code safe for re-execution we need to reset type of MDL - request as code below may change it. - To allow concurrent execution of read-only operations we acquire - weak metadata lock for them. - */ - table->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? - MDL_SHARED_NO_READ_WRITE : MDL_SHARED_READ); - /* open only one table from local list of command */ - { - TABLE_LIST *save_next_global, *save_next_local; - save_next_global= table->next_global; - table->next_global= 0; - save_next_local= table->next_local; - table->next_local= 0; - select->table_list.first= table; - /* - Time zone tables and SP tables can be add to lex->query_tables list, - so it have to be prepared. - TODO: Investigate if we can put extra tables into argument instead of - using lex->query_tables - */ - lex->query_tables= table; - lex->query_tables_last= &table->next_global; - lex->query_tables_own_last= 0; - thd->no_warnings_for_error= no_warnings_for_error; - if (view_operator_func == NULL) - table->required_type=FRMTYPE_TABLE; - - open_error= open_and_lock_tables(thd, table, TRUE, 0); - thd->no_warnings_for_error= 0; - table->next_global= save_next_global; - table->next_local= save_next_local; - thd->open_options&= ~extra_open_options; - /* - Under locked tables, we know that the table can be opened, - so any errors opening the table are logical errors. - In these cases it does not make sense to try to repair. - */ - if (open_error && thd->locked_tables_mode) - { - result_code= HA_ADMIN_FAILED; - goto send_result; - } -#ifdef WITH_PARTITION_STORAGE_ENGINE - if (table->table) - { - /* - Set up which partitions that should be processed - if ALTER TABLE t ANALYZE/CHECK/OPTIMIZE/REPAIR PARTITION .. - CACHE INDEX/LOAD INDEX for specified partitions - */ - Alter_info *alter_info= &lex->alter_info; - - if (alter_info->flags & ALTER_ADMIN_PARTITION) - { - if (!table->table->part_info) - { - my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0)); - DBUG_RETURN(TRUE); - } - uint num_parts_found; - uint num_parts_opt= alter_info->partition_names.elements; - num_parts_found= set_part_state(alter_info, table->table->part_info, - PART_ADMIN); - if (num_parts_found != num_parts_opt && - (!(alter_info->flags & ALTER_ALL_PARTITION))) - { - char buff[FN_REFLEN + MYSQL_ERRMSG_SIZE]; - size_t length; - DBUG_PRINT("admin", ("sending non existent partition error")); - protocol->prepare_for_resend(); - protocol->store(table_name, system_charset_info); - protocol->store(operator_name, system_charset_info); - protocol->store(STRING_WITH_LEN("error"), system_charset_info); - length= my_snprintf(buff, sizeof(buff), - ER(ER_DROP_PARTITION_NON_EXISTENT), - table_name); - protocol->store(buff, length, system_charset_info); - if(protocol->write()) - goto err; - my_eof(thd); - goto err; - } - } - } -#endif - } - DBUG_PRINT("admin", ("table: 0x%lx", (long) table->table)); - - if (prepare_func) - { - DBUG_PRINT("admin", ("calling prepare_func")); - switch ((*prepare_func)(thd, table, check_opt)) { - case 1: // error, message written to net - trans_rollback_stmt(thd); - trans_rollback(thd); - close_thread_tables(thd); - thd->mdl_context.release_transactional_locks(); - DBUG_PRINT("admin", ("simple error, admin next table")); - continue; - case -1: // error, message could be written to net - /* purecov: begin inspected */ - DBUG_PRINT("admin", ("severe error, stop")); - goto err; - /* purecov: end */ - default: // should be 0 otherwise - DBUG_PRINT("admin", ("prepare_func succeeded")); - ; - } - } - - /* - CHECK TABLE command is only command where VIEW allowed here and this - command use only temporary teble method for VIEWs resolving => there - can't be VIEW tree substitition of join view => if opening table - succeed then table->table will have real TABLE pointer as value (in - case of join view substitution table->table can be 0, but here it is - impossible) - */ - if (!table->table) - { - DBUG_PRINT("admin", ("open table failed")); - if (thd->warning_info->is_empty()) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_CHECK_NO_SUCH_TABLE, ER(ER_CHECK_NO_SUCH_TABLE)); - /* if it was a view will check md5 sum */ - if (table->view && - view_checksum(thd, table) == HA_ADMIN_WRONG_CHECKSUM) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_VIEW_CHECKSUM, ER(ER_VIEW_CHECKSUM)); - if (thd->stmt_da->is_error() && - (thd->stmt_da->sql_errno() == ER_NO_SUCH_TABLE || - thd->stmt_da->sql_errno() == ER_FILE_NOT_FOUND)) - /* A missing table is just issued as a failed command */ - result_code= HA_ADMIN_FAILED; - else - /* Default failure code is corrupt table */ - result_code= HA_ADMIN_CORRUPT; - goto send_result; - } - - if (table->view) - { - DBUG_PRINT("admin", ("calling view_operator_func")); - result_code= (*view_operator_func)(thd, table); - goto send_result; - } - - if (table->schema_table) - { - result_code= HA_ADMIN_NOT_IMPLEMENTED; - goto send_result; - } - - if ((table->table->db_stat & HA_READ_ONLY) && open_for_modify) - { - /* purecov: begin inspected */ - char buff[FN_REFLEN + MYSQL_ERRMSG_SIZE]; - size_t length; - enum_sql_command save_sql_command= lex->sql_command; - DBUG_PRINT("admin", ("sending error message")); - protocol->prepare_for_resend(); - protocol->store(table_name, system_charset_info); - protocol->store(operator_name, system_charset_info); - protocol->store(STRING_WITH_LEN("error"), system_charset_info); - length= my_snprintf(buff, sizeof(buff), ER(ER_OPEN_AS_READONLY), - table_name); - protocol->store(buff, length, system_charset_info); - trans_commit_stmt(thd); - trans_commit(thd); - close_thread_tables(thd); - thd->mdl_context.release_transactional_locks(); - lex->reset_query_tables_list(FALSE); - /* - Restore Query_tables_list::sql_command value to make statement - safe for re-execution. - */ - lex->sql_command= save_sql_command; - table->table=0; // For query cache - if (protocol->write()) - goto err; - thd->stmt_da->reset_diagnostics_area(); - continue; - /* purecov: end */ - } - - /* - Close all instances of the table to allow MyISAM "repair" - to rename files. - @todo: This code does not close all instances of the table. - It only closes instances in other connections, but if this - connection has LOCK TABLE t1 a READ, t1 b WRITE, - both t1 instances will be kept open. - There is no need to execute this branch for InnoDB, which does - repair by recreate. There is no need to do it for OPTIMIZE, - which doesn't move files around. - Hence, this code should be moved to prepare_for_repair(), - and executed only for MyISAM engine. - */ - if (lock_type == TL_WRITE && !table->table->s->tmp_table) - { - if (wait_while_table_is_used(thd, table->table, - HA_EXTRA_PREPARE_FOR_RENAME)) - goto err; - DEBUG_SYNC(thd, "after_admin_flush"); - /* Flush entries in the query cache involving this table. */ - query_cache_invalidate3(thd, table->table, 0); - /* - XXX: hack: switch off open_for_modify to skip the - flush that is made later in the execution flow. - */ - open_for_modify= 0; - } - - if (table->table->s->crashed && operator_func == &handler::ha_check) - { - /* purecov: begin inspected */ - DBUG_PRINT("admin", ("sending crashed warning")); - protocol->prepare_for_resend(); - protocol->store(table_name, system_charset_info); - protocol->store(operator_name, system_charset_info); - protocol->store(STRING_WITH_LEN("warning"), system_charset_info); - protocol->store(STRING_WITH_LEN("Table is marked as crashed"), - system_charset_info); - if (protocol->write()) - goto err; - /* purecov: end */ - } - - if (operator_func == &handler::ha_repair && - !(check_opt->sql_flags & TT_USEFRM)) - { - if ((table->table->file->check_old_types() == HA_ADMIN_NEEDS_ALTER) || - (table->table->file->ha_check_for_upgrade(check_opt) == - HA_ADMIN_NEEDS_ALTER)) - { - DBUG_PRINT("admin", ("recreating table")); - trans_rollback_stmt(thd); - trans_rollback(thd); - close_thread_tables(thd); - thd->mdl_context.release_transactional_locks(); - tmp_disable_binlog(thd); // binlogging is done by caller if wanted - result_code= mysql_recreate_table(thd, table); - reenable_binlog(thd); - /* - mysql_recreate_table() can push OK or ERROR. - Clear 'OK' status. If there is an error, keep it: - we will store the error message in a result set row - and then clear. - */ - if (thd->stmt_da->is_ok()) - thd->stmt_da->reset_diagnostics_area(); - table->table= NULL; - result_code= result_code ? HA_ADMIN_FAILED : HA_ADMIN_OK; - goto send_result; - } - } - - DBUG_PRINT("admin", ("calling operator_func '%s'", operator_name)); - result_code = (table->table->file->*operator_func)(thd, check_opt); - DBUG_PRINT("admin", ("operator_func returned: %d", result_code)); - -send_result: - - lex->cleanup_after_one_table_open(); - thd->clear_error(); // these errors shouldn't get client - { - List_iterator_fast it(thd->warning_info->warn_list()); - MYSQL_ERROR *err; - while ((err= it++)) - { - protocol->prepare_for_resend(); - protocol->store(table_name, system_charset_info); - protocol->store((char*) operator_name, system_charset_info); - protocol->store(warning_level_names[err->get_level()].str, - warning_level_names[err->get_level()].length, - system_charset_info); - protocol->store(err->get_message_text(), system_charset_info); - if (protocol->write()) - goto err; - } - thd->warning_info->clear_warning_info(thd->query_id); - } - protocol->prepare_for_resend(); - protocol->store(table_name, system_charset_info); - protocol->store(operator_name, system_charset_info); - -send_result_message: - - DBUG_PRINT("info", ("result_code: %d", result_code)); - switch (result_code) { - case HA_ADMIN_NOT_IMPLEMENTED: - { - char buf[MYSQL_ERRMSG_SIZE]; - size_t length=my_snprintf(buf, sizeof(buf), - ER(ER_CHECK_NOT_IMPLEMENTED), operator_name); - protocol->store(STRING_WITH_LEN("note"), system_charset_info); - protocol->store(buf, length, system_charset_info); - } - break; - - case HA_ADMIN_NOT_BASE_TABLE: - { - char buf[MYSQL_ERRMSG_SIZE]; - size_t length= my_snprintf(buf, sizeof(buf), - ER(ER_BAD_TABLE_ERROR), table_name); - protocol->store(STRING_WITH_LEN("note"), system_charset_info); - protocol->store(buf, length, system_charset_info); - } - break; - - case HA_ADMIN_OK: - protocol->store(STRING_WITH_LEN("status"), system_charset_info); - protocol->store(STRING_WITH_LEN("OK"), system_charset_info); - break; - - case HA_ADMIN_FAILED: - protocol->store(STRING_WITH_LEN("status"), system_charset_info); - protocol->store(STRING_WITH_LEN("Operation failed"), - system_charset_info); - break; - - case HA_ADMIN_REJECT: - protocol->store(STRING_WITH_LEN("status"), system_charset_info); - protocol->store(STRING_WITH_LEN("Operation need committed state"), - system_charset_info); - open_for_modify= FALSE; - break; - - case HA_ADMIN_ALREADY_DONE: - protocol->store(STRING_WITH_LEN("status"), system_charset_info); - protocol->store(STRING_WITH_LEN("Table is already up to date"), - system_charset_info); - break; - - case HA_ADMIN_CORRUPT: - protocol->store(STRING_WITH_LEN("error"), system_charset_info); - protocol->store(STRING_WITH_LEN("Corrupt"), system_charset_info); - fatal_error=1; - break; - - case HA_ADMIN_INVALID: - protocol->store(STRING_WITH_LEN("error"), system_charset_info); - protocol->store(STRING_WITH_LEN("Invalid argument"), - system_charset_info); - break; - - case HA_ADMIN_TRY_ALTER: - { - /* - This is currently used only by InnoDB. ha_innobase::optimize() answers - "try with alter", so here we close the table, do an ALTER TABLE, - reopen the table and do ha_innobase::analyze() on it. - We have to end the row, so analyze could return more rows. - */ - trans_commit_stmt(thd); - trans_commit(thd); - close_thread_tables(thd); - thd->mdl_context.release_transactional_locks(); - DEBUG_SYNC(thd, "ha_admin_try_alter"); - protocol->store(STRING_WITH_LEN("note"), system_charset_info); - protocol->store(STRING_WITH_LEN( - "Table does not support optimize, doing recreate + analyze instead"), - system_charset_info); - if (protocol->write()) - goto err; - DBUG_PRINT("info", ("HA_ADMIN_TRY_ALTER, trying analyze...")); - TABLE_LIST *save_next_local= table->next_local, - *save_next_global= table->next_global; - table->next_local= table->next_global= 0; - tmp_disable_binlog(thd); // binlogging is done by caller if wanted - result_code= mysql_recreate_table(thd, table); - reenable_binlog(thd); - /* - mysql_recreate_table() can push OK or ERROR. - Clear 'OK' status. If there is an error, keep it: - we will store the error message in a result set row - and then clear. - */ - if (thd->stmt_da->is_ok()) - thd->stmt_da->reset_diagnostics_area(); - trans_commit_stmt(thd); - trans_commit(thd); - close_thread_tables(thd); - thd->mdl_context.release_transactional_locks(); - table->table= NULL; - if (!result_code) // recreation went ok - { - /* Clear the ticket released above. */ - table->mdl_request.ticket= NULL; - DEBUG_SYNC(thd, "ha_admin_open_ltable"); - table->mdl_request.set_type(MDL_SHARED_WRITE); - if ((table->table= open_ltable(thd, table, lock_type, 0))) - { - result_code= table->table->file->ha_analyze(thd, check_opt); - if (result_code == HA_ADMIN_ALREADY_DONE) - result_code= HA_ADMIN_OK; - else if (result_code) // analyze failed - table->table->file->print_error(result_code, MYF(0)); - } - else - result_code= -1; // open failed - } - /* Start a new row for the final status row */ - protocol->prepare_for_resend(); - protocol->store(table_name, system_charset_info); - protocol->store(operator_name, system_charset_info); - if (result_code) // either mysql_recreate_table or analyze failed - { - DBUG_ASSERT(thd->is_error()); - if (thd->is_error()) - { - const char *err_msg= thd->stmt_da->message(); - if (!thd->vio_ok()) - { - sql_print_error("%s", err_msg); - } - else - { - /* Hijack the row already in-progress. */ - protocol->store(STRING_WITH_LEN("error"), system_charset_info); - protocol->store(err_msg, system_charset_info); - if (protocol->write()) - goto err; - /* Start off another row for HA_ADMIN_FAILED */ - protocol->prepare_for_resend(); - protocol->store(table_name, system_charset_info); - protocol->store(operator_name, system_charset_info); - } - thd->clear_error(); - } - } - result_code= result_code ? HA_ADMIN_FAILED : HA_ADMIN_OK; - table->next_local= save_next_local; - table->next_global= save_next_global; - goto send_result_message; - } - case HA_ADMIN_WRONG_CHECKSUM: - { - protocol->store(STRING_WITH_LEN("note"), system_charset_info); - protocol->store(ER(ER_VIEW_CHECKSUM), strlen(ER(ER_VIEW_CHECKSUM)), - system_charset_info); - break; - } - - case HA_ADMIN_NEEDS_UPGRADE: - case HA_ADMIN_NEEDS_ALTER: - { - char buf[MYSQL_ERRMSG_SIZE]; - size_t length; - - protocol->store(STRING_WITH_LEN("error"), system_charset_info); - length=my_snprintf(buf, sizeof(buf), ER(ER_TABLE_NEEDS_UPGRADE), - table->table_name); - protocol->store(buf, length, system_charset_info); - fatal_error=1; - break; - } - - default: // Probably HA_ADMIN_INTERNAL_ERROR - { - char buf[MYSQL_ERRMSG_SIZE]; - size_t length=my_snprintf(buf, sizeof(buf), - "Unknown - internal error %d during operation", - result_code); - protocol->store(STRING_WITH_LEN("error"), system_charset_info); - protocol->store(buf, length, system_charset_info); - fatal_error=1; - break; - } - } - if (table->table) - { - if (table->table->s->tmp_table) - { - /* - If the table was not opened successfully, do not try to get - status information. (Bug#47633) - */ - if (open_for_modify && !open_error) - table->table->file->info(HA_STATUS_CONST); - } - else if (open_for_modify || fatal_error) - { - mysql_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, - table->db, table->table_name); - mysql_mutex_unlock(&LOCK_open); - /* - May be something modified. Consequently, we have to - invalidate the query cache. - */ - table->table= 0; // For query cache - query_cache_invalidate3(thd, table, 0); - } - } - /* Error path, a admin command failed. */ - trans_commit_stmt(thd); - trans_commit_implicit(thd); - close_thread_tables(thd); - thd->mdl_context.release_transactional_locks(); - - /* - If it is CHECK TABLE v1, v2, v3, and v1, v2, v3 are views, we will run - separate open_tables() for each CHECK TABLE argument. - Right now we do not have a separate method to reset the prelocking - state in the lex to the state after parsing, so each open will pollute - this state: add elements to lex->srotuines_list, TABLE_LISTs to - lex->query_tables. Below is a lame attempt to recover from this - pollution. - @todo: have a method to reset a prelocking context, or use separate - contexts for each open. - */ - for (Sroutine_hash_entry *rt= - (Sroutine_hash_entry*)thd->lex->sroutines_list.first; - rt; rt= rt->next) - rt->mdl_request.ticket= NULL; - - if (protocol->write()) - goto err; - } - - my_eof(thd); - DBUG_RETURN(FALSE); - -err: - trans_rollback_stmt(thd); - trans_rollback(thd); - close_thread_tables(thd); // Shouldn't be needed - thd->mdl_context.release_transactional_locks(); - if (table) - table->table=0; - DBUG_RETURN(TRUE); -} - - -bool mysql_repair_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt) -{ - DBUG_ENTER("mysql_repair_table"); - DBUG_RETURN(mysql_admin_table(thd, tables, check_opt, - "repair", TL_WRITE, 1, - test(check_opt->sql_flags & TT_USEFRM), - HA_OPEN_FOR_REPAIR, - &prepare_for_repair, - &handler::ha_repair, 0)); -} - - -bool mysql_optimize_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt) -{ - DBUG_ENTER("mysql_optimize_table"); - DBUG_RETURN(mysql_admin_table(thd, tables, check_opt, - "optimize", TL_WRITE, 1,0,0,0, - &handler::ha_optimize, 0)); -} - - -/* - Assigned specified indexes for a table into key cache - - SYNOPSIS - mysql_assign_to_keycache() - thd Thread object - tables Table list (one table only) - - RETURN VALUES - FALSE ok - TRUE error -*/ - -bool mysql_assign_to_keycache(THD* thd, TABLE_LIST* tables, - LEX_STRING *key_cache_name) -{ - HA_CHECK_OPT check_opt; - KEY_CACHE *key_cache; - DBUG_ENTER("mysql_assign_to_keycache"); - - check_opt.init(); - mysql_mutex_lock(&LOCK_global_system_variables); - if (!(key_cache= get_key_cache(key_cache_name))) - { - mysql_mutex_unlock(&LOCK_global_system_variables); - my_error(ER_UNKNOWN_KEY_CACHE, MYF(0), key_cache_name->str); - DBUG_RETURN(TRUE); - } - mysql_mutex_unlock(&LOCK_global_system_variables); - check_opt.key_cache= key_cache; - DBUG_RETURN(mysql_admin_table(thd, tables, &check_opt, - "assign_to_keycache", TL_READ_NO_INSERT, 0, 0, - 0, 0, &handler::assign_to_keycache, 0)); -} - - -/* - Preload specified indexes for a table into key cache - - SYNOPSIS - mysql_preload_keys() - thd Thread object - tables Table list (one table only) - - RETURN VALUES - FALSE ok - TRUE error -*/ - -bool mysql_preload_keys(THD* thd, TABLE_LIST* tables) -{ - DBUG_ENTER("mysql_preload_keys"); - /* - We cannot allow concurrent inserts. The storage engine reads - directly from the index file, bypassing the cache. It could read - outdated information if parallel inserts into cache blocks happen. - */ - DBUG_RETURN(mysql_admin_table(thd, tables, 0, - "preload_keys", TL_READ_NO_INSERT, 0, 0, 0, 0, - &handler::preload_keys, 0)); -} - - /* Create a table identical to the specified table @@ -5436,29 +4552,6 @@ err: } -bool mysql_analyze_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt) -{ - thr_lock_type lock_type = TL_READ_NO_INSERT; - - DBUG_ENTER("mysql_analyze_table"); - DBUG_RETURN(mysql_admin_table(thd, tables, check_opt, - "analyze", lock_type, 1, 0, 0, 0, - &handler::ha_analyze, 0)); -} - - -bool mysql_check_table(THD* thd, TABLE_LIST* tables,HA_CHECK_OPT* check_opt) -{ - thr_lock_type lock_type = TL_READ_NO_INSERT; - - DBUG_ENTER("mysql_check_table"); - DBUG_RETURN(mysql_admin_table(thd, tables, check_opt, - "check", lock_type, - 0, 0, HA_OPEN_FOR_REPAIR, 0, - &handler::ha_check, &view_checksum)); -} - - /* table_list should contain just one table */ static int mysql_discard_or_import_tablespace(THD *thd, @@ -5569,7 +4662,7 @@ is_index_maintenance_unique (TABLE *table, Alter_info *alter_info) /* SYNOPSIS - compare_tables() + mysql_compare_tables() table The original table. alter_info Alter options, fields and keys for the new table. @@ -5609,17 +4702,16 @@ is_index_maintenance_unique (TABLE *table, Alter_info *alter_info) FALSE success */ -static bool -compare_tables(TABLE *table, - Alter_info *alter_info, - HA_CREATE_INFO *create_info, - uint order_num, - enum_alter_table_change_level *need_copy_table, - KEY **key_info_buffer, - uint **index_drop_buffer, uint *index_drop_count, - uint **index_add_buffer, uint *index_add_count, - uint *candidate_key_count) +mysql_compare_tables(TABLE *table, + Alter_info *alter_info, + HA_CREATE_INFO *create_info, + uint order_num, + enum_alter_table_change_level *need_copy_table, + KEY **key_info_buffer, + uint **index_drop_buffer, uint *index_drop_count, + uint **index_add_buffer, uint *index_add_count, + uint *candidate_key_count) { Field **f_ptr, *field; uint changes= 0, tmp; @@ -5635,7 +4727,7 @@ compare_tables(TABLE *table, */ bool varchar= create_info->varchar; bool not_nullable= true; - DBUG_ENTER("compare_tables"); + DBUG_ENTER("mysql_compare_tables"); /* Create a copy of alter_info. @@ -5646,7 +4738,7 @@ compare_tables(TABLE *table, mysql_prepare_create_table. Unfortunately, mysql_prepare_create_table performs its transformations "in-place", that is, modifies the argument. Since we would - like to keep compare_tables() idempotent (not altering any + like to keep mysql_compare_tables() idempotent (not altering any of the arguments) we create a copy of alter_info here and pass it to mysql_prepare_create_table, then use the result to evaluate possibility of fast ALTER TABLE, and then @@ -6026,7 +5118,7 @@ blob_length_by_type(enum_field_types type) @retval FALSE success */ -static bool +bool mysql_prepare_alter_table(THD *thd, TABLE *table, HA_CREATE_INFO *create_info, Alter_info *alter_info) @@ -6435,7 +5527,7 @@ err: Important is the fact, that this function tries to do as little work as possible, by finding out whether a intermediate table is needed to copy data into and when finishing the altering to use it as the original table. - For this reason the function compare_tables() is called, which decides + For this reason the function mysql_compare_tables() is called, which decides based on all kind of data how similar are the new and the original tables. @@ -6873,13 +5965,13 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, { enum_alter_table_change_level need_copy_table_res; /* Check how much the tables differ. */ - if (compare_tables(table, alter_info, - create_info, order_num, - &need_copy_table_res, - &key_info_buffer, - &index_drop_buffer, &index_drop_count, - &index_add_buffer, &index_add_count, - &candidate_key_count)) + if (mysql_compare_tables(table, alter_info, + create_info, order_num, + &need_copy_table_res, + &key_info_buffer, + &index_drop_buffer, &index_drop_count, + &index_add_buffer, &index_add_count, + &candidate_key_count)) goto err; DBUG_EXECUTE_IF("alter_table_only_metadata_change", { @@ -7393,7 +6485,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, it should become the actual table. Later, we will recycle the old table. However, in case of ALTER TABLE RENAME there might be no intermediate table. This is when the old and new tables are compatible, according to - compare_table(). Then, we need one additional call to + mysql_compare_table(). Then, we need one additional call to mysql_rename_table() with flag NO_FRM_RENAME, which does nothing else but actual rename in the SE and the FRM is not touched. Note that, if the table is renamed and the SE is also changed, then an intermediate table @@ -8048,3 +7140,4 @@ static bool check_engine(THD *thd, const char *table_name, } return FALSE; } + diff --git a/sql/sql_table.h b/sql/sql_table.h index dca4b706605..2e1cc89d585 100644 --- a/sql/sql_table.h +++ b/sql/sql_table.h @@ -19,7 +19,6 @@ #include "my_global.h" /* my_bool */ #include "my_sys.h" // pthread_mutex_t -class Alter_info; class Alter_info; class Create_field; struct TABLE_LIST; @@ -28,11 +27,12 @@ struct TABLE; struct handlerton; typedef struct st_ha_check_opt HA_CHECK_OPT; typedef struct st_ha_create_information HA_CREATE_INFO; +typedef struct st_key KEY; typedef struct st_key_cache KEY_CACHE; typedef struct st_lock_param_type ALTER_PARTITION_PARAM_TYPE; -typedef struct st_lock_param_type ALTER_PARTITION_PARAM_TYPE; typedef struct st_mysql_lex_string LEX_STRING; typedef struct st_order ORDER; +class Alter_table_change_level; enum ddl_log_entry_code { @@ -139,12 +139,23 @@ bool mysql_create_table_no_lock(THD *thd, const char *db, HA_CREATE_INFO *create_info, Alter_info *alter_info, bool tmp_table, uint select_field_count); - +bool mysql_prepare_alter_table(THD *thd, TABLE *table, + HA_CREATE_INFO *create_info, + Alter_info *alter_info); bool mysql_alter_table(THD *thd, char *new_db, char *new_name, HA_CREATE_INFO *create_info, TABLE_LIST *table_list, Alter_info *alter_info, uint order_num, ORDER *order, bool ignore); +bool mysql_compare_tables(TABLE *table, + Alter_info *alter_info, + HA_CREATE_INFO *create_info, + uint order_num, + Alter_table_change_level *need_copy_table, + KEY **key_info_buffer, + uint **index_drop_buffer, uint *index_drop_count, + uint **index_add_buffer, uint *index_add_count, + uint *candidate_key_count); bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list); bool mysql_create_like_table(THD *thd, TABLE_LIST *table, TABLE_LIST *src_table, @@ -158,19 +169,6 @@ bool mysql_restore_table(THD* thd, TABLE_LIST* table_list); bool mysql_checksum_table(THD* thd, TABLE_LIST* table_list, HA_CHECK_OPT* check_opt); -bool mysql_check_table(THD* thd, TABLE_LIST* table_list, - HA_CHECK_OPT* check_opt); -bool mysql_repair_table(THD* thd, TABLE_LIST* table_list, - HA_CHECK_OPT* check_opt); -bool mysql_analyze_table(THD* thd, TABLE_LIST* table_list, - HA_CHECK_OPT* check_opt); -bool mysql_optimize_table(THD* thd, TABLE_LIST* table_list, - HA_CHECK_OPT* check_opt); -bool mysql_assign_to_keycache(THD* thd, TABLE_LIST* table_list, - LEX_STRING *key_cache_name); -bool mysql_preload_keys(THD* thd, TABLE_LIST* table_list); -int reassign_keycache_tables(THD* thd, KEY_CACHE *src_cache, - KEY_CACHE *dst_cache); 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, diff --git a/sql/sql_table_maintenance.cc b/sql/sql_table_maintenance.cc new file mode 100644 index 00000000000..b7033208788 --- /dev/null +++ b/sql/sql_table_maintenance.cc @@ -0,0 +1,1004 @@ +/* Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "sql_class.h" // THD +#include "keycaches.h" // get_key_cache +#include "sql_base.h" // Open_table_context +#include "lock.h" // MYSQL_OPEN_* +#include "sql_handler.h" // mysql_ha_rm_tables +#include "partition_element.h" // PART_ADMIN +#include "sql_partition.h" // set_part_state +#include "transaction.h" // trans_rollback_stmt +#include "sql_view.h" // view_checksum +#include "sql_table.h" // mysql_recreate_table +#include "debug_sync.h" // DEBUG_SYNC +#include "sql_acl.h" // *_ACL +#include "sp.h" // Sroutine_hash_entry +#include "sql_parse.h" // check_table_access +#include "sql_table_maintenance.h" + +static int send_check_errmsg(THD *thd, TABLE_LIST* table, + const char* operator_name, const char* errmsg) + +{ + Protocol *protocol= thd->protocol; + protocol->prepare_for_resend(); + protocol->store(table->alias, system_charset_info); + protocol->store((char*) operator_name, system_charset_info); + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + protocol->store(errmsg, system_charset_info); + thd->clear_error(); + if (protocol->write()) + return -1; + return 1; +} + + +static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, + HA_CHECK_OPT *check_opt) +{ + int error= 0; + TABLE tmp_table, *table; + TABLE_SHARE *share; + bool has_mdl_lock= FALSE; + char from[FN_REFLEN],tmp[FN_REFLEN+32]; + const char **ext; + MY_STAT stat_info; + Open_table_context ot_ctx(thd, (MYSQL_OPEN_IGNORE_FLUSH | + MYSQL_OPEN_HAS_MDL_LOCK | + MYSQL_LOCK_IGNORE_TIMEOUT)); + DBUG_ENTER("prepare_for_repair"); + + if (!(check_opt->sql_flags & TT_USEFRM)) + DBUG_RETURN(0); + + if (!(table= table_list->table)) + { + char key[MAX_DBKEY_LENGTH]; + uint key_length; + /* + If the table didn't exist, we have a shared metadata lock + on it that is left from mysql_admin_table()'s attempt to + open it. Release the shared metadata lock before trying to + acquire the exclusive lock to satisfy MDL asserts and avoid + deadlocks. + */ + thd->mdl_context.release_transactional_locks(); + /* + Attempt to do full-blown table open in mysql_admin_table() has failed. + Let us try to open at least a .FRM for this table. + */ + my_hash_value_type hash_value; + + key_length= create_table_def_key(thd, key, table_list, 0); + table_list->mdl_request.init(MDL_key::TABLE, + table_list->db, table_list->table_name, + MDL_EXCLUSIVE); + + if (lock_table_names(thd, table_list, table_list->next_global, + thd->variables.lock_wait_timeout, + MYSQL_OPEN_SKIP_TEMPORARY)) + DBUG_RETURN(0); + has_mdl_lock= TRUE; + + hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length); + mysql_mutex_lock(&LOCK_open); + if (!(share= (get_table_share(thd, table_list, key, key_length, 0, + &error, hash_value)))) + { + mysql_mutex_unlock(&LOCK_open); + DBUG_RETURN(0); // Can't open frm file + } + + if (open_table_from_share(thd, share, "", 0, 0, 0, &tmp_table, FALSE)) + { + release_table_share(share); + mysql_mutex_unlock(&LOCK_open); + DBUG_RETURN(0); // Out of memory + } + mysql_mutex_unlock(&LOCK_open); + table= &tmp_table; + } + + /* A MERGE table must not come here. */ + DBUG_ASSERT(table->file->ht->db_type != DB_TYPE_MRG_MYISAM); + + /* + REPAIR TABLE ... USE_FRM for temporary tables makes little sense. + */ + if (table->s->tmp_table) + { + error= send_check_errmsg(thd, table_list, "repair", + "Cannot repair temporary table from .frm file"); + goto end; + } + + /* + User gave us USE_FRM which means that the header in the index file is + trashed. + In this case we will try to fix the table the following way: + - Rename the data file to a temporary name + - Truncate the table + - Replace the new data file with the old one + - Run a normal repair using the new index file and the old data file + */ + + if (table->s->frm_version != FRM_VER_TRUE_VARCHAR) + { + error= send_check_errmsg(thd, table_list, "repair", + "Failed repairing incompatible .frm file"); + goto end; + } + + /* + Check if this is a table type that stores index and data separately, + like ISAM or MyISAM. We assume fixed order of engine file name + extentions array. First element of engine file name extentions array + is meta/index file extention. Second element - data file extention. + */ + ext= table->file->bas_ext(); + if (!ext[0] || !ext[1]) + goto end; // No data file + + // Name of data file + strxmov(from, table->s->normalized_path.str, ext[1], NullS); + if (!mysql_file_stat(key_file_misc, from, &stat_info, MYF(0))) + goto end; // Can't use USE_FRM flag + + my_snprintf(tmp, sizeof(tmp), "%s-%lx_%lx", + from, current_pid, thd->thread_id); + + if (table_list->table) + { + /* + Table was successfully open in mysql_admin_table(). Now we need + to close it, but leave it protected by exclusive metadata lock. + */ + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + goto end; + close_all_tables_for_name(thd, table_list->table->s, FALSE); + table_list->table= 0; + } + /* + After this point we have an exclusive metadata lock on our table + in both cases when table was successfully open in mysql_admin_table() + and when it was open in prepare_for_repair(). + */ + + if (my_rename(from, tmp, MYF(MY_WME))) + { + error= send_check_errmsg(thd, table_list, "repair", + "Failed renaming data file"); + goto end; + } + if (dd_recreate_table(thd, table_list->db, table_list->table_name)) + { + error= send_check_errmsg(thd, table_list, "repair", + "Failed generating table from .frm file"); + goto end; + } + /* + 'FALSE' for 'using_transactions' means don't postpone + invalidation till the end of a transaction, but do it + immediately. + */ + query_cache_invalidate3(thd, table_list, FALSE); + if (mysql_file_rename(key_file_misc, tmp, from, MYF(MY_WME))) + { + error= send_check_errmsg(thd, table_list, "repair", + "Failed restoring .MYD file"); + goto end; + } + + if (thd->locked_tables_list.reopen_tables(thd)) + goto end; + + /* + Now we should be able to open the partially repaired table + to finish the repair in the handler later on. + */ + if (open_table(thd, table_list, thd->mem_root, &ot_ctx)) + { + error= send_check_errmsg(thd, table_list, "repair", + "Failed to open partially repaired table"); + goto end; + } + +end: + thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); + if (table == &tmp_table) + { + mysql_mutex_lock(&LOCK_open); + closefrm(table, 1); // Free allocated memory + mysql_mutex_unlock(&LOCK_open); + } + /* In case of a temporary table there will be no metadata lock. */ + if (error && has_mdl_lock) + thd->mdl_context.release_transactional_locks(); + + DBUG_RETURN(error); +} + + + +/* + RETURN VALUES + FALSE Message sent to net (admin operation went ok) + TRUE Message should be sent by caller + (admin operation or network communication failed) +*/ +static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, + HA_CHECK_OPT* check_opt, + const char *operator_name, + thr_lock_type lock_type, + bool open_for_modify, + bool no_warnings_for_error, + uint extra_open_options, + int (*prepare_func)(THD *, TABLE_LIST *, + HA_CHECK_OPT *), + int (handler::*operator_func)(THD *, + HA_CHECK_OPT *), + int (view_operator_func)(THD *, TABLE_LIST*)) +{ + TABLE_LIST *table; + SELECT_LEX *select= &thd->lex->select_lex; + List field_list; + Item *item; + Protocol *protocol= thd->protocol; + LEX *lex= thd->lex; + int result_code; + DBUG_ENTER("mysql_admin_table"); + + field_list.push_back(item = new Item_empty_string("Table", NAME_CHAR_LEN*2)); + item->maybe_null = 1; + field_list.push_back(item = new Item_empty_string("Op", 10)); + item->maybe_null = 1; + field_list.push_back(item = new Item_empty_string("Msg_type", 10)); + item->maybe_null = 1; + field_list.push_back(item = new Item_empty_string("Msg_text", 255)); + item->maybe_null = 1; + if (protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); + + mysql_ha_rm_tables(thd, tables); + + for (table= tables; table; table= table->next_local) + { + char table_name[NAME_LEN*2+2]; + char* db = table->db; + bool fatal_error=0; + bool open_error; + + DBUG_PRINT("admin", ("table: '%s'.'%s'", table->db, table->table_name)); + DBUG_PRINT("admin", ("extra_open_options: %u", extra_open_options)); + strxmov(table_name, db, ".", table->table_name, NullS); + thd->open_options|= extra_open_options; + table->lock_type= lock_type; + /* + To make code safe for re-execution we need to reset type of MDL + request as code below may change it. + To allow concurrent execution of read-only operations we acquire + weak metadata lock for them. + */ + table->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? + MDL_SHARED_NO_READ_WRITE : MDL_SHARED_READ); + /* open only one table from local list of command */ + { + TABLE_LIST *save_next_global, *save_next_local; + save_next_global= table->next_global; + table->next_global= 0; + save_next_local= table->next_local; + table->next_local= 0; + select->table_list.first= table; + /* + Time zone tables and SP tables can be add to lex->query_tables list, + so it have to be prepared. + TODO: Investigate if we can put extra tables into argument instead of + using lex->query_tables + */ + lex->query_tables= table; + lex->query_tables_last= &table->next_global; + lex->query_tables_own_last= 0; + thd->no_warnings_for_error= no_warnings_for_error; + if (view_operator_func == NULL) + table->required_type=FRMTYPE_TABLE; + + open_error= open_and_lock_tables(thd, table, TRUE, 0); + thd->no_warnings_for_error= 0; + table->next_global= save_next_global; + table->next_local= save_next_local; + thd->open_options&= ~extra_open_options; + /* + Under locked tables, we know that the table can be opened, + so any errors opening the table are logical errors. + In these cases it does not make sense to try to repair. + */ + if (open_error && thd->locked_tables_mode) + { + result_code= HA_ADMIN_FAILED; + goto send_result; + } +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (table->table) + { + /* + Set up which partitions that should be processed + if ALTER TABLE t ANALYZE/CHECK/OPTIMIZE/REPAIR PARTITION .. + CACHE INDEX/LOAD INDEX for specified partitions + */ + Alter_info *alter_info= &lex->alter_info; + + if (alter_info->flags & ALTER_ADMIN_PARTITION) + { + if (!table->table->part_info) + { + my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0)); + DBUG_RETURN(TRUE); + } + uint num_parts_found; + uint num_parts_opt= alter_info->partition_names.elements; + num_parts_found= set_part_state(alter_info, table->table->part_info, + PART_ADMIN); + if (num_parts_found != num_parts_opt && + (!(alter_info->flags & ALTER_ALL_PARTITION))) + { + char buff[FN_REFLEN + MYSQL_ERRMSG_SIZE]; + size_t length; + DBUG_PRINT("admin", ("sending non existent partition error")); + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + length= my_snprintf(buff, sizeof(buff), + ER(ER_DROP_PARTITION_NON_EXISTENT), + table_name); + protocol->store(buff, length, system_charset_info); + if(protocol->write()) + goto err; + my_eof(thd); + goto err; + } + } + } +#endif + } + DBUG_PRINT("admin", ("table: 0x%lx", (long) table->table)); + + if (prepare_func) + { + DBUG_PRINT("admin", ("calling prepare_func")); + switch ((*prepare_func)(thd, table, check_opt)) { + case 1: // error, message written to net + trans_rollback_stmt(thd); + trans_rollback(thd); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + DBUG_PRINT("admin", ("simple error, admin next table")); + continue; + case -1: // error, message could be written to net + /* purecov: begin inspected */ + DBUG_PRINT("admin", ("severe error, stop")); + goto err; + /* purecov: end */ + default: // should be 0 otherwise + DBUG_PRINT("admin", ("prepare_func succeeded")); + ; + } + } + + /* + CHECK TABLE command is only command where VIEW allowed here and this + command use only temporary teble method for VIEWs resolving => there + can't be VIEW tree substitition of join view => if opening table + succeed then table->table will have real TABLE pointer as value (in + case of join view substitution table->table can be 0, but here it is + impossible) + */ + if (!table->table) + { + DBUG_PRINT("admin", ("open table failed")); + if (thd->warning_info->is_empty()) + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_CHECK_NO_SUCH_TABLE, ER(ER_CHECK_NO_SUCH_TABLE)); + /* if it was a view will check md5 sum */ + if (table->view && + view_checksum(thd, table) == HA_ADMIN_WRONG_CHECKSUM) + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_VIEW_CHECKSUM, ER(ER_VIEW_CHECKSUM)); + if (thd->stmt_da->is_error() && + (thd->stmt_da->sql_errno() == ER_NO_SUCH_TABLE || + thd->stmt_da->sql_errno() == ER_FILE_NOT_FOUND)) + /* A missing table is just issued as a failed command */ + result_code= HA_ADMIN_FAILED; + else + /* Default failure code is corrupt table */ + result_code= HA_ADMIN_CORRUPT; + goto send_result; + } + + if (table->view) + { + DBUG_PRINT("admin", ("calling view_operator_func")); + result_code= (*view_operator_func)(thd, table); + goto send_result; + } + + if (table->schema_table) + { + result_code= HA_ADMIN_NOT_IMPLEMENTED; + goto send_result; + } + + if ((table->table->db_stat & HA_READ_ONLY) && open_for_modify) + { + /* purecov: begin inspected */ + char buff[FN_REFLEN + MYSQL_ERRMSG_SIZE]; + size_t length; + enum_sql_command save_sql_command= lex->sql_command; + DBUG_PRINT("admin", ("sending error message")); + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + length= my_snprintf(buff, sizeof(buff), ER(ER_OPEN_AS_READONLY), + table_name); + protocol->store(buff, length, system_charset_info); + trans_commit_stmt(thd); + trans_commit(thd); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + lex->reset_query_tables_list(FALSE); + /* + Restore Query_tables_list::sql_command value to make statement + safe for re-execution. + */ + lex->sql_command= save_sql_command; + table->table=0; // For query cache + if (protocol->write()) + goto err; + thd->stmt_da->reset_diagnostics_area(); + continue; + /* purecov: end */ + } + + /* + Close all instances of the table to allow MyISAM "repair" + to rename files. + @todo: This code does not close all instances of the table. + It only closes instances in other connections, but if this + connection has LOCK TABLE t1 a READ, t1 b WRITE, + both t1 instances will be kept open. + There is no need to execute this branch for InnoDB, which does + repair by recreate. There is no need to do it for OPTIMIZE, + which doesn't move files around. + Hence, this code should be moved to prepare_for_repair(), + and executed only for MyISAM engine. + */ + if (lock_type == TL_WRITE && !table->table->s->tmp_table) + { + if (wait_while_table_is_used(thd, table->table, + HA_EXTRA_PREPARE_FOR_RENAME)) + goto err; + DEBUG_SYNC(thd, "after_admin_flush"); + /* Flush entries in the query cache involving this table. */ + query_cache_invalidate3(thd, table->table, 0); + /* + XXX: hack: switch off open_for_modify to skip the + flush that is made later in the execution flow. + */ + open_for_modify= 0; + } + + if (table->table->s->crashed && operator_func == &handler::ha_check) + { + /* purecov: begin inspected */ + DBUG_PRINT("admin", ("sending crashed warning")); + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + protocol->store(STRING_WITH_LEN("warning"), system_charset_info); + protocol->store(STRING_WITH_LEN("Table is marked as crashed"), + system_charset_info); + if (protocol->write()) + goto err; + /* purecov: end */ + } + + if (operator_func == &handler::ha_repair && + !(check_opt->sql_flags & TT_USEFRM)) + { + if ((table->table->file->check_old_types() == HA_ADMIN_NEEDS_ALTER) || + (table->table->file->ha_check_for_upgrade(check_opt) == + HA_ADMIN_NEEDS_ALTER)) + { + DBUG_PRINT("admin", ("recreating table")); + trans_rollback_stmt(thd); + trans_rollback(thd); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + tmp_disable_binlog(thd); // binlogging is done by caller if wanted + result_code= mysql_recreate_table(thd, table); + reenable_binlog(thd); + /* + mysql_recreate_table() can push OK or ERROR. + Clear 'OK' status. If there is an error, keep it: + we will store the error message in a result set row + and then clear. + */ + if (thd->stmt_da->is_ok()) + thd->stmt_da->reset_diagnostics_area(); + table->table= NULL; + result_code= result_code ? HA_ADMIN_FAILED : HA_ADMIN_OK; + goto send_result; + } + } + + DBUG_PRINT("admin", ("calling operator_func '%s'", operator_name)); + result_code = (table->table->file->*operator_func)(thd, check_opt); + DBUG_PRINT("admin", ("operator_func returned: %d", result_code)); + +send_result: + + lex->cleanup_after_one_table_open(); + thd->clear_error(); // these errors shouldn't get client + { + List_iterator_fast it(thd->warning_info->warn_list()); + MYSQL_ERROR *err; + while ((err= it++)) + { + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store((char*) operator_name, system_charset_info); + protocol->store(warning_level_names[err->get_level()].str, + warning_level_names[err->get_level()].length, + system_charset_info); + protocol->store(err->get_message_text(), system_charset_info); + if (protocol->write()) + goto err; + } + thd->warning_info->clear_warning_info(thd->query_id); + } + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + +send_result_message: + + DBUG_PRINT("info", ("result_code: %d", result_code)); + switch (result_code) { + case HA_ADMIN_NOT_IMPLEMENTED: + { + char buf[MYSQL_ERRMSG_SIZE]; + size_t length=my_snprintf(buf, sizeof(buf), + ER(ER_CHECK_NOT_IMPLEMENTED), operator_name); + protocol->store(STRING_WITH_LEN("note"), system_charset_info); + protocol->store(buf, length, system_charset_info); + } + break; + + case HA_ADMIN_NOT_BASE_TABLE: + { + char buf[MYSQL_ERRMSG_SIZE]; + size_t length= my_snprintf(buf, sizeof(buf), + ER(ER_BAD_TABLE_ERROR), table_name); + protocol->store(STRING_WITH_LEN("note"), system_charset_info); + protocol->store(buf, length, system_charset_info); + } + break; + + case HA_ADMIN_OK: + protocol->store(STRING_WITH_LEN("status"), system_charset_info); + protocol->store(STRING_WITH_LEN("OK"), system_charset_info); + break; + + case HA_ADMIN_FAILED: + protocol->store(STRING_WITH_LEN("status"), system_charset_info); + protocol->store(STRING_WITH_LEN("Operation failed"), + system_charset_info); + break; + + case HA_ADMIN_REJECT: + protocol->store(STRING_WITH_LEN("status"), system_charset_info); + protocol->store(STRING_WITH_LEN("Operation need committed state"), + system_charset_info); + open_for_modify= FALSE; + break; + + case HA_ADMIN_ALREADY_DONE: + protocol->store(STRING_WITH_LEN("status"), system_charset_info); + protocol->store(STRING_WITH_LEN("Table is already up to date"), + system_charset_info); + break; + + case HA_ADMIN_CORRUPT: + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + protocol->store(STRING_WITH_LEN("Corrupt"), system_charset_info); + fatal_error=1; + break; + + case HA_ADMIN_INVALID: + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + protocol->store(STRING_WITH_LEN("Invalid argument"), + system_charset_info); + break; + + case HA_ADMIN_TRY_ALTER: + { + /* + This is currently used only by InnoDB. ha_innobase::optimize() answers + "try with alter", so here we close the table, do an ALTER TABLE, + reopen the table and do ha_innobase::analyze() on it. + We have to end the row, so analyze could return more rows. + */ + trans_commit_stmt(thd); + trans_commit(thd); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + DEBUG_SYNC(thd, "ha_admin_try_alter"); + protocol->store(STRING_WITH_LEN("note"), system_charset_info); + protocol->store(STRING_WITH_LEN( + "Table does not support optimize, doing recreate + analyze instead"), + system_charset_info); + if (protocol->write()) + goto err; + DBUG_PRINT("info", ("HA_ADMIN_TRY_ALTER, trying analyze...")); + TABLE_LIST *save_next_local= table->next_local, + *save_next_global= table->next_global; + table->next_local= table->next_global= 0; + tmp_disable_binlog(thd); // binlogging is done by caller if wanted + result_code= mysql_recreate_table(thd, table); + reenable_binlog(thd); + /* + mysql_recreate_table() can push OK or ERROR. + Clear 'OK' status. If there is an error, keep it: + we will store the error message in a result set row + and then clear. + */ + if (thd->stmt_da->is_ok()) + thd->stmt_da->reset_diagnostics_area(); + trans_commit_stmt(thd); + trans_commit(thd); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + table->table= NULL; + if (!result_code) // recreation went ok + { + /* Clear the ticket released above. */ + table->mdl_request.ticket= NULL; + DEBUG_SYNC(thd, "ha_admin_open_ltable"); + table->mdl_request.set_type(MDL_SHARED_WRITE); + if ((table->table= open_ltable(thd, table, lock_type, 0))) + { + result_code= table->table->file->ha_analyze(thd, check_opt); + if (result_code == HA_ADMIN_ALREADY_DONE) + result_code= HA_ADMIN_OK; + else if (result_code) // analyze failed + table->table->file->print_error(result_code, MYF(0)); + } + else + result_code= -1; // open failed + } + /* Start a new row for the final status row */ + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + if (result_code) // either mysql_recreate_table or analyze failed + { + DBUG_ASSERT(thd->is_error()); + if (thd->is_error()) + { + const char *err_msg= thd->stmt_da->message(); + if (!thd->vio_ok()) + { + sql_print_error("%s", err_msg); + } + else + { + /* Hijack the row already in-progress. */ + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + protocol->store(err_msg, system_charset_info); + if (protocol->write()) + goto err; + /* Start off another row for HA_ADMIN_FAILED */ + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + } + thd->clear_error(); + } + } + result_code= result_code ? HA_ADMIN_FAILED : HA_ADMIN_OK; + table->next_local= save_next_local; + table->next_global= save_next_global; + goto send_result_message; + } + case HA_ADMIN_WRONG_CHECKSUM: + { + protocol->store(STRING_WITH_LEN("note"), system_charset_info); + protocol->store(ER(ER_VIEW_CHECKSUM), strlen(ER(ER_VIEW_CHECKSUM)), + system_charset_info); + break; + } + + case HA_ADMIN_NEEDS_UPGRADE: + case HA_ADMIN_NEEDS_ALTER: + { + char buf[MYSQL_ERRMSG_SIZE]; + size_t length; + + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + length=my_snprintf(buf, sizeof(buf), ER(ER_TABLE_NEEDS_UPGRADE), + table->table_name); + protocol->store(buf, length, system_charset_info); + fatal_error=1; + break; + } + + default: // Probably HA_ADMIN_INTERNAL_ERROR + { + char buf[MYSQL_ERRMSG_SIZE]; + size_t length=my_snprintf(buf, sizeof(buf), + "Unknown - internal error %d during operation", + result_code); + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + protocol->store(buf, length, system_charset_info); + fatal_error=1; + break; + } + } + if (table->table) + { + if (table->table->s->tmp_table) + { + /* + If the table was not opened successfully, do not try to get + status information. (Bug#47633) + */ + if (open_for_modify && !open_error) + table->table->file->info(HA_STATUS_CONST); + } + else if (open_for_modify || fatal_error) + { + mysql_mutex_lock(&LOCK_open); + tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, + table->db, table->table_name); + mysql_mutex_unlock(&LOCK_open); + /* + May be something modified. Consequently, we have to + invalidate the query cache. + */ + table->table= 0; // For query cache + query_cache_invalidate3(thd, table, 0); + } + } + /* Error path, a admin command failed. */ + trans_commit_stmt(thd); + trans_commit_implicit(thd); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + + /* + If it is CHECK TABLE v1, v2, v3, and v1, v2, v3 are views, we will run + separate open_tables() for each CHECK TABLE argument. + Right now we do not have a separate method to reset the prelocking + state in the lex to the state after parsing, so each open will pollute + this state: add elements to lex->srotuines_list, TABLE_LISTs to + lex->query_tables. Below is a lame attempt to recover from this + pollution. + @todo: have a method to reset a prelocking context, or use separate + contexts for each open. + */ + for (Sroutine_hash_entry *rt= + (Sroutine_hash_entry*)thd->lex->sroutines_list.first; + rt; rt= rt->next) + rt->mdl_request.ticket= NULL; + + if (protocol->write()) + goto err; + } + + my_eof(thd); + DBUG_RETURN(FALSE); + +err: + trans_rollback_stmt(thd); + trans_rollback(thd); + close_thread_tables(thd); // Shouldn't be needed + thd->mdl_context.release_transactional_locks(); + if (table) + table->table=0; + DBUG_RETURN(TRUE); +} + + +/* + Assigned specified indexes for a table into key cache + + SYNOPSIS + mysql_assign_to_keycache() + thd Thread object + tables Table list (one table only) + + RETURN VALUES + FALSE ok + TRUE error +*/ + +bool mysql_assign_to_keycache(THD* thd, TABLE_LIST* tables, + LEX_STRING *key_cache_name) +{ + HA_CHECK_OPT check_opt; + KEY_CACHE *key_cache; + DBUG_ENTER("mysql_assign_to_keycache"); + + check_opt.init(); + mysql_mutex_lock(&LOCK_global_system_variables); + if (!(key_cache= get_key_cache(key_cache_name))) + { + mysql_mutex_unlock(&LOCK_global_system_variables); + my_error(ER_UNKNOWN_KEY_CACHE, MYF(0), key_cache_name->str); + DBUG_RETURN(TRUE); + } + mysql_mutex_unlock(&LOCK_global_system_variables); + check_opt.key_cache= key_cache; + DBUG_RETURN(mysql_admin_table(thd, tables, &check_opt, + "assign_to_keycache", TL_READ_NO_INSERT, 0, 0, + 0, 0, &handler::assign_to_keycache, 0)); +} + + +/* + Preload specified indexes for a table into key cache + + SYNOPSIS + mysql_preload_keys() + thd Thread object + tables Table list (one table only) + + RETURN VALUES + FALSE ok + TRUE error +*/ + +bool mysql_preload_keys(THD* thd, TABLE_LIST* tables) +{ + DBUG_ENTER("mysql_preload_keys"); + /* + We cannot allow concurrent inserts. The storage engine reads + directly from the index file, bypassing the cache. It could read + outdated information if parallel inserts into cache blocks happen. + */ + DBUG_RETURN(mysql_admin_table(thd, tables, 0, + "preload_keys", TL_READ_NO_INSERT, 0, 0, 0, 0, + &handler::preload_keys, 0)); +} + + +bool Analyze_table_statement::execute(THD *thd) +{ + TABLE_LIST *first_table= m_lex->select_lex.table_list.first; + bool res= TRUE; + thr_lock_type lock_type = TL_READ_NO_INSERT; + DBUG_ENTER("Analyze_table_statement::execute"); + + if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, + FALSE, UINT_MAX, FALSE)) + goto error; + thd->enable_slow_log= opt_log_slow_admin_statements; + res= mysql_admin_table(thd, first_table, &m_lex->check_opt, + "analyze", lock_type, 1, 0, 0, 0, + &handler::ha_analyze, 0); + /* ! we write after unlocking the table */ + if (!res && !m_lex->no_write_to_binlog) + { + /* + Presumably, ANALYZE and binlog writing doesn't require synchronization + */ + res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + } + m_lex->select_lex.table_list.first= first_table; + m_lex->query_tables= first_table; + +error: + DBUG_RETURN(res); +} + + +bool Check_table_statement::execute(THD *thd) +{ + TABLE_LIST *first_table= m_lex->select_lex.table_list.first; + thr_lock_type lock_type = TL_READ_NO_INSERT; + bool res= TRUE; + DBUG_ENTER("Check_table_statement::execute"); + + if (check_table_access(thd, SELECT_ACL, first_table, + TRUE, UINT_MAX, FALSE)) + goto error; /* purecov: inspected */ + thd->enable_slow_log= opt_log_slow_admin_statements; + + res= mysql_admin_table(thd, first_table, &m_lex->check_opt, "check", + lock_type, 0, 0, HA_OPEN_FOR_REPAIR, 0, + &handler::ha_check, &view_checksum); + + m_lex->select_lex.table_list.first= first_table; + m_lex->query_tables= first_table; + +error: + DBUG_RETURN(res); +} + + +bool Optimize_table_statement::execute(THD *thd) +{ + TABLE_LIST *first_table= m_lex->select_lex.table_list.first; + bool res= TRUE; + DBUG_ENTER("Optimize_table_statement::execute"); + + if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, + FALSE, UINT_MAX, FALSE)) + goto error; /* purecov: inspected */ + thd->enable_slow_log= opt_log_slow_admin_statements; + res= (specialflag & (SPECIAL_SAFE_MODE | SPECIAL_NO_NEW_FUNC)) ? + mysql_recreate_table(thd, first_table) : + mysql_admin_table(thd, first_table, &m_lex->check_opt, + "optimize", TL_WRITE, 1, 0, 0, 0, + &handler::ha_optimize, 0); + /* ! we write after unlocking the table */ + if (!res && !m_lex->no_write_to_binlog) + { + /* + Presumably, OPTIMIZE and binlog writing doesn't require synchronization + */ + res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + } + m_lex->select_lex.table_list.first= first_table; + m_lex->query_tables= first_table; + +error: + DBUG_RETURN(res); +} + + +bool Repair_table_statement::execute(THD *thd) +{ + TABLE_LIST *first_table= m_lex->select_lex.table_list.first; + bool res= TRUE; + DBUG_ENTER("Repair_table_statement::execute"); + + if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, + FALSE, UINT_MAX, FALSE)) + goto error; /* purecov: inspected */ + thd->enable_slow_log= opt_log_slow_admin_statements; + res= mysql_admin_table(thd, first_table, &m_lex->check_opt, "repair", + TL_WRITE, 1, + test(m_lex->check_opt.sql_flags & TT_USEFRM), + HA_OPEN_FOR_REPAIR, &prepare_for_repair, + &handler::ha_repair, 0); + + /* ! we write after unlocking the table */ + if (!res && !m_lex->no_write_to_binlog) + { + /* + Presumably, REPAIR and binlog writing doesn't require synchronization + */ + res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + } + m_lex->select_lex.table_list.first= first_table; + m_lex->query_tables= first_table; + +error: + DBUG_RETURN(res); +} diff --git a/sql/sql_table_maintenance.h b/sql/sql_table_maintenance.h new file mode 100644 index 00000000000..fdfffec8361 --- /dev/null +++ b/sql/sql_table_maintenance.h @@ -0,0 +1,132 @@ +/* Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifndef SQL_TABLE_MAINTENANCE_H +#define SQL_TABLE_MAINTENANCE_H + + +bool mysql_assign_to_keycache(THD* thd, TABLE_LIST* table_list, + LEX_STRING *key_cache_name); +bool mysql_preload_keys(THD* thd, TABLE_LIST* table_list); +int reassign_keycache_tables(THD* thd, KEY_CACHE *src_cache, + KEY_CACHE *dst_cache); + +/** + Analyze_statement represents the ANALYZE TABLE statement. +*/ +class Analyze_table_statement : public Sql_statement +{ +public: + /** + Constructor, used to represent a ANALYZE TABLE statement. + @param lex the LEX structure for this statement. + */ + Analyze_table_statement(LEX *lex) + : Sql_statement(lex) + {} + + ~Analyze_table_statement() + {} + + /** + Execute a ANALYZE TABLE statement at runtime. + @param thd the current thread. + @return false on success. + */ + bool execute(THD *thd); +}; + + + +/** + Check_table_statement represents the CHECK TABLE statement. +*/ +class Check_table_statement : public Sql_statement +{ +public: + /** + Constructor, used to represent a CHECK TABLE statement. + @param lex the LEX structure for this statement. + */ + Check_table_statement(LEX *lex) + : Sql_statement(lex) + {} + + ~Check_table_statement() + {} + + /** + Execute a CHECK TABLE statement at runtime. + @param thd the current thread. + @return false on success. + */ + bool execute(THD *thd); +}; + + + +/** + Optimize_table_statement represents the OPTIMIZE TABLE statement. +*/ +class Optimize_table_statement : public Sql_statement +{ +public: + /** + Constructor, used to represent a OPTIMIZE TABLE statement. + @param lex the LEX structure for this statement. + */ + Optimize_table_statement(LEX *lex) + : Sql_statement(lex) + {} + + ~Optimize_table_statement() + {} + + /** + Execute a OPTIMIZE TABLE statement at runtime. + @param thd the current thread. + @return false on success. + */ + bool execute(THD *thd); +}; + + + +/** + Repair_table_statement represents the REPAIR TABLE statement. +*/ +class Repair_table_statement : public Sql_statement +{ +public: + /** + Constructor, used to represent a REPAIR TABLE statement. + @param lex the LEX structure for this statement. + */ + Repair_table_statement(LEX *lex) + : Sql_statement(lex) + {} + + ~Repair_table_statement() + {} + + /** + Execute a REPAIR TABLE statement at runtime. + @param thd the current thread. + @return false on success. + */ + bool execute(THD *thd); +}; + +#endif diff --git a/sql/sql_truncate.cc b/sql/sql_truncate.cc index ee5c707cd69..62e51d9dfe6 100644 --- a/sql/sql_truncate.cc +++ b/sql/sql_truncate.cc @@ -13,7 +13,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "sql_truncate.h" #include "sql_priv.h" #include "transaction.h" #include "debug_sync.h" @@ -25,6 +24,9 @@ #include "sql_handler.h" // mysql_ha_rm_tables #include "datadict.h" // dd_recreate_table() #include "lock.h" // MYSQL_OPEN_TEMPORARY_ONLY +#include "sql_acl.h" // DROP_ACL +#include "sql_parse.h" // check_one_table_access() +#include "sql_truncate.h" /* @@ -242,6 +244,7 @@ static bool open_and_lock_table_for_truncate(THD *thd, TABLE_LIST *table_ref, MDL_ticket **ticket_downgrade) { TABLE *table= NULL; + handlerton *table_type; DBUG_ENTER("open_and_lock_table_for_truncate"); DBUG_ASSERT(table_ref->lock_type == TL_WRITE); @@ -265,7 +268,8 @@ static bool open_and_lock_table_for_truncate(THD *thd, TABLE_LIST *table_ref, table_ref->table_name, FALSE))) DBUG_RETURN(TRUE); - *hton_can_recreate= ha_check_storage_engine_flag(table->s->db_type(), + table_type= table->s->db_type(); + *hton_can_recreate= ha_check_storage_engine_flag(table_type, HTON_CAN_RECREATE); table_ref->mdl_request.ticket= table->mdl_ticket; } @@ -281,11 +285,25 @@ static bool open_and_lock_table_for_truncate(THD *thd, TABLE_LIST *table_ref, MYSQL_OPEN_SKIP_TEMPORARY)) DBUG_RETURN(TRUE); - if (dd_check_storage_engine_flag(thd, table_ref->db, table_ref->table_name, - HTON_CAN_RECREATE, hton_can_recreate)) + if (dd_frm_storage_engine(thd, table_ref->db, table_ref->table_name, + &table_type)) DBUG_RETURN(TRUE); + *hton_can_recreate= ha_check_storage_engine_flag(table_type, + HTON_CAN_RECREATE); } +#ifdef WITH_PARTITION_STORAGE_ENGINE + /* + TODO: Add support for TRUNCATE PARTITION for NDB and other engines + supporting native partitioning. + */ + if (thd->lex->alter_info.flags & ALTER_ADMIN_PARTITION && + table_type != partition_hton) + { + my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0)); + DBUG_RETURN(TRUE); + } +#endif DEBUG_SYNC(thd, "lock_table_for_truncate"); if (*hton_can_recreate) @@ -477,3 +495,27 @@ bool mysql_truncate_table(THD *thd, TABLE_LIST *table_ref) DBUG_RETURN(test(error)); } + +bool Truncate_statement::execute(THD *thd) +{ + TABLE_LIST *first_table= thd->lex->select_lex.table_list.first; + bool res= TRUE; + DBUG_ENTER("Truncate_statement::execute"); + + if (check_one_table_access(thd, DROP_ACL, first_table)) + goto error; + /* + Don't allow this within a transaction because we want to use + re-generate table + */ + if (thd->in_active_multi_stmt_transaction()) + { + my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, + ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); + goto error; + } + if (! (res= mysql_truncate_table(thd, first_table))) + my_ok(thd); +error: + DBUG_RETURN(res); +} diff --git a/sql/sql_truncate.h b/sql/sql_truncate.h index 11c07c7187c..b8b1d3da53d 100644 --- a/sql/sql_truncate.h +++ b/sql/sql_truncate.h @@ -20,4 +20,30 @@ struct TABLE_LIST; bool mysql_truncate_table(THD *thd, TABLE_LIST *table_ref); +/** + Truncate_statement represents the TRUNCATE statement. +*/ +class Truncate_statement : public Sql_statement +{ +public: + /** + Constructor, used to represent a ALTER TABLE statement. + @param lex the LEX structure for this statement. + */ + Truncate_statement(LEX *lex) + : Sql_statement(lex) + {} + + ~Truncate_statement() + {} + + /** + Execute a TRUNCATE statement at runtime. + @param thd the current thread. + @return false on success. + */ + bool execute(THD *thd); +}; + + #endif diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index ca951897055..64ce1a5f64b 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -51,6 +51,10 @@ #include "sp_pcontext.h" #include "sp_rcontext.h" #include "sp.h" +#include "sql_alter_table.h" // Alter_table*_statement +#include "sql_truncate.h" // Truncate_statement +#include "sql_table_maintenance.h" // Analyze/Check..._table_stmt +#include "sql_partition_admin.h" // Alter_table_*_partition_stmt #include "sql_signal.h" #include "event_parse_data.h" #include @@ -6172,9 +6176,20 @@ alter: lex->no_write_to_binlog= 0; lex->create_info.storage_media= HA_SM_DEFAULT; lex->create_last_non_select_table= lex->last_table(); + DBUG_ASSERT(!lex->m_stmt); } alter_commands - {} + { + THD *thd= YYTHD; + LEX *lex= thd->lex; + if (!lex->m_stmt) + { + /* Create a generic ALTER TABLE statment. */ + lex->m_stmt= new (thd->mem_root) Alter_table_statement(lex); + if (lex->m_stmt == NULL) + MYSQL_YYABORT; + } + } | ALTER DATABASE ident_or_empty { Lex->create_info.default_table_charset= NULL; @@ -6393,38 +6408,54 @@ alter_commands: | OPTIMIZE PARTITION_SYM opt_no_write_to_binlog all_or_alt_part_name_list { - LEX *lex= Lex; - lex->sql_command= SQLCOM_OPTIMIZE; - lex->alter_info.flags|= ALTER_ADMIN_PARTITION; + THD *thd= YYTHD; + LEX *lex= thd->lex; lex->no_write_to_binlog= $3; lex->check_opt.init(); + DBUG_ASSERT(!lex->m_stmt); + lex->m_stmt= new (thd->mem_root) + Alter_table_optimize_partition_statement(lex); + if (lex->m_stmt == NULL) + MYSQL_YYABORT; } opt_no_write_to_binlog | ANALYZE_SYM PARTITION_SYM opt_no_write_to_binlog all_or_alt_part_name_list { - LEX *lex= Lex; - lex->sql_command= SQLCOM_ANALYZE; - lex->alter_info.flags|= ALTER_ADMIN_PARTITION; + THD *thd= YYTHD; + LEX *lex= thd->lex; lex->no_write_to_binlog= $3; lex->check_opt.init(); + DBUG_ASSERT(!lex->m_stmt); + lex->m_stmt= new (thd->mem_root) + Alter_table_analyze_partition_statement(lex); + if (lex->m_stmt == NULL) + MYSQL_YYABORT; } | CHECK_SYM PARTITION_SYM all_or_alt_part_name_list { - LEX *lex= Lex; - lex->sql_command= SQLCOM_CHECK; - lex->alter_info.flags|= ALTER_ADMIN_PARTITION; + THD *thd= YYTHD; + LEX *lex= thd->lex; lex->check_opt.init(); + DBUG_ASSERT(!lex->m_stmt); + lex->m_stmt= new (thd->mem_root) + Alter_table_check_partition_statement(lex); + if (lex->m_stmt == NULL) + MYSQL_YYABORT; } opt_mi_check_type | REPAIR PARTITION_SYM opt_no_write_to_binlog all_or_alt_part_name_list { - LEX *lex= Lex; - lex->sql_command= SQLCOM_REPAIR; - lex->alter_info.flags|= ALTER_ADMIN_PARTITION; + THD *thd= YYTHD; + LEX *lex= thd->lex; lex->no_write_to_binlog= $3; lex->check_opt.init(); + DBUG_ASSERT(!lex->m_stmt); + lex->m_stmt= new (thd->mem_root) + Alter_table_repair_partition_statement(lex); + if (lex->m_stmt == NULL) + MYSQL_YYABORT; } opt_mi_repair_type | COALESCE PARTITION_SYM opt_no_write_to_binlog real_ulong_num @@ -6436,12 +6467,14 @@ alter_commands: } | TRUNCATE_SYM PARTITION_SYM all_or_alt_part_name_list { - LEX *lex= Lex; - lex->sql_command= SQLCOM_TRUNCATE; - lex->alter_info.flags|= ALTER_ADMIN_PARTITION; + THD *thd= YYTHD; + LEX *lex= thd->lex; lex->check_opt.init(); - lex->query_tables->mdl_request.set_type(MDL_SHARED_NO_READ_WRITE); - lex->query_tables->lock_type= TL_WRITE; + DBUG_ASSERT(!lex->m_stmt); + lex->m_stmt= new (thd->mem_root) + Alter_table_truncate_partition_statement(lex); + if (lex->m_stmt == NULL) + MYSQL_YYABORT; } | reorg_partition_rule ; @@ -6878,7 +6911,14 @@ repair: YYPS->m_lock_type= TL_UNLOCK; } table_list opt_mi_repair_type - {} + { + THD *thd= YYTHD; + LEX* lex= thd->lex; + DBUG_ASSERT(!lex->m_stmt); + lex->m_stmt= new (thd->mem_root) Repair_table_statement(lex); + if (lex->m_stmt == NULL) + MYSQL_YYABORT; + } ; opt_mi_repair_type: @@ -6909,7 +6949,14 @@ analyze: YYPS->m_lock_type= TL_UNLOCK; } table_list - {} + { + THD *thd= YYTHD; + LEX* lex= thd->lex; + DBUG_ASSERT(!lex->m_stmt); + lex->m_stmt= new (thd->mem_root) Analyze_table_statement(lex); + if (lex->m_stmt == NULL) + MYSQL_YYABORT; + } ; binlog_base64_event: @@ -6937,7 +6984,14 @@ check: YYPS->m_lock_type= TL_UNLOCK; } table_list opt_mi_check_type - {} + { + THD *thd= YYTHD; + LEX* lex= thd->lex; + DBUG_ASSERT(!lex->m_stmt); + lex->m_stmt= new (thd->mem_root) Check_table_statement(lex); + if (lex->m_stmt == NULL) + MYSQL_YYABORT; + } ; opt_mi_check_type: @@ -6971,7 +7025,14 @@ optimize: YYPS->m_lock_type= TL_UNLOCK; } table_list - {} + { + THD *thd= YYTHD; + LEX* lex= thd->lex; + DBUG_ASSERT(!lex->m_stmt); + lex->m_stmt= new (thd->mem_root) Optimize_table_statement(lex); + if (lex->m_stmt == NULL) + MYSQL_YYABORT; + } ; opt_no_write_to_binlog: @@ -10698,7 +10759,14 @@ truncate: YYPS->m_mdl_type= MDL_SHARED_NO_READ_WRITE; } table_name - {} + { + THD *thd= YYTHD; + LEX* lex= thd->lex; + DBUG_ASSERT(!lex->m_stmt); + lex->m_stmt= new (thd->mem_root) Truncate_statement(lex); + if (lex->m_stmt == NULL) + MYSQL_YYABORT; + } ; opt_table_sym: -- cgit v1.2.1 From a3b9557b3aee40df2671ca6d828d6f7abde4b467 Mon Sep 17 00:00:00 2001 From: Magne Mahre Date: Mon, 16 Aug 2010 15:05:01 +0200 Subject: Bug#55001 Change in behavior: thread_concurrency (docs, example files) The system variable 'thread_concurrency' has been (re-)enabled on all platforms, to prevent startup errors. 'thread_concurrency' is unused and has no effect, on any platform, in MySQL 5.1 and later versions. It will be deprecated, and removed, in context of worklog WL#5265 mysql-test/include/have_thread_concurrency.inc: include/have_thread_concurrency.inc was only needed to support platforms that didn't have the thread_concurrency variable available. Since the variable is now available on all platforms, the file is removed. --- sql/sys_vars.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'sql') diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 13d1e646f04..fdda514986b 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -1617,14 +1617,17 @@ static Sys_var_charptr Sys_socket( READ_ONLY GLOBAL_VAR(mysqld_unix_port), CMD_LINE(REQUIRED_ARG), IN_FS_CHARSET, DEFAULT(0)); -#ifdef HAVE_THR_SETCONCURRENCY +/* + thread_concurrency is a no-op on all platforms since + MySQL 5.1. It will be removed in the context of + WL#5265 +*/ static Sys_var_ulong Sys_thread_concurrency( "thread_concurrency", "Permits the application to give the threads system a hint for " "the desired number of threads that should be run at the same time", READ_ONLY GLOBAL_VAR(concurrency), CMD_LINE(REQUIRED_ARG), VALID_RANGE(1, 512), DEFAULT(DEFAULT_CONCURRENCY), BLOCK_SIZE(1)); -#endif static Sys_var_ulong Sys_thread_stack( "thread_stack", "The stack size for each thread", -- cgit v1.2.1 From b67924eb4e6c29dd9f9e4ceddfc30b6f0a93ef0e Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Mon, 16 Aug 2010 16:25:23 +0200 Subject: Rename of sql_alter_table -> sql_alter and sql_table_maintenance -> sql_admin --- sql/CMakeLists.txt | 4 +- sql/Makefile.am | 8 +- sql/sql_admin.cc | 1004 ++++++++++++++++++++++++++++++++++++++++++ sql/sql_admin.h | 132 ++++++ sql/sql_alter.cc | 106 +++++ sql/sql_alter.h | 66 +++ sql/sql_alter_table.cc | 106 ----- sql/sql_alter_table.h | 66 --- sql/sql_lex.h | 2 +- sql/sql_parse.cc | 2 +- sql/sql_partition_admin.cc | 2 +- sql/sql_table_maintenance.cc | 1004 ------------------------------------------ sql/sql_table_maintenance.h | 132 ------ sql/sql_yacc.yy | 4 +- 14 files changed, 1319 insertions(+), 1319 deletions(-) create mode 100644 sql/sql_admin.cc create mode 100644 sql/sql_admin.h create mode 100644 sql/sql_alter.cc create mode 100644 sql/sql_alter.h delete mode 100644 sql/sql_alter_table.cc delete mode 100644 sql/sql_alter_table.h delete mode 100644 sql/sql_table_maintenance.cc delete mode 100644 sql/sql_table_maintenance.h (limited to 'sql') diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 1a162d39e0b..a32c4a4b7e2 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -72,8 +72,8 @@ SET (SQL_SOURCE partition_info.cc rpl_utility.cc rpl_injector.cc sql_locale.cc rpl_rli.cc rpl_mi.cc sql_servers.cc sql_audit.cc sql_connect.cc scheduler.cc sql_partition_admin.cc - sql_profile.cc event_parse_data.cc sql_alter_table.cc - sql_signal.cc rpl_handler.cc mdl.cc sql_table_maintenance.cc + sql_profile.cc event_parse_data.cc sql_alter.cc + sql_signal.cc rpl_handler.cc mdl.cc sql_admin.cc transaction.cc sys_vars.cc sql_truncate.cc datadict.cc ${GEN_SOURCES} ${MYSYS_LIBWRAP_SOURCE}) diff --git a/sql/Makefile.am b/sql/Makefile.am index b99afcbdbeb..4fb7bfe919c 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -122,11 +122,11 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.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_table.h sql_partition_admin.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_table_maintenance.h datadict.h + sql_admin.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 \ @@ -143,7 +143,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.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_table_maintenance.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 \ @@ -172,7 +172,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.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_table.cc sql_partition_admin.cc sha2.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 diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc new file mode 100644 index 00000000000..6f0405bd4a8 --- /dev/null +++ b/sql/sql_admin.cc @@ -0,0 +1,1004 @@ +/* Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "sql_class.h" // THD +#include "keycaches.h" // get_key_cache +#include "sql_base.h" // Open_table_context +#include "lock.h" // MYSQL_OPEN_* +#include "sql_handler.h" // mysql_ha_rm_tables +#include "partition_element.h" // PART_ADMIN +#include "sql_partition.h" // set_part_state +#include "transaction.h" // trans_rollback_stmt +#include "sql_view.h" // view_checksum +#include "sql_table.h" // mysql_recreate_table +#include "debug_sync.h" // DEBUG_SYNC +#include "sql_acl.h" // *_ACL +#include "sp.h" // Sroutine_hash_entry +#include "sql_parse.h" // check_table_access +#include "sql_admin.h" + +static int send_check_errmsg(THD *thd, TABLE_LIST* table, + const char* operator_name, const char* errmsg) + +{ + Protocol *protocol= thd->protocol; + protocol->prepare_for_resend(); + protocol->store(table->alias, system_charset_info); + protocol->store((char*) operator_name, system_charset_info); + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + protocol->store(errmsg, system_charset_info); + thd->clear_error(); + if (protocol->write()) + return -1; + return 1; +} + + +static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, + HA_CHECK_OPT *check_opt) +{ + int error= 0; + TABLE tmp_table, *table; + TABLE_SHARE *share; + bool has_mdl_lock= FALSE; + char from[FN_REFLEN],tmp[FN_REFLEN+32]; + const char **ext; + MY_STAT stat_info; + Open_table_context ot_ctx(thd, (MYSQL_OPEN_IGNORE_FLUSH | + MYSQL_OPEN_HAS_MDL_LOCK | + MYSQL_LOCK_IGNORE_TIMEOUT)); + DBUG_ENTER("prepare_for_repair"); + + if (!(check_opt->sql_flags & TT_USEFRM)) + DBUG_RETURN(0); + + if (!(table= table_list->table)) + { + char key[MAX_DBKEY_LENGTH]; + uint key_length; + /* + If the table didn't exist, we have a shared metadata lock + on it that is left from mysql_admin_table()'s attempt to + open it. Release the shared metadata lock before trying to + acquire the exclusive lock to satisfy MDL asserts and avoid + deadlocks. + */ + thd->mdl_context.release_transactional_locks(); + /* + Attempt to do full-blown table open in mysql_admin_table() has failed. + Let us try to open at least a .FRM for this table. + */ + my_hash_value_type hash_value; + + key_length= create_table_def_key(thd, key, table_list, 0); + table_list->mdl_request.init(MDL_key::TABLE, + table_list->db, table_list->table_name, + MDL_EXCLUSIVE); + + if (lock_table_names(thd, table_list, table_list->next_global, + thd->variables.lock_wait_timeout, + MYSQL_OPEN_SKIP_TEMPORARY)) + DBUG_RETURN(0); + has_mdl_lock= TRUE; + + hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length); + mysql_mutex_lock(&LOCK_open); + if (!(share= (get_table_share(thd, table_list, key, key_length, 0, + &error, hash_value)))) + { + mysql_mutex_unlock(&LOCK_open); + DBUG_RETURN(0); // Can't open frm file + } + + if (open_table_from_share(thd, share, "", 0, 0, 0, &tmp_table, FALSE)) + { + release_table_share(share); + mysql_mutex_unlock(&LOCK_open); + DBUG_RETURN(0); // Out of memory + } + mysql_mutex_unlock(&LOCK_open); + table= &tmp_table; + } + + /* A MERGE table must not come here. */ + DBUG_ASSERT(table->file->ht->db_type != DB_TYPE_MRG_MYISAM); + + /* + REPAIR TABLE ... USE_FRM for temporary tables makes little sense. + */ + if (table->s->tmp_table) + { + error= send_check_errmsg(thd, table_list, "repair", + "Cannot repair temporary table from .frm file"); + goto end; + } + + /* + User gave us USE_FRM which means that the header in the index file is + trashed. + In this case we will try to fix the table the following way: + - Rename the data file to a temporary name + - Truncate the table + - Replace the new data file with the old one + - Run a normal repair using the new index file and the old data file + */ + + if (table->s->frm_version != FRM_VER_TRUE_VARCHAR) + { + error= send_check_errmsg(thd, table_list, "repair", + "Failed repairing incompatible .frm file"); + goto end; + } + + /* + Check if this is a table type that stores index and data separately, + like ISAM or MyISAM. We assume fixed order of engine file name + extentions array. First element of engine file name extentions array + is meta/index file extention. Second element - data file extention. + */ + ext= table->file->bas_ext(); + if (!ext[0] || !ext[1]) + goto end; // No data file + + // Name of data file + strxmov(from, table->s->normalized_path.str, ext[1], NullS); + if (!mysql_file_stat(key_file_misc, from, &stat_info, MYF(0))) + goto end; // Can't use USE_FRM flag + + my_snprintf(tmp, sizeof(tmp), "%s-%lx_%lx", + from, current_pid, thd->thread_id); + + if (table_list->table) + { + /* + Table was successfully open in mysql_admin_table(). Now we need + to close it, but leave it protected by exclusive metadata lock. + */ + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + goto end; + close_all_tables_for_name(thd, table_list->table->s, FALSE); + table_list->table= 0; + } + /* + After this point we have an exclusive metadata lock on our table + in both cases when table was successfully open in mysql_admin_table() + and when it was open in prepare_for_repair(). + */ + + if (my_rename(from, tmp, MYF(MY_WME))) + { + error= send_check_errmsg(thd, table_list, "repair", + "Failed renaming data file"); + goto end; + } + if (dd_recreate_table(thd, table_list->db, table_list->table_name)) + { + error= send_check_errmsg(thd, table_list, "repair", + "Failed generating table from .frm file"); + goto end; + } + /* + 'FALSE' for 'using_transactions' means don't postpone + invalidation till the end of a transaction, but do it + immediately. + */ + query_cache_invalidate3(thd, table_list, FALSE); + if (mysql_file_rename(key_file_misc, tmp, from, MYF(MY_WME))) + { + error= send_check_errmsg(thd, table_list, "repair", + "Failed restoring .MYD file"); + goto end; + } + + if (thd->locked_tables_list.reopen_tables(thd)) + goto end; + + /* + Now we should be able to open the partially repaired table + to finish the repair in the handler later on. + */ + if (open_table(thd, table_list, thd->mem_root, &ot_ctx)) + { + error= send_check_errmsg(thd, table_list, "repair", + "Failed to open partially repaired table"); + goto end; + } + +end: + thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); + if (table == &tmp_table) + { + mysql_mutex_lock(&LOCK_open); + closefrm(table, 1); // Free allocated memory + mysql_mutex_unlock(&LOCK_open); + } + /* In case of a temporary table there will be no metadata lock. */ + if (error && has_mdl_lock) + thd->mdl_context.release_transactional_locks(); + + DBUG_RETURN(error); +} + + + +/* + RETURN VALUES + FALSE Message sent to net (admin operation went ok) + TRUE Message should be sent by caller + (admin operation or network communication failed) +*/ +static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, + HA_CHECK_OPT* check_opt, + const char *operator_name, + thr_lock_type lock_type, + bool open_for_modify, + bool no_warnings_for_error, + uint extra_open_options, + int (*prepare_func)(THD *, TABLE_LIST *, + HA_CHECK_OPT *), + int (handler::*operator_func)(THD *, + HA_CHECK_OPT *), + int (view_operator_func)(THD *, TABLE_LIST*)) +{ + TABLE_LIST *table; + SELECT_LEX *select= &thd->lex->select_lex; + List field_list; + Item *item; + Protocol *protocol= thd->protocol; + LEX *lex= thd->lex; + int result_code; + DBUG_ENTER("mysql_admin_table"); + + field_list.push_back(item = new Item_empty_string("Table", NAME_CHAR_LEN*2)); + item->maybe_null = 1; + field_list.push_back(item = new Item_empty_string("Op", 10)); + item->maybe_null = 1; + field_list.push_back(item = new Item_empty_string("Msg_type", 10)); + item->maybe_null = 1; + field_list.push_back(item = new Item_empty_string("Msg_text", 255)); + item->maybe_null = 1; + if (protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); + + mysql_ha_rm_tables(thd, tables); + + for (table= tables; table; table= table->next_local) + { + char table_name[NAME_LEN*2+2]; + char* db = table->db; + bool fatal_error=0; + bool open_error; + + DBUG_PRINT("admin", ("table: '%s'.'%s'", table->db, table->table_name)); + DBUG_PRINT("admin", ("extra_open_options: %u", extra_open_options)); + strxmov(table_name, db, ".", table->table_name, NullS); + thd->open_options|= extra_open_options; + table->lock_type= lock_type; + /* + To make code safe for re-execution we need to reset type of MDL + request as code below may change it. + To allow concurrent execution of read-only operations we acquire + weak metadata lock for them. + */ + table->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? + MDL_SHARED_NO_READ_WRITE : MDL_SHARED_READ); + /* open only one table from local list of command */ + { + TABLE_LIST *save_next_global, *save_next_local; + save_next_global= table->next_global; + table->next_global= 0; + save_next_local= table->next_local; + table->next_local= 0; + select->table_list.first= table; + /* + Time zone tables and SP tables can be add to lex->query_tables list, + so it have to be prepared. + TODO: Investigate if we can put extra tables into argument instead of + using lex->query_tables + */ + lex->query_tables= table; + lex->query_tables_last= &table->next_global; + lex->query_tables_own_last= 0; + thd->no_warnings_for_error= no_warnings_for_error; + if (view_operator_func == NULL) + table->required_type=FRMTYPE_TABLE; + + open_error= open_and_lock_tables(thd, table, TRUE, 0); + thd->no_warnings_for_error= 0; + table->next_global= save_next_global; + table->next_local= save_next_local; + thd->open_options&= ~extra_open_options; + /* + Under locked tables, we know that the table can be opened, + so any errors opening the table are logical errors. + In these cases it does not make sense to try to repair. + */ + if (open_error && thd->locked_tables_mode) + { + result_code= HA_ADMIN_FAILED; + goto send_result; + } +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (table->table) + { + /* + Set up which partitions that should be processed + if ALTER TABLE t ANALYZE/CHECK/OPTIMIZE/REPAIR PARTITION .. + CACHE INDEX/LOAD INDEX for specified partitions + */ + Alter_info *alter_info= &lex->alter_info; + + if (alter_info->flags & ALTER_ADMIN_PARTITION) + { + if (!table->table->part_info) + { + my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0)); + DBUG_RETURN(TRUE); + } + uint num_parts_found; + uint num_parts_opt= alter_info->partition_names.elements; + num_parts_found= set_part_state(alter_info, table->table->part_info, + PART_ADMIN); + if (num_parts_found != num_parts_opt && + (!(alter_info->flags & ALTER_ALL_PARTITION))) + { + char buff[FN_REFLEN + MYSQL_ERRMSG_SIZE]; + size_t length; + DBUG_PRINT("admin", ("sending non existent partition error")); + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + length= my_snprintf(buff, sizeof(buff), + ER(ER_DROP_PARTITION_NON_EXISTENT), + table_name); + protocol->store(buff, length, system_charset_info); + if(protocol->write()) + goto err; + my_eof(thd); + goto err; + } + } + } +#endif + } + DBUG_PRINT("admin", ("table: 0x%lx", (long) table->table)); + + if (prepare_func) + { + DBUG_PRINT("admin", ("calling prepare_func")); + switch ((*prepare_func)(thd, table, check_opt)) { + case 1: // error, message written to net + trans_rollback_stmt(thd); + trans_rollback(thd); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + DBUG_PRINT("admin", ("simple error, admin next table")); + continue; + case -1: // error, message could be written to net + /* purecov: begin inspected */ + DBUG_PRINT("admin", ("severe error, stop")); + goto err; + /* purecov: end */ + default: // should be 0 otherwise + DBUG_PRINT("admin", ("prepare_func succeeded")); + ; + } + } + + /* + CHECK TABLE command is only command where VIEW allowed here and this + command use only temporary teble method for VIEWs resolving => there + can't be VIEW tree substitition of join view => if opening table + succeed then table->table will have real TABLE pointer as value (in + case of join view substitution table->table can be 0, but here it is + impossible) + */ + if (!table->table) + { + DBUG_PRINT("admin", ("open table failed")); + if (thd->warning_info->is_empty()) + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_CHECK_NO_SUCH_TABLE, ER(ER_CHECK_NO_SUCH_TABLE)); + /* if it was a view will check md5 sum */ + if (table->view && + view_checksum(thd, table) == HA_ADMIN_WRONG_CHECKSUM) + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_VIEW_CHECKSUM, ER(ER_VIEW_CHECKSUM)); + if (thd->stmt_da->is_error() && + (thd->stmt_da->sql_errno() == ER_NO_SUCH_TABLE || + thd->stmt_da->sql_errno() == ER_FILE_NOT_FOUND)) + /* A missing table is just issued as a failed command */ + result_code= HA_ADMIN_FAILED; + else + /* Default failure code is corrupt table */ + result_code= HA_ADMIN_CORRUPT; + goto send_result; + } + + if (table->view) + { + DBUG_PRINT("admin", ("calling view_operator_func")); + result_code= (*view_operator_func)(thd, table); + goto send_result; + } + + if (table->schema_table) + { + result_code= HA_ADMIN_NOT_IMPLEMENTED; + goto send_result; + } + + if ((table->table->db_stat & HA_READ_ONLY) && open_for_modify) + { + /* purecov: begin inspected */ + char buff[FN_REFLEN + MYSQL_ERRMSG_SIZE]; + size_t length; + enum_sql_command save_sql_command= lex->sql_command; + DBUG_PRINT("admin", ("sending error message")); + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + length= my_snprintf(buff, sizeof(buff), ER(ER_OPEN_AS_READONLY), + table_name); + protocol->store(buff, length, system_charset_info); + trans_commit_stmt(thd); + trans_commit(thd); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + lex->reset_query_tables_list(FALSE); + /* + Restore Query_tables_list::sql_command value to make statement + safe for re-execution. + */ + lex->sql_command= save_sql_command; + table->table=0; // For query cache + if (protocol->write()) + goto err; + thd->stmt_da->reset_diagnostics_area(); + continue; + /* purecov: end */ + } + + /* + Close all instances of the table to allow MyISAM "repair" + to rename files. + @todo: This code does not close all instances of the table. + It only closes instances in other connections, but if this + connection has LOCK TABLE t1 a READ, t1 b WRITE, + both t1 instances will be kept open. + There is no need to execute this branch for InnoDB, which does + repair by recreate. There is no need to do it for OPTIMIZE, + which doesn't move files around. + Hence, this code should be moved to prepare_for_repair(), + and executed only for MyISAM engine. + */ + if (lock_type == TL_WRITE && !table->table->s->tmp_table) + { + if (wait_while_table_is_used(thd, table->table, + HA_EXTRA_PREPARE_FOR_RENAME)) + goto err; + DEBUG_SYNC(thd, "after_admin_flush"); + /* Flush entries in the query cache involving this table. */ + query_cache_invalidate3(thd, table->table, 0); + /* + XXX: hack: switch off open_for_modify to skip the + flush that is made later in the execution flow. + */ + open_for_modify= 0; + } + + if (table->table->s->crashed && operator_func == &handler::ha_check) + { + /* purecov: begin inspected */ + DBUG_PRINT("admin", ("sending crashed warning")); + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + protocol->store(STRING_WITH_LEN("warning"), system_charset_info); + protocol->store(STRING_WITH_LEN("Table is marked as crashed"), + system_charset_info); + if (protocol->write()) + goto err; + /* purecov: end */ + } + + if (operator_func == &handler::ha_repair && + !(check_opt->sql_flags & TT_USEFRM)) + { + if ((table->table->file->check_old_types() == HA_ADMIN_NEEDS_ALTER) || + (table->table->file->ha_check_for_upgrade(check_opt) == + HA_ADMIN_NEEDS_ALTER)) + { + DBUG_PRINT("admin", ("recreating table")); + trans_rollback_stmt(thd); + trans_rollback(thd); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + tmp_disable_binlog(thd); // binlogging is done by caller if wanted + result_code= mysql_recreate_table(thd, table); + reenable_binlog(thd); + /* + mysql_recreate_table() can push OK or ERROR. + Clear 'OK' status. If there is an error, keep it: + we will store the error message in a result set row + and then clear. + */ + if (thd->stmt_da->is_ok()) + thd->stmt_da->reset_diagnostics_area(); + table->table= NULL; + result_code= result_code ? HA_ADMIN_FAILED : HA_ADMIN_OK; + goto send_result; + } + } + + DBUG_PRINT("admin", ("calling operator_func '%s'", operator_name)); + result_code = (table->table->file->*operator_func)(thd, check_opt); + DBUG_PRINT("admin", ("operator_func returned: %d", result_code)); + +send_result: + + lex->cleanup_after_one_table_open(); + thd->clear_error(); // these errors shouldn't get client + { + List_iterator_fast it(thd->warning_info->warn_list()); + MYSQL_ERROR *err; + while ((err= it++)) + { + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store((char*) operator_name, system_charset_info); + protocol->store(warning_level_names[err->get_level()].str, + warning_level_names[err->get_level()].length, + system_charset_info); + protocol->store(err->get_message_text(), system_charset_info); + if (protocol->write()) + goto err; + } + thd->warning_info->clear_warning_info(thd->query_id); + } + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + +send_result_message: + + DBUG_PRINT("info", ("result_code: %d", result_code)); + switch (result_code) { + case HA_ADMIN_NOT_IMPLEMENTED: + { + char buf[MYSQL_ERRMSG_SIZE]; + size_t length=my_snprintf(buf, sizeof(buf), + ER(ER_CHECK_NOT_IMPLEMENTED), operator_name); + protocol->store(STRING_WITH_LEN("note"), system_charset_info); + protocol->store(buf, length, system_charset_info); + } + break; + + case HA_ADMIN_NOT_BASE_TABLE: + { + char buf[MYSQL_ERRMSG_SIZE]; + size_t length= my_snprintf(buf, sizeof(buf), + ER(ER_BAD_TABLE_ERROR), table_name); + protocol->store(STRING_WITH_LEN("note"), system_charset_info); + protocol->store(buf, length, system_charset_info); + } + break; + + case HA_ADMIN_OK: + protocol->store(STRING_WITH_LEN("status"), system_charset_info); + protocol->store(STRING_WITH_LEN("OK"), system_charset_info); + break; + + case HA_ADMIN_FAILED: + protocol->store(STRING_WITH_LEN("status"), system_charset_info); + protocol->store(STRING_WITH_LEN("Operation failed"), + system_charset_info); + break; + + case HA_ADMIN_REJECT: + protocol->store(STRING_WITH_LEN("status"), system_charset_info); + protocol->store(STRING_WITH_LEN("Operation need committed state"), + system_charset_info); + open_for_modify= FALSE; + break; + + case HA_ADMIN_ALREADY_DONE: + protocol->store(STRING_WITH_LEN("status"), system_charset_info); + protocol->store(STRING_WITH_LEN("Table is already up to date"), + system_charset_info); + break; + + case HA_ADMIN_CORRUPT: + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + protocol->store(STRING_WITH_LEN("Corrupt"), system_charset_info); + fatal_error=1; + break; + + case HA_ADMIN_INVALID: + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + protocol->store(STRING_WITH_LEN("Invalid argument"), + system_charset_info); + break; + + case HA_ADMIN_TRY_ALTER: + { + /* + This is currently used only by InnoDB. ha_innobase::optimize() answers + "try with alter", so here we close the table, do an ALTER TABLE, + reopen the table and do ha_innobase::analyze() on it. + We have to end the row, so analyze could return more rows. + */ + trans_commit_stmt(thd); + trans_commit(thd); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + DEBUG_SYNC(thd, "ha_admin_try_alter"); + protocol->store(STRING_WITH_LEN("note"), system_charset_info); + protocol->store(STRING_WITH_LEN( + "Table does not support optimize, doing recreate + analyze instead"), + system_charset_info); + if (protocol->write()) + goto err; + DBUG_PRINT("info", ("HA_ADMIN_TRY_ALTER, trying analyze...")); + TABLE_LIST *save_next_local= table->next_local, + *save_next_global= table->next_global; + table->next_local= table->next_global= 0; + tmp_disable_binlog(thd); // binlogging is done by caller if wanted + result_code= mysql_recreate_table(thd, table); + reenable_binlog(thd); + /* + mysql_recreate_table() can push OK or ERROR. + Clear 'OK' status. If there is an error, keep it: + we will store the error message in a result set row + and then clear. + */ + if (thd->stmt_da->is_ok()) + thd->stmt_da->reset_diagnostics_area(); + trans_commit_stmt(thd); + trans_commit(thd); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + table->table= NULL; + if (!result_code) // recreation went ok + { + /* Clear the ticket released above. */ + table->mdl_request.ticket= NULL; + DEBUG_SYNC(thd, "ha_admin_open_ltable"); + table->mdl_request.set_type(MDL_SHARED_WRITE); + if ((table->table= open_ltable(thd, table, lock_type, 0))) + { + result_code= table->table->file->ha_analyze(thd, check_opt); + if (result_code == HA_ADMIN_ALREADY_DONE) + result_code= HA_ADMIN_OK; + else if (result_code) // analyze failed + table->table->file->print_error(result_code, MYF(0)); + } + else + result_code= -1; // open failed + } + /* Start a new row for the final status row */ + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + if (result_code) // either mysql_recreate_table or analyze failed + { + DBUG_ASSERT(thd->is_error()); + if (thd->is_error()) + { + const char *err_msg= thd->stmt_da->message(); + if (!thd->vio_ok()) + { + sql_print_error("%s", err_msg); + } + else + { + /* Hijack the row already in-progress. */ + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + protocol->store(err_msg, system_charset_info); + if (protocol->write()) + goto err; + /* Start off another row for HA_ADMIN_FAILED */ + protocol->prepare_for_resend(); + protocol->store(table_name, system_charset_info); + protocol->store(operator_name, system_charset_info); + } + thd->clear_error(); + } + } + result_code= result_code ? HA_ADMIN_FAILED : HA_ADMIN_OK; + table->next_local= save_next_local; + table->next_global= save_next_global; + goto send_result_message; + } + case HA_ADMIN_WRONG_CHECKSUM: + { + protocol->store(STRING_WITH_LEN("note"), system_charset_info); + protocol->store(ER(ER_VIEW_CHECKSUM), strlen(ER(ER_VIEW_CHECKSUM)), + system_charset_info); + break; + } + + case HA_ADMIN_NEEDS_UPGRADE: + case HA_ADMIN_NEEDS_ALTER: + { + char buf[MYSQL_ERRMSG_SIZE]; + size_t length; + + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + length=my_snprintf(buf, sizeof(buf), ER(ER_TABLE_NEEDS_UPGRADE), + table->table_name); + protocol->store(buf, length, system_charset_info); + fatal_error=1; + break; + } + + default: // Probably HA_ADMIN_INTERNAL_ERROR + { + char buf[MYSQL_ERRMSG_SIZE]; + size_t length=my_snprintf(buf, sizeof(buf), + "Unknown - internal error %d during operation", + result_code); + protocol->store(STRING_WITH_LEN("error"), system_charset_info); + protocol->store(buf, length, system_charset_info); + fatal_error=1; + break; + } + } + if (table->table) + { + if (table->table->s->tmp_table) + { + /* + If the table was not opened successfully, do not try to get + status information. (Bug#47633) + */ + if (open_for_modify && !open_error) + table->table->file->info(HA_STATUS_CONST); + } + else if (open_for_modify || fatal_error) + { + mysql_mutex_lock(&LOCK_open); + tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, + table->db, table->table_name); + mysql_mutex_unlock(&LOCK_open); + /* + May be something modified. Consequently, we have to + invalidate the query cache. + */ + table->table= 0; // For query cache + query_cache_invalidate3(thd, table, 0); + } + } + /* Error path, a admin command failed. */ + trans_commit_stmt(thd); + trans_commit_implicit(thd); + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + + /* + If it is CHECK TABLE v1, v2, v3, and v1, v2, v3 are views, we will run + separate open_tables() for each CHECK TABLE argument. + Right now we do not have a separate method to reset the prelocking + state in the lex to the state after parsing, so each open will pollute + this state: add elements to lex->srotuines_list, TABLE_LISTs to + lex->query_tables. Below is a lame attempt to recover from this + pollution. + @todo: have a method to reset a prelocking context, or use separate + contexts for each open. + */ + for (Sroutine_hash_entry *rt= + (Sroutine_hash_entry*)thd->lex->sroutines_list.first; + rt; rt= rt->next) + rt->mdl_request.ticket= NULL; + + if (protocol->write()) + goto err; + } + + my_eof(thd); + DBUG_RETURN(FALSE); + +err: + trans_rollback_stmt(thd); + trans_rollback(thd); + close_thread_tables(thd); // Shouldn't be needed + thd->mdl_context.release_transactional_locks(); + if (table) + table->table=0; + DBUG_RETURN(TRUE); +} + + +/* + Assigned specified indexes for a table into key cache + + SYNOPSIS + mysql_assign_to_keycache() + thd Thread object + tables Table list (one table only) + + RETURN VALUES + FALSE ok + TRUE error +*/ + +bool mysql_assign_to_keycache(THD* thd, TABLE_LIST* tables, + LEX_STRING *key_cache_name) +{ + HA_CHECK_OPT check_opt; + KEY_CACHE *key_cache; + DBUG_ENTER("mysql_assign_to_keycache"); + + check_opt.init(); + mysql_mutex_lock(&LOCK_global_system_variables); + if (!(key_cache= get_key_cache(key_cache_name))) + { + mysql_mutex_unlock(&LOCK_global_system_variables); + my_error(ER_UNKNOWN_KEY_CACHE, MYF(0), key_cache_name->str); + DBUG_RETURN(TRUE); + } + mysql_mutex_unlock(&LOCK_global_system_variables); + check_opt.key_cache= key_cache; + DBUG_RETURN(mysql_admin_table(thd, tables, &check_opt, + "assign_to_keycache", TL_READ_NO_INSERT, 0, 0, + 0, 0, &handler::assign_to_keycache, 0)); +} + + +/* + Preload specified indexes for a table into key cache + + SYNOPSIS + mysql_preload_keys() + thd Thread object + tables Table list (one table only) + + RETURN VALUES + FALSE ok + TRUE error +*/ + +bool mysql_preload_keys(THD* thd, TABLE_LIST* tables) +{ + DBUG_ENTER("mysql_preload_keys"); + /* + We cannot allow concurrent inserts. The storage engine reads + directly from the index file, bypassing the cache. It could read + outdated information if parallel inserts into cache blocks happen. + */ + DBUG_RETURN(mysql_admin_table(thd, tables, 0, + "preload_keys", TL_READ_NO_INSERT, 0, 0, 0, 0, + &handler::preload_keys, 0)); +} + + +bool Analyze_table_statement::execute(THD *thd) +{ + TABLE_LIST *first_table= m_lex->select_lex.table_list.first; + bool res= TRUE; + thr_lock_type lock_type = TL_READ_NO_INSERT; + DBUG_ENTER("Analyze_table_statement::execute"); + + if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, + FALSE, UINT_MAX, FALSE)) + goto error; + thd->enable_slow_log= opt_log_slow_admin_statements; + res= mysql_admin_table(thd, first_table, &m_lex->check_opt, + "analyze", lock_type, 1, 0, 0, 0, + &handler::ha_analyze, 0); + /* ! we write after unlocking the table */ + if (!res && !m_lex->no_write_to_binlog) + { + /* + Presumably, ANALYZE and binlog writing doesn't require synchronization + */ + res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + } + m_lex->select_lex.table_list.first= first_table; + m_lex->query_tables= first_table; + +error: + DBUG_RETURN(res); +} + + +bool Check_table_statement::execute(THD *thd) +{ + TABLE_LIST *first_table= m_lex->select_lex.table_list.first; + thr_lock_type lock_type = TL_READ_NO_INSERT; + bool res= TRUE; + DBUG_ENTER("Check_table_statement::execute"); + + if (check_table_access(thd, SELECT_ACL, first_table, + TRUE, UINT_MAX, FALSE)) + goto error; /* purecov: inspected */ + thd->enable_slow_log= opt_log_slow_admin_statements; + + res= mysql_admin_table(thd, first_table, &m_lex->check_opt, "check", + lock_type, 0, 0, HA_OPEN_FOR_REPAIR, 0, + &handler::ha_check, &view_checksum); + + m_lex->select_lex.table_list.first= first_table; + m_lex->query_tables= first_table; + +error: + DBUG_RETURN(res); +} + + +bool Optimize_table_statement::execute(THD *thd) +{ + TABLE_LIST *first_table= m_lex->select_lex.table_list.first; + bool res= TRUE; + DBUG_ENTER("Optimize_table_statement::execute"); + + if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, + FALSE, UINT_MAX, FALSE)) + goto error; /* purecov: inspected */ + thd->enable_slow_log= opt_log_slow_admin_statements; + res= (specialflag & (SPECIAL_SAFE_MODE | SPECIAL_NO_NEW_FUNC)) ? + mysql_recreate_table(thd, first_table) : + mysql_admin_table(thd, first_table, &m_lex->check_opt, + "optimize", TL_WRITE, 1, 0, 0, 0, + &handler::ha_optimize, 0); + /* ! we write after unlocking the table */ + if (!res && !m_lex->no_write_to_binlog) + { + /* + Presumably, OPTIMIZE and binlog writing doesn't require synchronization + */ + res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + } + m_lex->select_lex.table_list.first= first_table; + m_lex->query_tables= first_table; + +error: + DBUG_RETURN(res); +} + + +bool Repair_table_statement::execute(THD *thd) +{ + TABLE_LIST *first_table= m_lex->select_lex.table_list.first; + bool res= TRUE; + DBUG_ENTER("Repair_table_statement::execute"); + + if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, + FALSE, UINT_MAX, FALSE)) + goto error; /* purecov: inspected */ + thd->enable_slow_log= opt_log_slow_admin_statements; + res= mysql_admin_table(thd, first_table, &m_lex->check_opt, "repair", + TL_WRITE, 1, + test(m_lex->check_opt.sql_flags & TT_USEFRM), + HA_OPEN_FOR_REPAIR, &prepare_for_repair, + &handler::ha_repair, 0); + + /* ! we write after unlocking the table */ + if (!res && !m_lex->no_write_to_binlog) + { + /* + Presumably, REPAIR and binlog writing doesn't require synchronization + */ + res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + } + m_lex->select_lex.table_list.first= first_table; + m_lex->query_tables= first_table; + +error: + DBUG_RETURN(res); +} diff --git a/sql/sql_admin.h b/sql/sql_admin.h new file mode 100644 index 00000000000..fdfffec8361 --- /dev/null +++ b/sql/sql_admin.h @@ -0,0 +1,132 @@ +/* Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifndef SQL_TABLE_MAINTENANCE_H +#define SQL_TABLE_MAINTENANCE_H + + +bool mysql_assign_to_keycache(THD* thd, TABLE_LIST* table_list, + LEX_STRING *key_cache_name); +bool mysql_preload_keys(THD* thd, TABLE_LIST* table_list); +int reassign_keycache_tables(THD* thd, KEY_CACHE *src_cache, + KEY_CACHE *dst_cache); + +/** + Analyze_statement represents the ANALYZE TABLE statement. +*/ +class Analyze_table_statement : public Sql_statement +{ +public: + /** + Constructor, used to represent a ANALYZE TABLE statement. + @param lex the LEX structure for this statement. + */ + Analyze_table_statement(LEX *lex) + : Sql_statement(lex) + {} + + ~Analyze_table_statement() + {} + + /** + Execute a ANALYZE TABLE statement at runtime. + @param thd the current thread. + @return false on success. + */ + bool execute(THD *thd); +}; + + + +/** + Check_table_statement represents the CHECK TABLE statement. +*/ +class Check_table_statement : public Sql_statement +{ +public: + /** + Constructor, used to represent a CHECK TABLE statement. + @param lex the LEX structure for this statement. + */ + Check_table_statement(LEX *lex) + : Sql_statement(lex) + {} + + ~Check_table_statement() + {} + + /** + Execute a CHECK TABLE statement at runtime. + @param thd the current thread. + @return false on success. + */ + bool execute(THD *thd); +}; + + + +/** + Optimize_table_statement represents the OPTIMIZE TABLE statement. +*/ +class Optimize_table_statement : public Sql_statement +{ +public: + /** + Constructor, used to represent a OPTIMIZE TABLE statement. + @param lex the LEX structure for this statement. + */ + Optimize_table_statement(LEX *lex) + : Sql_statement(lex) + {} + + ~Optimize_table_statement() + {} + + /** + Execute a OPTIMIZE TABLE statement at runtime. + @param thd the current thread. + @return false on success. + */ + bool execute(THD *thd); +}; + + + +/** + Repair_table_statement represents the REPAIR TABLE statement. +*/ +class Repair_table_statement : public Sql_statement +{ +public: + /** + Constructor, used to represent a REPAIR TABLE statement. + @param lex the LEX structure for this statement. + */ + Repair_table_statement(LEX *lex) + : Sql_statement(lex) + {} + + ~Repair_table_statement() + {} + + /** + Execute a REPAIR TABLE statement at runtime. + @param thd the current thread. + @return false on success. + */ + bool execute(THD *thd); +}; + +#endif diff --git a/sql/sql_alter.cc b/sql/sql_alter.cc new file mode 100644 index 00000000000..046e09b20a3 --- /dev/null +++ b/sql/sql_alter.cc @@ -0,0 +1,106 @@ +/* Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "sql_parse.h" // check_access, + // check_merge_table_access +#include "sql_table.h" // mysql_alter_table, + // mysql_exchange_partition +#include "sql_alter.h" + +bool Alter_table_statement::execute(THD *thd) +{ + LEX *lex= thd->lex; + /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ + SELECT_LEX *select_lex= &lex->select_lex; + /* first table of first SELECT_LEX */ + TABLE_LIST *first_table= (TABLE_LIST*) select_lex->table_list.first; + /* + Code in mysql_alter_table() may modify its HA_CREATE_INFO argument, + so we have to use a copy of this structure to make execution + prepared statement- safe. A shallow copy is enough as no memory + referenced from this structure will be modified. + @todo move these into constructor... + */ + HA_CREATE_INFO create_info(lex->create_info); + Alter_info alter_info(lex->alter_info, thd->mem_root); + ulong priv=0; + ulong priv_needed= ALTER_ACL; + bool result; + + DBUG_ENTER("Alter_table_statement::execute"); + + if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */ + DBUG_RETURN(TRUE); + /* + We also require DROP priv for ALTER TABLE ... DROP PARTITION, as well + as for RENAME TO, as being done by SQLCOM_RENAME_TABLE + */ + if (alter_info.flags & (ALTER_DROP_PARTITION | ALTER_RENAME)) + priv_needed|= DROP_ACL; + + /* Must be set in the parser */ + DBUG_ASSERT(select_lex->db); + DBUG_ASSERT(!(alter_info.flags & ALTER_ADMIN_PARTITION)); + if (check_access(thd, priv_needed, first_table->db, + &first_table->grant.privilege, + &first_table->grant.m_internal, + 0, 0) || + check_access(thd, INSERT_ACL | CREATE_ACL, select_lex->db, + &priv, + NULL, /* Don't use first_tab->grant with sel_lex->db */ + 0, 0) || + check_merge_table_access(thd, first_table->db, + create_info.merge_list.first)) + DBUG_RETURN(TRUE); /* purecov: inspected */ + + if (check_grant(thd, priv_needed, first_table, FALSE, UINT_MAX, FALSE)) + DBUG_RETURN(TRUE); /* purecov: inspected */ + + if (lex->name.str && !test_all_bits(priv, INSERT_ACL | CREATE_ACL)) + { + // Rename of table + TABLE_LIST tmp_table; + bzero((char*) &tmp_table,sizeof(tmp_table)); + tmp_table.table_name= lex->name.str; + tmp_table.db= select_lex->db; + tmp_table.grant.privilege= priv; + if (check_grant(thd, INSERT_ACL | CREATE_ACL, &tmp_table, FALSE, + UINT_MAX, FALSE)) + DBUG_RETURN(TRUE); /* purecov: inspected */ + } + + /* Don't yet allow changing of symlinks with ALTER TABLE */ + if (create_info.data_file_name) + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), + "DATA DIRECTORY"); + if (create_info.index_file_name) + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), + "INDEX DIRECTORY"); + create_info.data_file_name= create_info.index_file_name= NULL; + + thd->enable_slow_log= opt_log_slow_admin_statements; + + result= mysql_alter_table(thd, select_lex->db, lex->name.str, + &create_info, + first_table, + &alter_info, + select_lex->order_list.elements, + select_lex->order_list.first, + lex->ignore); + + DBUG_RETURN(result); +} diff --git a/sql/sql_alter.h b/sql/sql_alter.h new file mode 100644 index 00000000000..6a17f87f5a4 --- /dev/null +++ b/sql/sql_alter.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifndef SQL_ALTER_TABLE_H +#define SQL_ALTER_TABLE_H + +/** + Alter_table_common represents the common properties of the ALTER TABLE + statements. + @todo move Alter_info and other ALTER generic structures from Lex here. +*/ +class Alter_table_common : public Sql_statement +{ +protected: + /** + Constructor. + @param lex the LEX structure for this statement. + */ + Alter_table_common(LEX *lex) + : Sql_statement(lex) + {} + + virtual ~Alter_table_common() + {} + +}; + +/** + Alter_table_statement represents the generic ALTER TABLE statement. + @todo move Alter_info and other ALTER specific structures from Lex here. +*/ +class Alter_table_statement : public Alter_table_common +{ +public: + /** + Constructor, used to represent a ALTER TABLE statement. + @param lex the LEX structure for this statement. + */ + Alter_table_statement(LEX *lex) + : Alter_table_common(lex) + {} + + ~Alter_table_statement() + {} + + /** + Execute a ALTER TABLE statement at runtime. + @param thd the current thread. + @return false on success. + */ + bool execute(THD *thd); +}; + +#endif diff --git a/sql/sql_alter_table.cc b/sql/sql_alter_table.cc deleted file mode 100644 index 607a8d5f5df..00000000000 --- a/sql/sql_alter_table.cc +++ /dev/null @@ -1,106 +0,0 @@ -/* Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - -#include "sql_parse.h" // check_access, - // check_merge_table_access -#include "sql_table.h" // mysql_alter_table, - // mysql_exchange_partition -#include "sql_alter_table.h" - -bool Alter_table_statement::execute(THD *thd) -{ - LEX *lex= thd->lex; - /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ - SELECT_LEX *select_lex= &lex->select_lex; - /* first table of first SELECT_LEX */ - TABLE_LIST *first_table= (TABLE_LIST*) select_lex->table_list.first; - /* - Code in mysql_alter_table() may modify its HA_CREATE_INFO argument, - so we have to use a copy of this structure to make execution - prepared statement- safe. A shallow copy is enough as no memory - referenced from this structure will be modified. - @todo move these into constructor... - */ - HA_CREATE_INFO create_info(lex->create_info); - Alter_info alter_info(lex->alter_info, thd->mem_root); - ulong priv=0; - ulong priv_needed= ALTER_ACL; - bool result; - - DBUG_ENTER("Alter_table_statement::execute"); - - if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */ - DBUG_RETURN(TRUE); - /* - We also require DROP priv for ALTER TABLE ... DROP PARTITION, as well - as for RENAME TO, as being done by SQLCOM_RENAME_TABLE - */ - if (alter_info.flags & (ALTER_DROP_PARTITION | ALTER_RENAME)) - priv_needed|= DROP_ACL; - - /* Must be set in the parser */ - DBUG_ASSERT(select_lex->db); - DBUG_ASSERT(!(alter_info.flags & ALTER_ADMIN_PARTITION)); - if (check_access(thd, priv_needed, first_table->db, - &first_table->grant.privilege, - &first_table->grant.m_internal, - 0, 0) || - check_access(thd, INSERT_ACL | CREATE_ACL, select_lex->db, - &priv, - NULL, /* Don't use first_tab->grant with sel_lex->db */ - 0, 0) || - check_merge_table_access(thd, first_table->db, - create_info.merge_list.first)) - DBUG_RETURN(TRUE); /* purecov: inspected */ - - if (check_grant(thd, priv_needed, first_table, FALSE, UINT_MAX, FALSE)) - DBUG_RETURN(TRUE); /* purecov: inspected */ - - if (lex->name.str && !test_all_bits(priv, INSERT_ACL | CREATE_ACL)) - { - // Rename of table - TABLE_LIST tmp_table; - bzero((char*) &tmp_table,sizeof(tmp_table)); - tmp_table.table_name= lex->name.str; - tmp_table.db= select_lex->db; - tmp_table.grant.privilege= priv; - if (check_grant(thd, INSERT_ACL | CREATE_ACL, &tmp_table, FALSE, - UINT_MAX, FALSE)) - DBUG_RETURN(TRUE); /* purecov: inspected */ - } - - /* Don't yet allow changing of symlinks with ALTER TABLE */ - if (create_info.data_file_name) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), - "DATA DIRECTORY"); - if (create_info.index_file_name) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), - "INDEX DIRECTORY"); - create_info.data_file_name= create_info.index_file_name= NULL; - - thd->enable_slow_log= opt_log_slow_admin_statements; - - result= mysql_alter_table(thd, select_lex->db, lex->name.str, - &create_info, - first_table, - &alter_info, - select_lex->order_list.elements, - select_lex->order_list.first, - lex->ignore); - - DBUG_RETURN(result); -} diff --git a/sql/sql_alter_table.h b/sql/sql_alter_table.h deleted file mode 100644 index 6a17f87f5a4..00000000000 --- a/sql/sql_alter_table.h +++ /dev/null @@ -1,66 +0,0 @@ -/* Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - -#ifndef SQL_ALTER_TABLE_H -#define SQL_ALTER_TABLE_H - -/** - Alter_table_common represents the common properties of the ALTER TABLE - statements. - @todo move Alter_info and other ALTER generic structures from Lex here. -*/ -class Alter_table_common : public Sql_statement -{ -protected: - /** - Constructor. - @param lex the LEX structure for this statement. - */ - Alter_table_common(LEX *lex) - : Sql_statement(lex) - {} - - virtual ~Alter_table_common() - {} - -}; - -/** - Alter_table_statement represents the generic ALTER TABLE statement. - @todo move Alter_info and other ALTER specific structures from Lex here. -*/ -class Alter_table_statement : public Alter_table_common -{ -public: - /** - Constructor, used to represent a ALTER TABLE statement. - @param lex the LEX structure for this statement. - */ - Alter_table_statement(LEX *lex) - : Alter_table_common(lex) - {} - - ~Alter_table_statement() - {} - - /** - Execute a ALTER TABLE statement at runtime. - @param thd the current thread. - @return false on success. - */ - bool execute(THD *thd); -}; - -#endif diff --git a/sql/sql_lex.h b/sql/sql_lex.h index d0f20d4e997..0eb8e5de6d2 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -923,7 +923,7 @@ enum enum_alter_table_change_level /** Temporary hack to enable a class bound forward declaration of the enum_alter_table_change_level enumeration. To be - removed once Alter_info is moved to the sql_alter_table.h + removed once Alter_info is moved to the sql_alter.h header. */ class Alter_table_change_level diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2f02188d122..806a2984905 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -50,7 +50,7 @@ // mysql_backup_table, // mysql_restore_table #include "sql_truncate.h" // mysql_truncate_table -#include "sql_table_maintenance.h" // mysql_assign_to_keycache +#include "sql_admin.h" // mysql_assign_to_keycache #include "sql_connect.h" // check_user, // decrease_user_connections, // thd_init_client_charset, check_mqh, diff --git a/sql/sql_partition_admin.cc b/sql/sql_partition_admin.cc index fc0183e9b3d..fee33303a04 100644 --- a/sql/sql_partition_admin.cc +++ b/sql/sql_partition_admin.cc @@ -18,7 +18,7 @@ #include "sql_lex.h" // Sql_statement #include "sql_truncate.h" // mysql_truncate_table, // Truncate_statement -#include "sql_table_maintenance.h" // Analyze/Check/.._table_statement +#include "sql_admin.h" // Analyze/Check/.._table_statement #include "sql_partition_admin.h" // Alter_table_*_partition #ifndef WITH_PARTITION_STORAGE_ENGINE diff --git a/sql/sql_table_maintenance.cc b/sql/sql_table_maintenance.cc deleted file mode 100644 index b7033208788..00000000000 --- a/sql/sql_table_maintenance.cc +++ /dev/null @@ -1,1004 +0,0 @@ -/* Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - -#include "sql_class.h" // THD -#include "keycaches.h" // get_key_cache -#include "sql_base.h" // Open_table_context -#include "lock.h" // MYSQL_OPEN_* -#include "sql_handler.h" // mysql_ha_rm_tables -#include "partition_element.h" // PART_ADMIN -#include "sql_partition.h" // set_part_state -#include "transaction.h" // trans_rollback_stmt -#include "sql_view.h" // view_checksum -#include "sql_table.h" // mysql_recreate_table -#include "debug_sync.h" // DEBUG_SYNC -#include "sql_acl.h" // *_ACL -#include "sp.h" // Sroutine_hash_entry -#include "sql_parse.h" // check_table_access -#include "sql_table_maintenance.h" - -static int send_check_errmsg(THD *thd, TABLE_LIST* table, - const char* operator_name, const char* errmsg) - -{ - Protocol *protocol= thd->protocol; - protocol->prepare_for_resend(); - protocol->store(table->alias, system_charset_info); - protocol->store((char*) operator_name, system_charset_info); - protocol->store(STRING_WITH_LEN("error"), system_charset_info); - protocol->store(errmsg, system_charset_info); - thd->clear_error(); - if (protocol->write()) - return -1; - return 1; -} - - -static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, - HA_CHECK_OPT *check_opt) -{ - int error= 0; - TABLE tmp_table, *table; - TABLE_SHARE *share; - bool has_mdl_lock= FALSE; - char from[FN_REFLEN],tmp[FN_REFLEN+32]; - const char **ext; - MY_STAT stat_info; - Open_table_context ot_ctx(thd, (MYSQL_OPEN_IGNORE_FLUSH | - MYSQL_OPEN_HAS_MDL_LOCK | - MYSQL_LOCK_IGNORE_TIMEOUT)); - DBUG_ENTER("prepare_for_repair"); - - if (!(check_opt->sql_flags & TT_USEFRM)) - DBUG_RETURN(0); - - if (!(table= table_list->table)) - { - char key[MAX_DBKEY_LENGTH]; - uint key_length; - /* - If the table didn't exist, we have a shared metadata lock - on it that is left from mysql_admin_table()'s attempt to - open it. Release the shared metadata lock before trying to - acquire the exclusive lock to satisfy MDL asserts and avoid - deadlocks. - */ - thd->mdl_context.release_transactional_locks(); - /* - Attempt to do full-blown table open in mysql_admin_table() has failed. - Let us try to open at least a .FRM for this table. - */ - my_hash_value_type hash_value; - - key_length= create_table_def_key(thd, key, table_list, 0); - table_list->mdl_request.init(MDL_key::TABLE, - table_list->db, table_list->table_name, - MDL_EXCLUSIVE); - - if (lock_table_names(thd, table_list, table_list->next_global, - thd->variables.lock_wait_timeout, - MYSQL_OPEN_SKIP_TEMPORARY)) - DBUG_RETURN(0); - has_mdl_lock= TRUE; - - hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length); - mysql_mutex_lock(&LOCK_open); - if (!(share= (get_table_share(thd, table_list, key, key_length, 0, - &error, hash_value)))) - { - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(0); // Can't open frm file - } - - if (open_table_from_share(thd, share, "", 0, 0, 0, &tmp_table, FALSE)) - { - release_table_share(share); - mysql_mutex_unlock(&LOCK_open); - DBUG_RETURN(0); // Out of memory - } - mysql_mutex_unlock(&LOCK_open); - table= &tmp_table; - } - - /* A MERGE table must not come here. */ - DBUG_ASSERT(table->file->ht->db_type != DB_TYPE_MRG_MYISAM); - - /* - REPAIR TABLE ... USE_FRM for temporary tables makes little sense. - */ - if (table->s->tmp_table) - { - error= send_check_errmsg(thd, table_list, "repair", - "Cannot repair temporary table from .frm file"); - goto end; - } - - /* - User gave us USE_FRM which means that the header in the index file is - trashed. - In this case we will try to fix the table the following way: - - Rename the data file to a temporary name - - Truncate the table - - Replace the new data file with the old one - - Run a normal repair using the new index file and the old data file - */ - - if (table->s->frm_version != FRM_VER_TRUE_VARCHAR) - { - error= send_check_errmsg(thd, table_list, "repair", - "Failed repairing incompatible .frm file"); - goto end; - } - - /* - Check if this is a table type that stores index and data separately, - like ISAM or MyISAM. We assume fixed order of engine file name - extentions array. First element of engine file name extentions array - is meta/index file extention. Second element - data file extention. - */ - ext= table->file->bas_ext(); - if (!ext[0] || !ext[1]) - goto end; // No data file - - // Name of data file - strxmov(from, table->s->normalized_path.str, ext[1], NullS); - if (!mysql_file_stat(key_file_misc, from, &stat_info, MYF(0))) - goto end; // Can't use USE_FRM flag - - my_snprintf(tmp, sizeof(tmp), "%s-%lx_%lx", - from, current_pid, thd->thread_id); - - if (table_list->table) - { - /* - Table was successfully open in mysql_admin_table(). Now we need - to close it, but leave it protected by exclusive metadata lock. - */ - if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) - goto end; - close_all_tables_for_name(thd, table_list->table->s, FALSE); - table_list->table= 0; - } - /* - After this point we have an exclusive metadata lock on our table - in both cases when table was successfully open in mysql_admin_table() - and when it was open in prepare_for_repair(). - */ - - if (my_rename(from, tmp, MYF(MY_WME))) - { - error= send_check_errmsg(thd, table_list, "repair", - "Failed renaming data file"); - goto end; - } - if (dd_recreate_table(thd, table_list->db, table_list->table_name)) - { - error= send_check_errmsg(thd, table_list, "repair", - "Failed generating table from .frm file"); - goto end; - } - /* - 'FALSE' for 'using_transactions' means don't postpone - invalidation till the end of a transaction, but do it - immediately. - */ - query_cache_invalidate3(thd, table_list, FALSE); - if (mysql_file_rename(key_file_misc, tmp, from, MYF(MY_WME))) - { - error= send_check_errmsg(thd, table_list, "repair", - "Failed restoring .MYD file"); - goto end; - } - - if (thd->locked_tables_list.reopen_tables(thd)) - goto end; - - /* - Now we should be able to open the partially repaired table - to finish the repair in the handler later on. - */ - if (open_table(thd, table_list, thd->mem_root, &ot_ctx)) - { - error= send_check_errmsg(thd, table_list, "repair", - "Failed to open partially repaired table"); - goto end; - } - -end: - thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); - if (table == &tmp_table) - { - mysql_mutex_lock(&LOCK_open); - closefrm(table, 1); // Free allocated memory - mysql_mutex_unlock(&LOCK_open); - } - /* In case of a temporary table there will be no metadata lock. */ - if (error && has_mdl_lock) - thd->mdl_context.release_transactional_locks(); - - DBUG_RETURN(error); -} - - - -/* - RETURN VALUES - FALSE Message sent to net (admin operation went ok) - TRUE Message should be sent by caller - (admin operation or network communication failed) -*/ -static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, - HA_CHECK_OPT* check_opt, - const char *operator_name, - thr_lock_type lock_type, - bool open_for_modify, - bool no_warnings_for_error, - uint extra_open_options, - int (*prepare_func)(THD *, TABLE_LIST *, - HA_CHECK_OPT *), - int (handler::*operator_func)(THD *, - HA_CHECK_OPT *), - int (view_operator_func)(THD *, TABLE_LIST*)) -{ - TABLE_LIST *table; - SELECT_LEX *select= &thd->lex->select_lex; - List field_list; - Item *item; - Protocol *protocol= thd->protocol; - LEX *lex= thd->lex; - int result_code; - DBUG_ENTER("mysql_admin_table"); - - field_list.push_back(item = new Item_empty_string("Table", NAME_CHAR_LEN*2)); - item->maybe_null = 1; - field_list.push_back(item = new Item_empty_string("Op", 10)); - item->maybe_null = 1; - field_list.push_back(item = new Item_empty_string("Msg_type", 10)); - item->maybe_null = 1; - field_list.push_back(item = new Item_empty_string("Msg_text", 255)); - item->maybe_null = 1; - if (protocol->send_result_set_metadata(&field_list, - Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) - DBUG_RETURN(TRUE); - - mysql_ha_rm_tables(thd, tables); - - for (table= tables; table; table= table->next_local) - { - char table_name[NAME_LEN*2+2]; - char* db = table->db; - bool fatal_error=0; - bool open_error; - - DBUG_PRINT("admin", ("table: '%s'.'%s'", table->db, table->table_name)); - DBUG_PRINT("admin", ("extra_open_options: %u", extra_open_options)); - strxmov(table_name, db, ".", table->table_name, NullS); - thd->open_options|= extra_open_options; - table->lock_type= lock_type; - /* - To make code safe for re-execution we need to reset type of MDL - request as code below may change it. - To allow concurrent execution of read-only operations we acquire - weak metadata lock for them. - */ - table->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? - MDL_SHARED_NO_READ_WRITE : MDL_SHARED_READ); - /* open only one table from local list of command */ - { - TABLE_LIST *save_next_global, *save_next_local; - save_next_global= table->next_global; - table->next_global= 0; - save_next_local= table->next_local; - table->next_local= 0; - select->table_list.first= table; - /* - Time zone tables and SP tables can be add to lex->query_tables list, - so it have to be prepared. - TODO: Investigate if we can put extra tables into argument instead of - using lex->query_tables - */ - lex->query_tables= table; - lex->query_tables_last= &table->next_global; - lex->query_tables_own_last= 0; - thd->no_warnings_for_error= no_warnings_for_error; - if (view_operator_func == NULL) - table->required_type=FRMTYPE_TABLE; - - open_error= open_and_lock_tables(thd, table, TRUE, 0); - thd->no_warnings_for_error= 0; - table->next_global= save_next_global; - table->next_local= save_next_local; - thd->open_options&= ~extra_open_options; - /* - Under locked tables, we know that the table can be opened, - so any errors opening the table are logical errors. - In these cases it does not make sense to try to repair. - */ - if (open_error && thd->locked_tables_mode) - { - result_code= HA_ADMIN_FAILED; - goto send_result; - } -#ifdef WITH_PARTITION_STORAGE_ENGINE - if (table->table) - { - /* - Set up which partitions that should be processed - if ALTER TABLE t ANALYZE/CHECK/OPTIMIZE/REPAIR PARTITION .. - CACHE INDEX/LOAD INDEX for specified partitions - */ - Alter_info *alter_info= &lex->alter_info; - - if (alter_info->flags & ALTER_ADMIN_PARTITION) - { - if (!table->table->part_info) - { - my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0)); - DBUG_RETURN(TRUE); - } - uint num_parts_found; - uint num_parts_opt= alter_info->partition_names.elements; - num_parts_found= set_part_state(alter_info, table->table->part_info, - PART_ADMIN); - if (num_parts_found != num_parts_opt && - (!(alter_info->flags & ALTER_ALL_PARTITION))) - { - char buff[FN_REFLEN + MYSQL_ERRMSG_SIZE]; - size_t length; - DBUG_PRINT("admin", ("sending non existent partition error")); - protocol->prepare_for_resend(); - protocol->store(table_name, system_charset_info); - protocol->store(operator_name, system_charset_info); - protocol->store(STRING_WITH_LEN("error"), system_charset_info); - length= my_snprintf(buff, sizeof(buff), - ER(ER_DROP_PARTITION_NON_EXISTENT), - table_name); - protocol->store(buff, length, system_charset_info); - if(protocol->write()) - goto err; - my_eof(thd); - goto err; - } - } - } -#endif - } - DBUG_PRINT("admin", ("table: 0x%lx", (long) table->table)); - - if (prepare_func) - { - DBUG_PRINT("admin", ("calling prepare_func")); - switch ((*prepare_func)(thd, table, check_opt)) { - case 1: // error, message written to net - trans_rollback_stmt(thd); - trans_rollback(thd); - close_thread_tables(thd); - thd->mdl_context.release_transactional_locks(); - DBUG_PRINT("admin", ("simple error, admin next table")); - continue; - case -1: // error, message could be written to net - /* purecov: begin inspected */ - DBUG_PRINT("admin", ("severe error, stop")); - goto err; - /* purecov: end */ - default: // should be 0 otherwise - DBUG_PRINT("admin", ("prepare_func succeeded")); - ; - } - } - - /* - CHECK TABLE command is only command where VIEW allowed here and this - command use only temporary teble method for VIEWs resolving => there - can't be VIEW tree substitition of join view => if opening table - succeed then table->table will have real TABLE pointer as value (in - case of join view substitution table->table can be 0, but here it is - impossible) - */ - if (!table->table) - { - DBUG_PRINT("admin", ("open table failed")); - if (thd->warning_info->is_empty()) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_CHECK_NO_SUCH_TABLE, ER(ER_CHECK_NO_SUCH_TABLE)); - /* if it was a view will check md5 sum */ - if (table->view && - view_checksum(thd, table) == HA_ADMIN_WRONG_CHECKSUM) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_VIEW_CHECKSUM, ER(ER_VIEW_CHECKSUM)); - if (thd->stmt_da->is_error() && - (thd->stmt_da->sql_errno() == ER_NO_SUCH_TABLE || - thd->stmt_da->sql_errno() == ER_FILE_NOT_FOUND)) - /* A missing table is just issued as a failed command */ - result_code= HA_ADMIN_FAILED; - else - /* Default failure code is corrupt table */ - result_code= HA_ADMIN_CORRUPT; - goto send_result; - } - - if (table->view) - { - DBUG_PRINT("admin", ("calling view_operator_func")); - result_code= (*view_operator_func)(thd, table); - goto send_result; - } - - if (table->schema_table) - { - result_code= HA_ADMIN_NOT_IMPLEMENTED; - goto send_result; - } - - if ((table->table->db_stat & HA_READ_ONLY) && open_for_modify) - { - /* purecov: begin inspected */ - char buff[FN_REFLEN + MYSQL_ERRMSG_SIZE]; - size_t length; - enum_sql_command save_sql_command= lex->sql_command; - DBUG_PRINT("admin", ("sending error message")); - protocol->prepare_for_resend(); - protocol->store(table_name, system_charset_info); - protocol->store(operator_name, system_charset_info); - protocol->store(STRING_WITH_LEN("error"), system_charset_info); - length= my_snprintf(buff, sizeof(buff), ER(ER_OPEN_AS_READONLY), - table_name); - protocol->store(buff, length, system_charset_info); - trans_commit_stmt(thd); - trans_commit(thd); - close_thread_tables(thd); - thd->mdl_context.release_transactional_locks(); - lex->reset_query_tables_list(FALSE); - /* - Restore Query_tables_list::sql_command value to make statement - safe for re-execution. - */ - lex->sql_command= save_sql_command; - table->table=0; // For query cache - if (protocol->write()) - goto err; - thd->stmt_da->reset_diagnostics_area(); - continue; - /* purecov: end */ - } - - /* - Close all instances of the table to allow MyISAM "repair" - to rename files. - @todo: This code does not close all instances of the table. - It only closes instances in other connections, but if this - connection has LOCK TABLE t1 a READ, t1 b WRITE, - both t1 instances will be kept open. - There is no need to execute this branch for InnoDB, which does - repair by recreate. There is no need to do it for OPTIMIZE, - which doesn't move files around. - Hence, this code should be moved to prepare_for_repair(), - and executed only for MyISAM engine. - */ - if (lock_type == TL_WRITE && !table->table->s->tmp_table) - { - if (wait_while_table_is_used(thd, table->table, - HA_EXTRA_PREPARE_FOR_RENAME)) - goto err; - DEBUG_SYNC(thd, "after_admin_flush"); - /* Flush entries in the query cache involving this table. */ - query_cache_invalidate3(thd, table->table, 0); - /* - XXX: hack: switch off open_for_modify to skip the - flush that is made later in the execution flow. - */ - open_for_modify= 0; - } - - if (table->table->s->crashed && operator_func == &handler::ha_check) - { - /* purecov: begin inspected */ - DBUG_PRINT("admin", ("sending crashed warning")); - protocol->prepare_for_resend(); - protocol->store(table_name, system_charset_info); - protocol->store(operator_name, system_charset_info); - protocol->store(STRING_WITH_LEN("warning"), system_charset_info); - protocol->store(STRING_WITH_LEN("Table is marked as crashed"), - system_charset_info); - if (protocol->write()) - goto err; - /* purecov: end */ - } - - if (operator_func == &handler::ha_repair && - !(check_opt->sql_flags & TT_USEFRM)) - { - if ((table->table->file->check_old_types() == HA_ADMIN_NEEDS_ALTER) || - (table->table->file->ha_check_for_upgrade(check_opt) == - HA_ADMIN_NEEDS_ALTER)) - { - DBUG_PRINT("admin", ("recreating table")); - trans_rollback_stmt(thd); - trans_rollback(thd); - close_thread_tables(thd); - thd->mdl_context.release_transactional_locks(); - tmp_disable_binlog(thd); // binlogging is done by caller if wanted - result_code= mysql_recreate_table(thd, table); - reenable_binlog(thd); - /* - mysql_recreate_table() can push OK or ERROR. - Clear 'OK' status. If there is an error, keep it: - we will store the error message in a result set row - and then clear. - */ - if (thd->stmt_da->is_ok()) - thd->stmt_da->reset_diagnostics_area(); - table->table= NULL; - result_code= result_code ? HA_ADMIN_FAILED : HA_ADMIN_OK; - goto send_result; - } - } - - DBUG_PRINT("admin", ("calling operator_func '%s'", operator_name)); - result_code = (table->table->file->*operator_func)(thd, check_opt); - DBUG_PRINT("admin", ("operator_func returned: %d", result_code)); - -send_result: - - lex->cleanup_after_one_table_open(); - thd->clear_error(); // these errors shouldn't get client - { - List_iterator_fast it(thd->warning_info->warn_list()); - MYSQL_ERROR *err; - while ((err= it++)) - { - protocol->prepare_for_resend(); - protocol->store(table_name, system_charset_info); - protocol->store((char*) operator_name, system_charset_info); - protocol->store(warning_level_names[err->get_level()].str, - warning_level_names[err->get_level()].length, - system_charset_info); - protocol->store(err->get_message_text(), system_charset_info); - if (protocol->write()) - goto err; - } - thd->warning_info->clear_warning_info(thd->query_id); - } - protocol->prepare_for_resend(); - protocol->store(table_name, system_charset_info); - protocol->store(operator_name, system_charset_info); - -send_result_message: - - DBUG_PRINT("info", ("result_code: %d", result_code)); - switch (result_code) { - case HA_ADMIN_NOT_IMPLEMENTED: - { - char buf[MYSQL_ERRMSG_SIZE]; - size_t length=my_snprintf(buf, sizeof(buf), - ER(ER_CHECK_NOT_IMPLEMENTED), operator_name); - protocol->store(STRING_WITH_LEN("note"), system_charset_info); - protocol->store(buf, length, system_charset_info); - } - break; - - case HA_ADMIN_NOT_BASE_TABLE: - { - char buf[MYSQL_ERRMSG_SIZE]; - size_t length= my_snprintf(buf, sizeof(buf), - ER(ER_BAD_TABLE_ERROR), table_name); - protocol->store(STRING_WITH_LEN("note"), system_charset_info); - protocol->store(buf, length, system_charset_info); - } - break; - - case HA_ADMIN_OK: - protocol->store(STRING_WITH_LEN("status"), system_charset_info); - protocol->store(STRING_WITH_LEN("OK"), system_charset_info); - break; - - case HA_ADMIN_FAILED: - protocol->store(STRING_WITH_LEN("status"), system_charset_info); - protocol->store(STRING_WITH_LEN("Operation failed"), - system_charset_info); - break; - - case HA_ADMIN_REJECT: - protocol->store(STRING_WITH_LEN("status"), system_charset_info); - protocol->store(STRING_WITH_LEN("Operation need committed state"), - system_charset_info); - open_for_modify= FALSE; - break; - - case HA_ADMIN_ALREADY_DONE: - protocol->store(STRING_WITH_LEN("status"), system_charset_info); - protocol->store(STRING_WITH_LEN("Table is already up to date"), - system_charset_info); - break; - - case HA_ADMIN_CORRUPT: - protocol->store(STRING_WITH_LEN("error"), system_charset_info); - protocol->store(STRING_WITH_LEN("Corrupt"), system_charset_info); - fatal_error=1; - break; - - case HA_ADMIN_INVALID: - protocol->store(STRING_WITH_LEN("error"), system_charset_info); - protocol->store(STRING_WITH_LEN("Invalid argument"), - system_charset_info); - break; - - case HA_ADMIN_TRY_ALTER: - { - /* - This is currently used only by InnoDB. ha_innobase::optimize() answers - "try with alter", so here we close the table, do an ALTER TABLE, - reopen the table and do ha_innobase::analyze() on it. - We have to end the row, so analyze could return more rows. - */ - trans_commit_stmt(thd); - trans_commit(thd); - close_thread_tables(thd); - thd->mdl_context.release_transactional_locks(); - DEBUG_SYNC(thd, "ha_admin_try_alter"); - protocol->store(STRING_WITH_LEN("note"), system_charset_info); - protocol->store(STRING_WITH_LEN( - "Table does not support optimize, doing recreate + analyze instead"), - system_charset_info); - if (protocol->write()) - goto err; - DBUG_PRINT("info", ("HA_ADMIN_TRY_ALTER, trying analyze...")); - TABLE_LIST *save_next_local= table->next_local, - *save_next_global= table->next_global; - table->next_local= table->next_global= 0; - tmp_disable_binlog(thd); // binlogging is done by caller if wanted - result_code= mysql_recreate_table(thd, table); - reenable_binlog(thd); - /* - mysql_recreate_table() can push OK or ERROR. - Clear 'OK' status. If there is an error, keep it: - we will store the error message in a result set row - and then clear. - */ - if (thd->stmt_da->is_ok()) - thd->stmt_da->reset_diagnostics_area(); - trans_commit_stmt(thd); - trans_commit(thd); - close_thread_tables(thd); - thd->mdl_context.release_transactional_locks(); - table->table= NULL; - if (!result_code) // recreation went ok - { - /* Clear the ticket released above. */ - table->mdl_request.ticket= NULL; - DEBUG_SYNC(thd, "ha_admin_open_ltable"); - table->mdl_request.set_type(MDL_SHARED_WRITE); - if ((table->table= open_ltable(thd, table, lock_type, 0))) - { - result_code= table->table->file->ha_analyze(thd, check_opt); - if (result_code == HA_ADMIN_ALREADY_DONE) - result_code= HA_ADMIN_OK; - else if (result_code) // analyze failed - table->table->file->print_error(result_code, MYF(0)); - } - else - result_code= -1; // open failed - } - /* Start a new row for the final status row */ - protocol->prepare_for_resend(); - protocol->store(table_name, system_charset_info); - protocol->store(operator_name, system_charset_info); - if (result_code) // either mysql_recreate_table or analyze failed - { - DBUG_ASSERT(thd->is_error()); - if (thd->is_error()) - { - const char *err_msg= thd->stmt_da->message(); - if (!thd->vio_ok()) - { - sql_print_error("%s", err_msg); - } - else - { - /* Hijack the row already in-progress. */ - protocol->store(STRING_WITH_LEN("error"), system_charset_info); - protocol->store(err_msg, system_charset_info); - if (protocol->write()) - goto err; - /* Start off another row for HA_ADMIN_FAILED */ - protocol->prepare_for_resend(); - protocol->store(table_name, system_charset_info); - protocol->store(operator_name, system_charset_info); - } - thd->clear_error(); - } - } - result_code= result_code ? HA_ADMIN_FAILED : HA_ADMIN_OK; - table->next_local= save_next_local; - table->next_global= save_next_global; - goto send_result_message; - } - case HA_ADMIN_WRONG_CHECKSUM: - { - protocol->store(STRING_WITH_LEN("note"), system_charset_info); - protocol->store(ER(ER_VIEW_CHECKSUM), strlen(ER(ER_VIEW_CHECKSUM)), - system_charset_info); - break; - } - - case HA_ADMIN_NEEDS_UPGRADE: - case HA_ADMIN_NEEDS_ALTER: - { - char buf[MYSQL_ERRMSG_SIZE]; - size_t length; - - protocol->store(STRING_WITH_LEN("error"), system_charset_info); - length=my_snprintf(buf, sizeof(buf), ER(ER_TABLE_NEEDS_UPGRADE), - table->table_name); - protocol->store(buf, length, system_charset_info); - fatal_error=1; - break; - } - - default: // Probably HA_ADMIN_INTERNAL_ERROR - { - char buf[MYSQL_ERRMSG_SIZE]; - size_t length=my_snprintf(buf, sizeof(buf), - "Unknown - internal error %d during operation", - result_code); - protocol->store(STRING_WITH_LEN("error"), system_charset_info); - protocol->store(buf, length, system_charset_info); - fatal_error=1; - break; - } - } - if (table->table) - { - if (table->table->s->tmp_table) - { - /* - If the table was not opened successfully, do not try to get - status information. (Bug#47633) - */ - if (open_for_modify && !open_error) - table->table->file->info(HA_STATUS_CONST); - } - else if (open_for_modify || fatal_error) - { - mysql_mutex_lock(&LOCK_open); - tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, - table->db, table->table_name); - mysql_mutex_unlock(&LOCK_open); - /* - May be something modified. Consequently, we have to - invalidate the query cache. - */ - table->table= 0; // For query cache - query_cache_invalidate3(thd, table, 0); - } - } - /* Error path, a admin command failed. */ - trans_commit_stmt(thd); - trans_commit_implicit(thd); - close_thread_tables(thd); - thd->mdl_context.release_transactional_locks(); - - /* - If it is CHECK TABLE v1, v2, v3, and v1, v2, v3 are views, we will run - separate open_tables() for each CHECK TABLE argument. - Right now we do not have a separate method to reset the prelocking - state in the lex to the state after parsing, so each open will pollute - this state: add elements to lex->srotuines_list, TABLE_LISTs to - lex->query_tables. Below is a lame attempt to recover from this - pollution. - @todo: have a method to reset a prelocking context, or use separate - contexts for each open. - */ - for (Sroutine_hash_entry *rt= - (Sroutine_hash_entry*)thd->lex->sroutines_list.first; - rt; rt= rt->next) - rt->mdl_request.ticket= NULL; - - if (protocol->write()) - goto err; - } - - my_eof(thd); - DBUG_RETURN(FALSE); - -err: - trans_rollback_stmt(thd); - trans_rollback(thd); - close_thread_tables(thd); // Shouldn't be needed - thd->mdl_context.release_transactional_locks(); - if (table) - table->table=0; - DBUG_RETURN(TRUE); -} - - -/* - Assigned specified indexes for a table into key cache - - SYNOPSIS - mysql_assign_to_keycache() - thd Thread object - tables Table list (one table only) - - RETURN VALUES - FALSE ok - TRUE error -*/ - -bool mysql_assign_to_keycache(THD* thd, TABLE_LIST* tables, - LEX_STRING *key_cache_name) -{ - HA_CHECK_OPT check_opt; - KEY_CACHE *key_cache; - DBUG_ENTER("mysql_assign_to_keycache"); - - check_opt.init(); - mysql_mutex_lock(&LOCK_global_system_variables); - if (!(key_cache= get_key_cache(key_cache_name))) - { - mysql_mutex_unlock(&LOCK_global_system_variables); - my_error(ER_UNKNOWN_KEY_CACHE, MYF(0), key_cache_name->str); - DBUG_RETURN(TRUE); - } - mysql_mutex_unlock(&LOCK_global_system_variables); - check_opt.key_cache= key_cache; - DBUG_RETURN(mysql_admin_table(thd, tables, &check_opt, - "assign_to_keycache", TL_READ_NO_INSERT, 0, 0, - 0, 0, &handler::assign_to_keycache, 0)); -} - - -/* - Preload specified indexes for a table into key cache - - SYNOPSIS - mysql_preload_keys() - thd Thread object - tables Table list (one table only) - - RETURN VALUES - FALSE ok - TRUE error -*/ - -bool mysql_preload_keys(THD* thd, TABLE_LIST* tables) -{ - DBUG_ENTER("mysql_preload_keys"); - /* - We cannot allow concurrent inserts. The storage engine reads - directly from the index file, bypassing the cache. It could read - outdated information if parallel inserts into cache blocks happen. - */ - DBUG_RETURN(mysql_admin_table(thd, tables, 0, - "preload_keys", TL_READ_NO_INSERT, 0, 0, 0, 0, - &handler::preload_keys, 0)); -} - - -bool Analyze_table_statement::execute(THD *thd) -{ - TABLE_LIST *first_table= m_lex->select_lex.table_list.first; - bool res= TRUE; - thr_lock_type lock_type = TL_READ_NO_INSERT; - DBUG_ENTER("Analyze_table_statement::execute"); - - if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, - FALSE, UINT_MAX, FALSE)) - goto error; - thd->enable_slow_log= opt_log_slow_admin_statements; - res= mysql_admin_table(thd, first_table, &m_lex->check_opt, - "analyze", lock_type, 1, 0, 0, 0, - &handler::ha_analyze, 0); - /* ! we write after unlocking the table */ - if (!res && !m_lex->no_write_to_binlog) - { - /* - Presumably, ANALYZE and binlog writing doesn't require synchronization - */ - res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - } - m_lex->select_lex.table_list.first= first_table; - m_lex->query_tables= first_table; - -error: - DBUG_RETURN(res); -} - - -bool Check_table_statement::execute(THD *thd) -{ - TABLE_LIST *first_table= m_lex->select_lex.table_list.first; - thr_lock_type lock_type = TL_READ_NO_INSERT; - bool res= TRUE; - DBUG_ENTER("Check_table_statement::execute"); - - if (check_table_access(thd, SELECT_ACL, first_table, - TRUE, UINT_MAX, FALSE)) - goto error; /* purecov: inspected */ - thd->enable_slow_log= opt_log_slow_admin_statements; - - res= mysql_admin_table(thd, first_table, &m_lex->check_opt, "check", - lock_type, 0, 0, HA_OPEN_FOR_REPAIR, 0, - &handler::ha_check, &view_checksum); - - m_lex->select_lex.table_list.first= first_table; - m_lex->query_tables= first_table; - -error: - DBUG_RETURN(res); -} - - -bool Optimize_table_statement::execute(THD *thd) -{ - TABLE_LIST *first_table= m_lex->select_lex.table_list.first; - bool res= TRUE; - DBUG_ENTER("Optimize_table_statement::execute"); - - if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, - FALSE, UINT_MAX, FALSE)) - goto error; /* purecov: inspected */ - thd->enable_slow_log= opt_log_slow_admin_statements; - res= (specialflag & (SPECIAL_SAFE_MODE | SPECIAL_NO_NEW_FUNC)) ? - mysql_recreate_table(thd, first_table) : - mysql_admin_table(thd, first_table, &m_lex->check_opt, - "optimize", TL_WRITE, 1, 0, 0, 0, - &handler::ha_optimize, 0); - /* ! we write after unlocking the table */ - if (!res && !m_lex->no_write_to_binlog) - { - /* - Presumably, OPTIMIZE and binlog writing doesn't require synchronization - */ - res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - } - m_lex->select_lex.table_list.first= first_table; - m_lex->query_tables= first_table; - -error: - DBUG_RETURN(res); -} - - -bool Repair_table_statement::execute(THD *thd) -{ - TABLE_LIST *first_table= m_lex->select_lex.table_list.first; - bool res= TRUE; - DBUG_ENTER("Repair_table_statement::execute"); - - if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, - FALSE, UINT_MAX, FALSE)) - goto error; /* purecov: inspected */ - thd->enable_slow_log= opt_log_slow_admin_statements; - res= mysql_admin_table(thd, first_table, &m_lex->check_opt, "repair", - TL_WRITE, 1, - test(m_lex->check_opt.sql_flags & TT_USEFRM), - HA_OPEN_FOR_REPAIR, &prepare_for_repair, - &handler::ha_repair, 0); - - /* ! we write after unlocking the table */ - if (!res && !m_lex->no_write_to_binlog) - { - /* - Presumably, REPAIR and binlog writing doesn't require synchronization - */ - res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - } - m_lex->select_lex.table_list.first= first_table; - m_lex->query_tables= first_table; - -error: - DBUG_RETURN(res); -} diff --git a/sql/sql_table_maintenance.h b/sql/sql_table_maintenance.h deleted file mode 100644 index fdfffec8361..00000000000 --- a/sql/sql_table_maintenance.h +++ /dev/null @@ -1,132 +0,0 @@ -/* Copyright (c) 2010 Oracle and/or its affiliates. All rights reserved. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - -#ifndef SQL_TABLE_MAINTENANCE_H -#define SQL_TABLE_MAINTENANCE_H - - -bool mysql_assign_to_keycache(THD* thd, TABLE_LIST* table_list, - LEX_STRING *key_cache_name); -bool mysql_preload_keys(THD* thd, TABLE_LIST* table_list); -int reassign_keycache_tables(THD* thd, KEY_CACHE *src_cache, - KEY_CACHE *dst_cache); - -/** - Analyze_statement represents the ANALYZE TABLE statement. -*/ -class Analyze_table_statement : public Sql_statement -{ -public: - /** - Constructor, used to represent a ANALYZE TABLE statement. - @param lex the LEX structure for this statement. - */ - Analyze_table_statement(LEX *lex) - : Sql_statement(lex) - {} - - ~Analyze_table_statement() - {} - - /** - Execute a ANALYZE TABLE statement at runtime. - @param thd the current thread. - @return false on success. - */ - bool execute(THD *thd); -}; - - - -/** - Check_table_statement represents the CHECK TABLE statement. -*/ -class Check_table_statement : public Sql_statement -{ -public: - /** - Constructor, used to represent a CHECK TABLE statement. - @param lex the LEX structure for this statement. - */ - Check_table_statement(LEX *lex) - : Sql_statement(lex) - {} - - ~Check_table_statement() - {} - - /** - Execute a CHECK TABLE statement at runtime. - @param thd the current thread. - @return false on success. - */ - bool execute(THD *thd); -}; - - - -/** - Optimize_table_statement represents the OPTIMIZE TABLE statement. -*/ -class Optimize_table_statement : public Sql_statement -{ -public: - /** - Constructor, used to represent a OPTIMIZE TABLE statement. - @param lex the LEX structure for this statement. - */ - Optimize_table_statement(LEX *lex) - : Sql_statement(lex) - {} - - ~Optimize_table_statement() - {} - - /** - Execute a OPTIMIZE TABLE statement at runtime. - @param thd the current thread. - @return false on success. - */ - bool execute(THD *thd); -}; - - - -/** - Repair_table_statement represents the REPAIR TABLE statement. -*/ -class Repair_table_statement : public Sql_statement -{ -public: - /** - Constructor, used to represent a REPAIR TABLE statement. - @param lex the LEX structure for this statement. - */ - Repair_table_statement(LEX *lex) - : Sql_statement(lex) - {} - - ~Repair_table_statement() - {} - - /** - Execute a REPAIR TABLE statement at runtime. - @param thd the current thread. - @return false on success. - */ - bool execute(THD *thd); -}; - -#endif diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 64ce1a5f64b..443b80799aa 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -51,9 +51,9 @@ #include "sp_pcontext.h" #include "sp_rcontext.h" #include "sp.h" -#include "sql_alter_table.h" // Alter_table*_statement +#include "sql_alter.h" // Alter_table*_statement #include "sql_truncate.h" // Truncate_statement -#include "sql_table_maintenance.h" // Analyze/Check..._table_stmt +#include "sql_admin.h" // Analyze/Check..._table_stmt #include "sql_partition_admin.h" // Alter_table_*_partition_stmt #include "sql_signal.h" #include "event_parse_data.h" -- cgit v1.2.1 From b8dde1ef402cb82cb48177718437cab019c65d04 Mon Sep 17 00:00:00 2001 From: Mats Kindahl Date: Tue, 17 Aug 2010 07:46:53 +0200 Subject: WL#5363: Thread pool interface Patch to make Visual C++ happy. --- sql/mysqld.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sql') diff --git a/sql/mysqld.h b/sql/mysqld.h index 2dc854134c7..6135fcead8b 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -28,7 +28,7 @@ class THD; struct handlerton; class Time_zone; -class scheduler_functions; +struct scheduler_functions; typedef struct st_mysql_const_lex_string LEX_CSTRING; typedef struct st_mysql_show_var SHOW_VAR; -- cgit v1.2.1 From b17b122b7daa2f6fbc04ab7a32269d6f2d22cbfe Mon Sep 17 00:00:00 2001 From: Jimmy Yang Date: Tue, 17 Aug 2010 01:19:24 -0700 Subject: Fix bug #53496 Use Lock_time in slow query log output for InnoDB row lock wait time. Including the InnoDB lock time in the exiting "Lock_time" output. --- sql/sql_class.cc | 5 +++++ sql/sql_class.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'sql') diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 1bec02afa96..28e86ecc67f 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -307,6 +307,11 @@ void **thd_ha_data(const THD *thd, const struct handlerton *hton) return (void **) &thd->ha_data[hton->slot].ha_ptr; } +extern "C" +void thd_storage_lock_wait(THD *thd, long long value) +{ + thd->utime_after_lock+= value; +} /** Provide a handler data getter to simplify coding diff --git a/sql/sql_class.h b/sql/sql_class.h index c095fee6232..b135af41af0 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1505,7 +1505,7 @@ public: // track down slow pthread_create ulonglong prior_thr_create_utime, thr_create_utime; ulonglong start_utime, utime_after_lock; - + thr_lock_type update_lock_default; Delayed_insert *di; -- cgit v1.2.1 From 9d6811502ed22f7b4aa99e2be1d5c8ac45792790 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 18 Aug 2010 12:56:06 +0800 Subject: WL#5370 Keep forward-compatibility when changing 'CREATE TABLE IF NOT EXISTS ... SELECT' behaviour BUG#55474, BUG#55499, BUG#55598, BUG#55616 and BUG#55777 are fixed in this patch too. This is the 5.1 part. It implements: - if the table exists, binlog two events: CREATE TABLE IF NOT EXISTS and INSERT ... SELECT - Insert nothing and binlog nothing on master if the existing object is a view. It only generates a warning that table already exists. mysql-test/r/trigger.result: Ather this patch, 'CREATE TABLE IF NOT EXISTS ... SELECT' will not insert anything if the creating table already exists and is a view. sql/sql_class.h: Declare virtual function write_to_binlog() for select_insert. It's used to binlog 'create select' sql/sql_insert.cc: Implement write_to_binlog(); Use write_to_binlog() instead of binlog_query() to binlog the statement. if the table exists, binlog two events: CREATE TABLE IF NOT EXISTS and INSERT ... SELECT sql/sql_lex.h: Declare create_select_start_with_brace and create_select_pos. They are helpful for binlogging 'create select' sql/sql_parse.cc: Do nothing on master if the existing object is a view. sql/sql_yacc.yy: Record the relative postion of 'SELECT' in the 'CREATE ...SELECT' statement. Record whether there is a '(' before the 'SELECT' clause. --- sql/sql_class.h | 8 +++- sql/sql_insert.cc | 122 ++++++++++++++++++++++++++++++++++++++++++++++++------ sql/sql_lex.h | 17 ++++++++ sql/sql_parse.cc | 19 +++++++++ sql/sql_yacc.yy | 28 +++++++++++-- 5 files changed, 176 insertions(+), 18 deletions(-) (limited to 'sql') diff --git a/sql/sql_class.h b/sql/sql_class.h index 1627b6ec02d..42c873e9fc3 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -2622,7 +2622,9 @@ public: class select_insert :public select_result_interceptor { - public: +protected: + virtual int write_to_binlog(bool is_trans, int errcode); +public: TABLE_LIST *table_list; TABLE *table; List *fields; @@ -2658,6 +2660,8 @@ class select_create: public select_insert { MYSQL_LOCK *m_lock; /* m_lock or thd->extra_lock */ MYSQL_LOCK **m_plock; + + virtual int write_to_binlog(bool is_trans, int errcode); public: select_create (TABLE_LIST *table_arg, HA_CREATE_INFO *create_info_par, @@ -2673,7 +2677,7 @@ public: {} int prepare(List &list, SELECT_LEX_UNIT *u); - int binlog_show_create_table(TABLE **tables, uint count); + int binlog_show_create_table(TABLE **tables, uint count, int errcode); void store_values(List &values); void send_error(uint errcode,const char *err); bool send_eof(); diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index eb9e1e5b3af..8604f876f37 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3268,7 +3268,7 @@ bool select_insert::send_eof() /* Write to binlog before commiting transaction. No statement will - be written by the binlog_query() below in RBR mode. All the + be written by the write_to_binlog() below in RBR mode. All the events are in the transaction cache and will be written when ha_autocommit_or_rollback() is issued below. */ @@ -3280,9 +3280,8 @@ bool select_insert::send_eof() thd->clear_error(); else errcode= query_error_code(thd, killed_status == THD::NOT_KILLED); - if (thd->binlog_query(THD::ROW_QUERY_TYPE, - thd->query(), thd->query_length(), - trans_table, FALSE, errcode)) + + if (write_to_binlog(trans_table, errcode)) { table->file->ha_release_auto_increment(); DBUG_RETURN(1); @@ -3356,9 +3355,7 @@ void select_insert::abort() { { int errcode= query_error_code(thd, thd->killed == THD::NOT_KILLED); /* error of writing binary log is ignored */ - (void) thd->binlog_query(THD::ROW_QUERY_TYPE, thd->query(), - thd->query_length(), - transactional_table, FALSE, errcode); + write_to_binlog(transactional_table, errcode); } if (!thd->current_stmt_binlog_row_based && !can_rollback_data()) thd->transaction.all.modified_non_trans_table= TRUE; @@ -3373,6 +3370,103 @@ void select_insert::abort() { DBUG_VOID_RETURN; } +int select_insert::write_to_binlog(bool is_trans, int errcode) +{ + /* It is only for statement mode */ + if (thd->current_stmt_binlog_row_based) + return 0; + + return thd->binlog_query(THD::ROW_QUERY_TYPE, + thd->query(), thd->query_length(), + is_trans, FALSE, errcode); +} + +/* Override the select_insert::write_to_binlog */ +int select_create::write_to_binlog(bool is_trans, int errcode) +{ + /* It is only for statement mode */ + if (thd->current_stmt_binlog_row_based) + return 0; + + /* + WL#5370 Keep the compatibility between 5.1 master and 5.5 slave. + Binlog a 'INSERT ... SELECT' statement only when it has the option + 'IF NOT EXISTS' and the table already exists as a base table. + */ + if ((create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) && + create_info->table_existed) + { + String query; + int result; + + thd->binlog_start_trans_and_stmt(); + /* Binlog the CREATE TABLE IF NOT EXISTS statement */ + result= binlog_show_create_table(&table, 1, 0); + if (result) + return result; + + uint db_len= strlen(create_table->db); + uint table_len= strlen(create_info->alias); + uint select_len= thd->query_length() - thd->lex->create_select_pos; + uint field_len= (table->s->fields - (field - table->field)) * + (MAX_FIELD_NAME + 3); + + /* + pre-allocating memory reduces the times of reallocating memory, + when calling query.appen(). + 40bytes is enough for other words("INSERT IGNORE INTO", etc.). + */ + if (query.real_alloc(40 + db_len + table_len + field_len + select_len)) + return 1; + + if (thd->lex->create_select_in_comment) + query.append(STRING_WITH_LEN("/*! ")); + if (thd->lex->ignore) + query.append(STRING_WITH_LEN("INSERT IGNORE INTO `")); + else if (thd->lex->duplicates == DUP_REPLACE) + query.append(STRING_WITH_LEN("REPLACE INTO `")); + else + query.append(STRING_WITH_LEN("INSERT INTO `")); + + query.append(create_table->db, db_len); + query.append(STRING_WITH_LEN("`.`")); + query.append(create_info->alias, table_len); + query.append(STRING_WITH_LEN("` ")); + + /* + The insert items. + Field is the the rightmost columns that the rows are inster in. + */ + query.append(STRING_WITH_LEN("(")); + for (Field **f= field ; *f ; f++) + { + if (f != field) + query.append(STRING_WITH_LEN(",")); + + query.append(STRING_WITH_LEN("`")); + query.append((*f)->field_name, strlen((*f)->field_name)); + query.append(STRING_WITH_LEN("`")); + } + query.append(STRING_WITH_LEN(") ")); + + /* The SELECT clause*/ + DBUG_ASSERT(thd->lex->create_select_pos); + if (thd->lex->create_select_start_with_brace) + query.append(STRING_WITH_LEN("(")); + if (query.append(thd->query() + thd->lex->create_select_pos, select_len)) + return 1; + + /* + Avoid to use thd->binlog_query() twice, otherwise it will print the unsafe + warning twice. + */ + Query_log_event ev(thd, query.c_ptr_safe(), query.length(), is_trans, + FALSE, errcode); + return mysql_bin_log.write(&ev); + } + else + return select_insert::write_to_binlog(is_trans, errcode); +} /*************************************************************************** CREATE TABLE (SELECT) ... @@ -3613,7 +3707,8 @@ select_create::prepare(List &values, SELECT_LEX_UNIT *u) !table->s->tmp_table && !ptr->get_create_info()->table_existed) { - if (int error= ptr->binlog_show_create_table(tables, count)) + int errcode= query_error_code(thd, thd->killed == THD::NOT_KILLED); + if (int error= ptr->binlog_show_create_table(tables, count, errcode)) return error; } return 0; @@ -3654,7 +3749,10 @@ select_create::prepare(List &values, SELECT_LEX_UNIT *u) ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), create_table->table_name); if (thd->current_stmt_binlog_row_based) - binlog_show_create_table(&(create_table->table), 1); + { + int errcode= query_error_code(thd, thd->killed == THD::NOT_KILLED); + binlog_show_create_table(&(create_table->table), 1, errcode); + } table= create_table->table; } else @@ -3722,10 +3820,10 @@ select_create::prepare(List &values, SELECT_LEX_UNIT *u) } int -select_create::binlog_show_create_table(TABLE **tables, uint count) +select_create::binlog_show_create_table(TABLE **tables, uint count, int errcode) { /* - Note 1: In RBR mode, we generate a CREATE TABLE statement for the + Note 1: We generate a CREATE TABLE statement for the created table by calling store_create_info() (behaves as SHOW CREATE TABLE). In the event of an error, nothing should be written to the binary log, even if the table is non-transactional; @@ -3741,7 +3839,6 @@ select_create::binlog_show_create_table(TABLE **tables, uint count) schema that will do a close_thread_tables(), destroying the statement transaction cache. */ - DBUG_ASSERT(thd->current_stmt_binlog_row_based); DBUG_ASSERT(tables && *tables && count > 0); char buf[2048]; @@ -3759,7 +3856,6 @@ select_create::binlog_show_create_table(TABLE **tables, uint count) if (mysql_bin_log.is_open()) { - int errcode= query_error_code(thd, thd->killed == THD::NOT_KILLED); result= thd->binlog_query(THD::STMT_QUERY_TYPE, query.ptr(), query.length(), /* is_trans */ TRUE, diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 7403bb5a1a4..9131cec9d04 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -1817,6 +1817,23 @@ typedef struct st_lex : public Query_tables_list */ bool protect_against_global_read_lock; + /* + The following three variables are used in 'CREATE TABLE IF NOT EXISTS ... + SELECT' statement. They are used to binlog the statement. + + create_select_start_with_brace will be set if there is a '(' before + the first SELECT clause + + create_select_pos records the relative position of the SELECT clause + in the whole statement. + + create_select_in_comment will be set if SELECT keyword is in conditional + comment. + */ + bool create_select_start_with_brace; + uint create_select_pos; + bool create_select_in_comment; + st_lex(); virtual ~st_lex() diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 9ec03ea1d5f..fbe9c9753d9 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2717,6 +2717,25 @@ mysql_execute_command(THD *thd) { TABLE_LIST *duplicate; create_table= lex->unlink_first_table(&link_to_local); + + if (create_table->view) + { + if (create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS) + { + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_TABLE_EXISTS_ERROR, + ER(ER_TABLE_EXISTS_ERROR), + create_info.alias); + my_ok(thd); + } + else + { + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), create_info.alias); + res= 1; + } + goto end_with_restore_list; + } + if ((duplicate= unique_table(thd, create_table, select_tables, 0))) { update_non_unique_table_error(create_table, "CREATE", duplicate); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 5a3ad0b3eba..ed367582ba5 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -3881,17 +3881,26 @@ create2a: create3 {} | opt_partitioning create_select ')' - { Select->set_braces(1);} + { + Select->set_braces(1); + Lex->create_select_start_with_brace= TRUE; + } union_opt {} ; create3: /* empty */ {} | opt_duplicate opt_as create_select - { Select->set_braces(0);} + { + Select->set_braces(0); + Lex->create_select_start_with_brace= FALSE; + } union_clause {} | opt_duplicate opt_as '(' create_select ')' - { Select->set_braces(1);} + { + Select->set_braces(1); + Lex->create_select_start_with_brace= TRUE; + } union_opt {} ; @@ -4516,6 +4525,19 @@ create_select: lex->current_select->table_list.save_and_clear(&lex->save_list); mysql_init_select(lex); lex->current_select->parsing_place= SELECT_LIST; + + if (lex->sql_command == SQLCOM_CREATE_TABLE && + (lex->create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS)) + { + Lex_input_stream *lip= YYLIP; + + if (lex->spcont) + lex->create_select_pos= lip->get_tok_start() - + lex->sphead->m_tmp_query; + else + lex->create_select_pos= lip->get_tok_start() - lip->get_buf(); + lex->create_select_in_comment= (lip->in_comment == DISCARD_COMMENT); + } } select_options select_item_list { -- cgit v1.2.1 From d0d8bbed5e901e59044be6bcaa6d4020238a1eb4 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 18 Aug 2010 17:35:41 +0800 Subject: WL#5370 Keep forward-compatibility when changing 'CREATE TABLE IF NOT EXISTS ... SELECT' behaviour BUG#47132, BUG#47442, BUG49494, BUG#23992 and BUG#48814 will disappear automatically after the this patch. BUG#55617 is fixed by this patch too. This is the 5.5 part. It implements: - 'CREATE TABLE IF NOT EXISTS ... SELECT' statement will not insert anything and binlog anything if the table already exists. It only generate a warning that table already exists. - A couple of test cases for the behavior changing. --- sql/handler.h | 1 - sql/sql_base.cc | 13 ------------- sql/sql_insert.cc | 55 +++++++++--------------------------------------------- sql/sql_parse.cc | 55 +++++++++++++++--------------------------------------- sql/sql_prepare.cc | 8 -------- sql/sql_table.cc | 5 ----- sql/sql_view.cc | 2 -- sql/sql_yacc.yy | 7 +++++++ sql/table.h | 17 ----------------- 9 files changed, 31 insertions(+), 132 deletions(-) (limited to 'sql') diff --git a/sql/handler.h b/sql/handler.h index cad97c1f751..83158708837 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -1001,7 +1001,6 @@ typedef struct st_ha_create_information uint merge_insert_method; uint extra_size; /* length of extra data segment */ enum enum_ha_unused unused1; - bool table_existed; /* 1 in create if table existed */ bool frm_only; /* 1 if no ha_create_table() */ bool varchar; /* 1 if table has a VARCHAR */ enum ha_storage_media storage_media; /* DEFAULT, DISK or MEMORY */ diff --git a/sql/sql_base.cc b/sql/sql_base.cc index e091c26592e..243a551d24b 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2540,10 +2540,6 @@ open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx, is never opened. In both cases, metadata locks are always taken according to the lock strategy. - If the lock strategy is OTLS_DOWNGRADE_IF_EXISTS and opening the table - is successful, the exclusive metadata lock acquired by the caller - is downgraded to a shared lock. - RETURN TRUE Open failed. "action" parameter may contain type of action needed to remedy problem before retrying again. @@ -2952,15 +2948,6 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, mysql_mutex_unlock(&LOCK_open); - /* - In CREATE TABLE .. If NOT EXISTS .. SELECT we have found that - table exists now we should downgrade our exclusive metadata - lock on this table to SW metadata lock. - */ - if (table_list->lock_strategy == TABLE_LIST::OTLS_DOWNGRADE_IF_EXISTS && - !(flags & MYSQL_OPEN_HAS_MDL_LOCK)) - mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_WRITE); - table->mdl_ticket= mdl_ticket; table->next= thd->open_tables; /* Link into simple list */ diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 326a5defa9b..2314dbbeaee 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3581,17 +3581,6 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, create_info, alter_info, 0, select_field_count)) { - if (create_info->table_existed) - { - /* - This means that someone created table underneath server - or it was created via different mysqld front-end to the - cluster. We don't have much options but throw an error. - */ - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), create_table->table_name); - DBUG_RETURN(0); - } - DBUG_EXECUTE_IF("sleep_create_select_before_open", my_sleep(6000000);); if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE)) @@ -3709,15 +3698,13 @@ select_create::prepare(List &values, SELECT_LEX_UNIT *u) TABLE const *const table = *tables; if (thd->is_current_stmt_binlog_format_row() && - !table->s->tmp_table && - !ptr->get_create_info()->table_existed) + !table->s->tmp_table) { if (int error= ptr->binlog_show_create_table(tables, count)) return error; } return 0; } - select_create *ptr; TABLE_LIST *create_table; TABLE_LIST *select_tables; @@ -3740,34 +3727,15 @@ select_create::prepare(List &values, SELECT_LEX_UNIT *u) thd->binlog_start_trans_and_stmt(); } + DBUG_ASSERT(create_table->table == NULL); + DBUG_EXECUTE_IF("sleep_create_select_before_check_if_exists", my_sleep(6000000);); - if (create_table->table) - { - /* Table already exists and was open at open_and_lock_tables() stage. */ - if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) - { - /* Mark that table existed */ - create_info->table_existed= 1; - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), - create_table->table_name); - if (thd->is_current_stmt_binlog_format_row()) - binlog_show_create_table(&(create_table->table), 1); - table= create_table->table; - } - else - { - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), create_table->table_name); - DBUG_RETURN(-1); - } - } - else - if (!(table= create_table_from_items(thd, create_info, create_table, - alter_info, &values, - &extra_lock, hook_ptr))) - /* abort() deletes table */ - DBUG_RETURN(-1); + if (!(table= create_table_from_items(thd, create_info, create_table, + alter_info, &values, + &extra_lock, hook_ptr))) + /* abort() deletes table */ + DBUG_RETURN(-1); if (extra_lock) { @@ -3887,10 +3855,6 @@ void select_create::send_error(uint errcode,const char *err) ("Current table (at 0x%lu) %s a temporary (or non-existant) table", (ulong) table, table && !table->s->tmp_table ? "is NOT" : "is")); - DBUG_PRINT("info", - ("Table %s prior to executing this statement", - get_create_info()->table_existed ? "existed" : "did not exist")); - /* This will execute any rollbacks that are necessary before writing the transcation cache. @@ -3979,8 +3943,7 @@ void select_create::abort_result_set() table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE); table->auto_increment_field_not_null= FALSE; - if (!create_info->table_existed) - drop_open_table(thd, table, create_table->db, create_table->table_name); + drop_open_table(thd, table, create_table->db, create_table->table_name); table=0; // Safety } DBUG_VOID_RETURN; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 806a2984905..b5f55b981c8 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2590,13 +2590,7 @@ case SQLCOM_PREPARE: } #endif - /* Set strategies: reset default or 'prepared' values. */ - create_table->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; - create_table->lock_strategy= TABLE_LIST::OTLS_DOWNGRADE_IF_EXISTS; - - /* - Close any open handlers for the table - */ + /* Close any open handlers for the table. */ mysql_ha_rm_tables(thd, create_table); if (select_lex->item_list.elements) // With select @@ -2656,44 +2650,25 @@ case SQLCOM_PREPARE: goto end_with_restore_list; } - if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) - { - /* Base table and temporary table are not in the same name space. */ - create_table->open_type= OT_BASE_ONLY; - } - if (!(res= open_and_lock_tables(thd, lex->query_tables, TRUE, 0))) { - /* - Is table which we are changing used somewhere in other parts - of query - */ - if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) + /* The table already exists */ + if (create_table->table) { - TABLE_LIST *duplicate; - if ((duplicate= unique_table(thd, create_table, select_tables, 0))) + if (create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS) { - update_non_unique_table_error(create_table, "CREATE", duplicate); - res= 1; - goto end_with_restore_list; + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_TABLE_EXISTS_ERROR, + ER(ER_TABLE_EXISTS_ERROR), + create_info.alias); + my_ok(thd); } - } - /* If we create merge table, we have to test tables in merge, too */ - if (create_info.used_fields & HA_CREATE_USED_UNION) - { - TABLE_LIST *tab; - for (tab= create_info.merge_list.first; - tab; - tab= tab->next_local) + else { - TABLE_LIST *duplicate; - if ((duplicate= unique_table(thd, tab, select_tables, 0))) - { - update_non_unique_table_error(tab, "CREATE", duplicate); - res= 1; - goto end_with_restore_list; - } + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), create_info.alias); + res= 1; } + goto end_with_restore_list; } /* @@ -2726,7 +2701,7 @@ case SQLCOM_PREPARE: res= handle_select(thd, lex, result, 0); delete result; } - + lex->link_first_table_back(create_table, link_to_local); } } @@ -7319,7 +7294,7 @@ void create_table_set_open_action_and_adjust_tables(LEX *lex) if (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) create_table->open_type= OT_TEMPORARY_ONLY; - else if (!lex->select_lex.item_list.elements) + else create_table->open_type= OT_BASE_ONLY; if (!lex->select_lex.item_list.elements) diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 39cebfbe048..366c46d9c92 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1717,14 +1717,6 @@ static bool mysql_test_create_table(Prepared_statement *stmt) if (create_table_precheck(thd, tables, create_table)) DBUG_RETURN(TRUE); - /* - The open and lock strategies will be set again once the - statement is executed. These values are only meaningful - for the prepare phase. - */ - create_table->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; - create_table->lock_strategy= TABLE_LIST::OTLS_NONE; - if (select_lex->item_list.elements) { /* Base table and temporary table are not in the same name space. */ diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 1db7aa55792..224de005af3 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4038,7 +4038,6 @@ bool mysql_create_table_no_lock(THD *thd, { if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS) { - create_info->table_existed= 1; // Mark that table existed push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), alias); @@ -4110,7 +4109,6 @@ bool mysql_create_table_no_lock(THD *thd, } thd_proc_info(thd, "creating table"); - create_info->table_existed= 0; // Mark that table is created #ifdef HAVE_READLINK { @@ -4205,7 +4203,6 @@ warn: push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR), alias); - create_info->table_existed= 1; // Mark that table existed goto unlock_and_end; } @@ -4469,11 +4466,9 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, non-temporary table. */ DBUG_ASSERT((create_info->options & HA_LEX_CREATE_TMP_TABLE) || - local_create_info.table_existed || thd->mdl_context.is_lock_owner(MDL_key::TABLE, table->db, table->table_name, MDL_EXCLUSIVE)); - /* We have to write the query before we unlock the tables. */ diff --git a/sql/sql_view.cc b/sql/sql_view.cc index be13349b5a1..64386d755eb 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -433,8 +433,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, goto err; lex->link_first_table_back(view, link_to_local); - view->open_strategy= TABLE_LIST::OPEN_STUB; - view->lock_strategy= TABLE_LIST::OTLS_NONE; view->open_type= OT_BASE_ONLY; if (open_and_lock_tables(thd, lex->query_tables, TRUE, 0)) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 443b80799aa..27510cefad3 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -2031,6 +2031,12 @@ create: TL_OPTION_UPDATING, TL_WRITE, MDL_EXCLUSIVE)) MYSQL_YYABORT; + /* + For CREATE TABLE, an non-existing table is not an error. + Instruct open_tables() to just take an MDL lock if the + table does not exist. + */ + lex->query_tables->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; lex->alter_info.reset(); lex->col_list.empty(); lex->change=NullS; @@ -14035,6 +14041,7 @@ view_tail: TL_IGNORE, MDL_EXCLUSIVE)) MYSQL_YYABORT; + lex->query_tables->open_strategy= TABLE_LIST::OPEN_STUB; } view_list_opt AS view_select ; diff --git a/sql/table.h b/sql/table.h index 52ac92299a6..6ed3ec0a921 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1587,23 +1587,6 @@ struct TABLE_LIST /* Don't associate a table share. */ OPEN_STUB } open_strategy; - /** - Indicates the locking strategy for the object being opened. - */ - enum - { - /* - Take metadata lock specified by 'mdl_request' member before - the object is opened. Do nothing after that. - */ - OTLS_NONE= 0, - /* - Take (exclusive) metadata lock specified by 'mdl_request' member - before object is opened. If opening is successful, downgrade to - a shared lock. - */ - OTLS_DOWNGRADE_IF_EXISTS - } lock_strategy; /* For transactional locking. */ int lock_timeout; /* NOWAIT or WAIT [X] */ bool lock_transactional; /* If transactional lock requested. */ -- cgit v1.2.1 From 9452dd112243d9a59355ba26537c4e8b695d7820 Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Wed, 18 Aug 2010 16:08:59 +0400 Subject: Bug#45263 utf32_general_ci, bad effects around CREATE TABLE AS SELECT Problem: Item_func_hex::val_str() returned data in ASCII format, which did not match collation.collation pointing to my_charset_utf32_general_ci. Fix: changing parent class of Item_func_hex to Item_str_ascii_func, as val_str() implementation is heavily ASCII-oriented. mysql-test/r/ctype_utf32.result mysql-test/t/ctype_utf32.test Adding test case sql/item_strfunc.cc sql/item_strfunc.h - Changing parent class to Item_str_ascii_func - Clean-up in Item_func_hex::fix_length_and_dec() Using fix_char_length() instead of setting max_length directly. --- sql/item_strfunc.cc | 3 ++- sql/item_strfunc.h | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'sql') diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 851d0e07a7e..808cf92f0bf 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -3081,7 +3081,7 @@ String *Item_func_collation::val_str(String *str) } -String *Item_func_hex::val_str(String *str) +String *Item_func_hex::val_str_ascii(String *str) { String *res; DBUG_ASSERT(fixed == 1); @@ -3120,6 +3120,7 @@ String *Item_func_hex::val_str(String *str) } null_value=0; tmp_value.length(res->length()*2); + tmp_value.set_charset(&my_charset_latin1); octet2hex((char*) tmp_value.ptr(), res->ptr(), res->length()); return &tmp_value; diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h index df794ecaaf4..4461373f7b3 100644 --- a/sql/item_strfunc.h +++ b/sql/item_strfunc.h @@ -622,18 +622,18 @@ public: }; -class Item_func_hex :public Item_str_func +class Item_func_hex :public Item_str_ascii_func { String tmp_value; public: - Item_func_hex(Item *a) :Item_str_func(a) {} + Item_func_hex(Item *a) :Item_str_ascii_func(a) {} const char *func_name() const { return "hex"; } - String *val_str(String *); + String *val_str_ascii(String *); void fix_length_and_dec() { collation.set(default_charset()); decimals=0; - max_length=args[0]->max_length*2*collation.collation->mbmaxlen; + fix_char_length(args[0]->max_length * 2); } }; -- cgit v1.2.1 From 41caa7f4835bbcf45e4cdc6f5f0b17f1c8b088a2 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Thu, 19 Aug 2010 11:33:37 +0200 Subject: Bug #56085 Embedded server tests fails with assert in check_if_table_exists() This assert was triggered when the server tried to load plugins while running in embedded server mode. In embedded server mode, check_if_table_exists() was used to check if mysql.plugin existed so that ER_NO_SUCH_TABLE could be silently ignored. The problem was that this check was done without acquiring a metadata lock on mysql.plugin first. This triggered the assert. This patch fixes the problem by removing the call to check_if_table_exists() from plugin_load(). Instead an error handler which traps ER_NO_SUCH_TABLE is installed before trying to open mysql.plugin when running in embedded server mode. No test coverage added since this assert was triggered by existing tests running in embedded server mode. sql/sql_base.cc: Renamed Prelock_error_handler to No_such_table_error_handler and moved the declaration to sql_base.h to make it usable in plugin_load(). sql/sql_base.h: Renamed Prelock_error_handler to No_such_table_error_handler and moved the declaration to sql_base.h to make it usable in plugin_load(). sql/sql_plugin.cc: Removed call to check_if_table_exists() used to check for mysql.plugin in plugin_load() for embedded server. Instead install error handler which traps ER_NO_SUCH_TABLE during open_and_lock_tables(). --- sql/sql_base.cc | 49 ++++++++++--------------------------------------- sql/sql_base.h | 30 ++++++++++++++++++++++++++++++ sql/sql_plugin.cc | 16 +++++++++++----- 3 files changed, 51 insertions(+), 44 deletions(-) (limited to 'sql') diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 6ca7a0a771a..d69644dcf17 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -59,42 +59,13 @@ #endif -/** - This internal handler is used to trap internally - errors that can occur when executing open table - during the prelocking phase. -*/ -class Prelock_error_handler : public Internal_error_handler -{ -public: - Prelock_error_handler() - : m_handled_errors(0), m_unhandled_errors(0) - {} - - virtual ~Prelock_error_handler() {} - - virtual bool handle_condition(THD *thd, - uint sql_errno, - const char* sqlstate, - MYSQL_ERROR::enum_warning_level level, - const char* msg, - MYSQL_ERROR ** cond_hdl); - - bool safely_trapped_errors(); - -private: - int m_handled_errors; - int m_unhandled_errors; -}; - - bool -Prelock_error_handler::handle_condition(THD *, - uint sql_errno, - const char*, - MYSQL_ERROR::enum_warning_level, - const char*, - MYSQL_ERROR ** cond_hdl) +No_such_table_error_handler::handle_condition(THD *, + uint sql_errno, + const char*, + MYSQL_ERROR::enum_warning_level, + const char*, + MYSQL_ERROR ** cond_hdl) { *cond_hdl= NULL; if (sql_errno == ER_NO_SUCH_TABLE) @@ -108,7 +79,7 @@ Prelock_error_handler::handle_condition(THD *, } -bool Prelock_error_handler::safely_trapped_errors() +bool No_such_table_error_handler::safely_trapped_errors() { /* If m_unhandled_errors != 0, something else, unanticipated, happened, @@ -4353,11 +4324,11 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, The real failure will occur when/if a statement attempts to use that table. */ - Prelock_error_handler prelock_handler; - thd->push_internal_handler(& prelock_handler); + No_such_table_error_handler no_such_table_handler; + thd->push_internal_handler(&no_such_table_handler); error= open_table(thd, tables, new_frm_mem, ot_ctx); thd->pop_internal_handler(); - safe_to_ignore_table= prelock_handler.safely_trapped_errors(); + safe_to_ignore_table= no_such_table_handler.safely_trapped_errors(); } else error= open_table(thd, tables, new_frm_mem, ot_ctx); diff --git a/sql/sql_base.h b/sql/sql_base.h index 7a6f18545ad..ab11e939a50 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -526,4 +526,34 @@ private: }; +/** + This internal handler is used to trap ER_NO_SUCH_TABLE. +*/ + +class No_such_table_error_handler : public Internal_error_handler +{ +public: + No_such_table_error_handler() + : m_handled_errors(0), m_unhandled_errors(0) + {} + + bool handle_condition(THD *thd, + uint sql_errno, + const char* sqlstate, + MYSQL_ERROR::enum_warning_level level, + const char* msg, + MYSQL_ERROR ** cond_hdl); + + /** + Returns TRUE if one or more ER_NO_SUCH_TABLE errors have been + trapped and no other errors have been seen. FALSE otherwise. + */ + bool safely_trapped_errors(); + +private: + int m_handled_errors; + int m_unhandled_errors; +}; + + #endif /* SQL_BASE_INCLUDED */ diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index e3323260373..0020741b78f 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -1393,8 +1393,9 @@ static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv) READ_RECORD read_record_info; int error; THD *new_thd= &thd; + bool result; #ifdef EMBEDDED_LIBRARY - bool table_exists; + No_such_table_error_handler error_handler; #endif /* EMBEDDED_LIBRARY */ DBUG_ENTER("plugin_load"); @@ -1410,13 +1411,18 @@ static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv) When building an embedded library, if the mysql.plugin table does not exist, we silently ignore the missing table */ - if (check_if_table_exists(new_thd, &tables, &table_exists)) - table_exists= FALSE; - if (!table_exists) + new_thd->push_internal_handler(&error_handler); +#endif /* EMBEDDED_LIBRARY */ + + result= open_and_lock_tables(new_thd, &tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT); + +#ifdef EMBEDDED_LIBRARY + new_thd->pop_internal_handler(); + if (error_handler.safely_trapped_errors()) goto end; #endif /* EMBEDDED_LIBRARY */ - if (open_and_lock_tables(new_thd, &tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) + if (result) { DBUG_PRINT("error",("Can't open plugin table")); sql_print_error("Can't open the mysql.plugin table. Please " -- cgit v1.2.1 From 7f98714247d601c353779d8615a5624cf00b5e6a Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Thu, 19 Aug 2010 15:55:35 +0400 Subject: Bug#54916 GROUP_CONCAT + IFNULL truncates output Problem: a few functions did not calculate their max_length correctly. This is an after-fix for WL#2649 Number-to-string conversions". Fix: changing the buggy functions to calculate max_length using fix_char_length() introduced in WL#2649, instead of setting max_length directly mysql-test/include/ctype_numconv.inc Adding new tests mysql-test/r/ctype_binary.result Adding new tests mysql-test/r/ctype_cp1251.result Adding new tests mysql-test/r/ctype_latin1.result Adding new tests mysql-test/r/ctype_ucs.result Adding new tests mysql-test/r/ctype_utf8.result Adding new tests mysql-test/t/ctype_utf8.test Including ctype_numconv sql/item.h - Introducing new method fix_char_length_ulonglong(), for the cases when length is potentially greater than UINT_MAX32. This method removes a few instances of duplicate code, e.g. in item_strfunc.cc. - Setting collation in Item_copy properly. This change fixes wrong metadata on client side in some cases, when "binary" instead of the real character set was reported. sql/item_cmpfunc.cc - Using fix_char_length() and max_char_length() methods, instead of direct access to max_length, to calculate item length properly. - Moving count_only_length() in COALESCE after agg_arg_charsets_for_string_result(). The old order was incorrect and led to wrong length calucation in case of multi-byte character sets. sql/item_func.cc Fixing that count_only_length() didn't work properly for multi-byte character sets. Using fix_char_length() and max_char_length() instead of direct access to max_length. sql/item_strfunc.cc - Using fix_char_length(), fix_char_length_ulonglong(), max_char_length() instead of direct access to max_length. - Removing wierd condition: "if (collation.collation->mbmaxlen > 0)", which is never FALSE. --- sql/item.h | 15 ++++- sql/item_cmpfunc.cc | 26 +++++--- sql/item_func.cc | 10 +-- sql/item_strfunc.cc | 180 +++++++++++++++++++--------------------------------- 4 files changed, 102 insertions(+), 129 deletions(-) (limited to 'sql') diff --git a/sql/item.h b/sql/item.h index 3af33fd2d31..8e8199ecac8 100644 --- a/sql/item.h +++ b/sql/item.h @@ -547,7 +547,7 @@ public: @see Query_arena::free_list */ Item *next; - uint32 max_length; + uint32 max_length; /* Maximum length, in bytes */ uint name_length; /* Length of name */ int8 marker; uint8 decimals; @@ -1221,6 +1221,18 @@ public: max_length= char_to_byte_length_safe(max_char_length_arg, collation.collation->mbmaxlen); } + void fix_char_length_ulonglong(ulonglong max_char_length_arg) + { + ulonglong max_result_length= max_char_length_arg * + collation.collation->mbmaxlen; + if (max_result_length >= MAX_BLOB_WIDTH) + { + max_length= MAX_BLOB_WIDTH; + maybe_null= 1; + } + else + max_length= max_result_length; + } void fix_length_and_charset_datetime(uint32 max_char_length_arg) { collation.set(&my_charset_numeric, DERIVATION_NUMERIC, MY_REPERTOIRE_ASCII); @@ -2825,6 +2837,7 @@ protected: cached_result_type= item->result_type(); unsigned_flag= item->unsigned_flag; fixed= item->fixed; + collation.set(item->collation); } public: diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index c18b79371df..641d3726aca 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -2374,6 +2374,7 @@ void Item_func_between::print(String *str, enum_query_type query_type) void Item_func_ifnull::fix_length_and_dec() { + uint32 char_length; agg_result_type(&hybrid_type, args, 2); maybe_null=args[1]->maybe_null; decimals= max(args[0]->decimals, args[1]->decimals); @@ -2381,20 +2382,21 @@ Item_func_ifnull::fix_length_and_dec() if (hybrid_type == DECIMAL_RESULT || hybrid_type == INT_RESULT) { - int len0= args[0]->max_length - args[0]->decimals + int len0= args[0]->max_char_length() - args[0]->decimals - (args[0]->unsigned_flag ? 0 : 1); - int len1= args[1]->max_length - args[1]->decimals + int len1= args[1]->max_char_length() - args[1]->decimals - (args[1]->unsigned_flag ? 0 : 1); - max_length= max(len0, len1) + decimals + (unsigned_flag ? 0 : 1); + char_length= max(len0, len1) + decimals + (unsigned_flag ? 0 : 1); } else - max_length= max(args[0]->max_length, args[1]->max_length); + char_length= max(args[0]->max_char_length(), args[1]->max_char_length()); switch (hybrid_type) { case STRING_RESULT: - agg_arg_charsets_for_comparison(collation, args, arg_count); + if (agg_arg_charsets_for_comparison(collation, args, arg_count)) + return; break; case DECIMAL_RESULT: case REAL_RESULT: @@ -2406,6 +2408,7 @@ Item_func_ifnull::fix_length_and_dec() default: DBUG_ASSERT(0); } + fix_char_length(char_length); cached_field_type= agg_field_type(args, 2); } @@ -2579,6 +2582,7 @@ Item_func_if::fix_length_and_dec() cached_field_type= agg_field_type(args + 1, 2); } + uint32 char_length; if ((cached_result_type == DECIMAL_RESULT ) || (cached_result_type == INT_RESULT)) { @@ -2588,10 +2592,11 @@ Item_func_if::fix_length_and_dec() int len2= args[2]->max_length - args[2]->decimals - (args[2]->unsigned_flag ? 0 : 1); - max_length=max(len1, len2) + decimals + (unsigned_flag ? 0 : 1); + char_length= max(len1, len2) + decimals + (unsigned_flag ? 0 : 1); } else - max_length= max(args[1]->max_length, args[2]->max_length); + char_length= max(args[1]->max_char_length(), args[2]->max_char_length()); + fix_char_length(char_length); } @@ -2901,7 +2906,7 @@ bool Item_func_case::fix_fields(THD *thd, Item **ref) void Item_func_case::agg_str_lengths(Item* arg) { - set_if_bigger(max_length, arg->max_length); + fix_char_length(max(max_char_length(), arg->max_char_length())); set_if_bigger(decimals, arg->decimals); unsigned_flag= unsigned_flag && arg->unsigned_flag; } @@ -3129,9 +3134,10 @@ void Item_func_coalesce::fix_length_and_dec() agg_result_type(&hybrid_type, args, arg_count); switch (hybrid_type) { case STRING_RESULT: - count_only_length(); decimals= NOT_FIXED_DEC; - agg_arg_charsets_for_string_result(collation, args, arg_count); + if (agg_arg_charsets_for_string_result(collation, args, arg_count)) + return; + count_only_length(); break; case DECIMAL_RESULT: count_decimal_length(); diff --git a/sql/item_func.cc b/sql/item_func.cc index 833eac6893b..95965668882 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -564,8 +564,9 @@ void Item_func::count_decimal_length() set_if_smaller(unsigned_flag, args[i]->unsigned_flag); } int precision= min(max_int_part + decimals, DECIMAL_MAX_PRECISION); - max_length= my_decimal_precision_to_length_no_truncation(precision, decimals, - unsigned_flag); + fix_char_length(my_decimal_precision_to_length_no_truncation(precision, + decimals, + unsigned_flag)); } @@ -575,13 +576,14 @@ void Item_func::count_decimal_length() void Item_func::count_only_length() { - max_length= 0; + uint32 char_length= 0; unsigned_flag= 0; for (uint i=0 ; i < arg_count ; i++) { - set_if_bigger(max_length, args[i]->max_length); + set_if_bigger(char_length, args[i]->max_char_length()); set_if_bigger(unsigned_flag, args[i]->unsigned_flag); } + fix_char_length(char_length); } diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 808cf92f0bf..1a772950303 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -605,27 +605,15 @@ null: void Item_func_concat::fix_length_and_dec() { - ulonglong max_result_length= 0; + ulonglong char_length= 0; if (agg_arg_charsets_for_string_result(collation, args, arg_count)) return; for (uint i=0 ; i < arg_count ; i++) - { - if (args[i]->collation.collation->mbmaxlen != collation.collation->mbmaxlen) - max_result_length+= (args[i]->max_length / - args[i]->collation.collation->mbmaxlen) * - collation.collation->mbmaxlen; - else - max_result_length+= args[i]->max_length; - } + char_length+= args[i]->max_char_length(); - if (max_result_length >= MAX_BLOB_WIDTH) - { - max_result_length= MAX_BLOB_WIDTH; - maybe_null= 1; - } - max_length= (ulong) max_result_length; + fix_char_length_ulonglong(char_length); } /** @@ -962,7 +950,7 @@ null: void Item_func_concat_ws::fix_length_and_dec() { - ulonglong max_result_length; + ulonglong char_length; if (agg_arg_charsets_for_string_result(collation, args, arg_count)) return; @@ -972,16 +960,11 @@ void Item_func_concat_ws::fix_length_and_dec() it is done on parser level in sql_yacc.yy so, (arg_count - 2) is safe here. */ - max_result_length= (ulonglong) args[0]->max_length * (arg_count - 2); + char_length= (ulonglong) args[0]->max_char_length() * (arg_count - 2); for (uint i=1 ; i < arg_count ; i++) - max_result_length+=args[i]->max_length; + char_length+= args[i]->max_char_length(); - if (max_result_length >= MAX_BLOB_WIDTH) - { - max_result_length= MAX_BLOB_WIDTH; - maybe_null= 1; - } - max_length= (ulong) max_result_length; + fix_char_length_ulonglong(char_length); } @@ -1036,6 +1019,7 @@ String *Item_func_reverse::val_str(String *str) void Item_func_reverse::fix_length_and_dec() { agg_arg_charsets_for_string_result(collation, args, 1); + DBUG_ASSERT(collation.collation != NULL); fix_char_length(args[0]->max_char_length()); } @@ -1165,22 +1149,17 @@ null: void Item_func_replace::fix_length_and_dec() { - ulonglong max_result_length= args[0]->max_length; - int diff=(int) (args[2]->max_length - args[1]->max_length); - if (diff > 0 && args[1]->max_length) + ulonglong char_length= (ulonglong) args[0]->max_char_length(); + int diff=(int) (args[2]->max_char_length() - args[1]->max_char_length()); + if (diff > 0 && args[1]->max_char_length()) { // Calculate of maxreplaces - ulonglong max_substrs= max_result_length/args[1]->max_length; - max_result_length+= max_substrs * (uint) diff; - } - if (max_result_length >= MAX_BLOB_WIDTH) - { - max_result_length= MAX_BLOB_WIDTH; - maybe_null= 1; + ulonglong max_substrs= char_length / args[1]->max_char_length(); + char_length+= max_substrs * (uint) diff; } - max_length= (ulong) max_result_length; - + if (agg_arg_charsets_for_comparison(collation, args, 3)) return; + fix_char_length_ulonglong(char_length); } @@ -1235,19 +1214,14 @@ null: void Item_func_insert::fix_length_and_dec() { - ulonglong max_result_length; + ulonglong char_length; // Handle character set for args[0] and args[3]. if (agg_arg_charsets_for_string_result(collation, args, 2, 3)) return; - max_result_length= ((ulonglong) args[0]->max_length+ - (ulonglong) args[3]->max_length); - if (max_result_length >= MAX_BLOB_WIDTH) - { - max_result_length= MAX_BLOB_WIDTH; - maybe_null= 1; - } - max_length= (ulong) max_result_length; + char_length= ((ulonglong) args[0]->max_char_length() + + (ulonglong) args[3]->max_char_length()); + fix_char_length_ulonglong(char_length); } @@ -1287,17 +1261,19 @@ String *Item_str_conv::val_str(String *str) void Item_func_lcase::fix_length_and_dec() { agg_arg_charsets_for_string_result(collation, args, 1); + DBUG_ASSERT(collation.collation != NULL); multiply= collation.collation->casedn_multiply; converter= collation.collation->cset->casedn; - max_length= args[0]->max_length * multiply; + fix_char_length_ulonglong((ulonglong) args[0]->max_char_length() * multiply); } void Item_func_ucase::fix_length_and_dec() { agg_arg_charsets_for_string_result(collation, args, 1); + DBUG_ASSERT(collation.collation != NULL); multiply= collation.collation->caseup_multiply; converter= collation.collation->cset->caseup; - max_length= args[0]->max_length * multiply; + fix_char_length_ulonglong((ulonglong) args[0]->max_char_length() * multiply); } @@ -1328,21 +1304,23 @@ String *Item_func_left::val_str(String *str) void Item_str_func::left_right_max_length() { - max_length=args[0]->max_length; + uint32 char_length= args[0]->max_char_length(); if (args[1]->const_item()) { - int length=(int) args[1]->val_int()*collation.collation->mbmaxlen; + int length= (int) args[1]->val_int(); if (length <= 0) - max_length=0; + char_length=0; else - set_if_smaller(max_length,(uint) length); + set_if_smaller(char_length, (uint) length); } + fix_char_length(char_length); } void Item_func_left::fix_length_and_dec() { agg_arg_charsets_for_string_result(collation, args, 1); + DBUG_ASSERT(collation.collation != NULL); left_right_max_length(); } @@ -1376,6 +1354,7 @@ String *Item_func_right::val_str(String *str) void Item_func_right::fix_length_and_dec() { agg_arg_charsets_for_string_result(collation, args, 1); + DBUG_ASSERT(collation.collation != NULL); left_right_max_length(); } @@ -1432,6 +1411,7 @@ void Item_func_substr::fix_length_and_dec() max_length=args[0]->max_length; agg_arg_charsets_for_string_result(collation, args, 1); + DBUG_ASSERT(collation.collation != NULL); if (args[1]->const_item()) { int32 start= (int32) args[1]->val_int(); @@ -1454,10 +1434,9 @@ void Item_func_substr::fix_length_and_dec() void Item_func_substr_index::fix_length_and_dec() { - max_length= args[0]->max_length; - if (agg_arg_charsets_for_comparison(collation, args, 2)) return; + fix_char_length(args[0]->max_char_length()); } @@ -1783,10 +1762,10 @@ String *Item_func_trim::val_str(String *str) void Item_func_trim::fix_length_and_dec() { - max_length= args[0]->max_length; if (arg_count == 1) { agg_arg_charsets_for_string_result(collation, args, 1); + DBUG_ASSERT(collation.collation != NULL); remove.set_charset(collation.collation); remove.set_ascii(" ",1); } @@ -1797,6 +1776,7 @@ void Item_func_trim::fix_length_and_dec() if (agg_arg_charsets_for_comparison(collation, &args[1], 2, -1)) return; } + fix_char_length(args[0]->max_char_length()); } void Item_func_trim::print(String *str, enum_query_type query_type) @@ -2072,9 +2052,11 @@ bool Item_func_current_user::fix_fields(THD *thd, Item **ref) void Item_func_soundex::fix_length_and_dec() { + uint32 char_length= args[0]->max_char_length(); agg_arg_charsets_for_string_result(collation, args, 1); - max_length=args[0]->max_length; - set_if_bigger(max_length, 4 * collation.collation->mbminlen); + DBUG_ASSERT(collation.collation != NULL); + set_if_bigger(char_length, 4); + fix_char_length(char_length); tmp_value.set_charset(collation.collation); } @@ -2251,11 +2233,10 @@ MY_LOCALE *Item_func_format::get_locale(Item *item) void Item_func_format::fix_length_and_dec() { - uint char_length= args[0]->max_length/args[0]->collation.collation->mbmaxlen; - uint max_sep_count= char_length/3 + (decimals ? 1 : 0) + /*sign*/1; + uint32 char_length= args[0]->max_char_length(); + uint32 max_sep_count= (char_length / 3) + (decimals ? 1 : 0) + /*sign*/1; collation.set(default_charset()); - max_length= (char_length + max_sep_count + decimals) * - collation.collation->mbmaxlen; + fix_char_length(char_length + max_sep_count + decimals); if (arg_count == 3) locale= args[2]->basic_const_item() ? get_locale(args[2]) : NULL; else @@ -2375,7 +2356,7 @@ void Item_func_format::print(String *str, enum_query_type query_type) void Item_func_elt::fix_length_and_dec() { - max_length=0; + uint32 char_length= 0; decimals=0; if (agg_arg_charsets_for_string_result(collation, args + 1, arg_count - 1)) @@ -2383,9 +2364,10 @@ void Item_func_elt::fix_length_and_dec() for (uint i= 1 ; i < arg_count ; i++) { - set_if_bigger(max_length,args[i]->max_length); + set_if_bigger(char_length, args[i]->max_char_length()); set_if_bigger(decimals,args[i]->decimals); } + fix_char_length(char_length); maybe_null=1; // NULL if wrong first arg } @@ -2443,14 +2425,14 @@ void Item_func_make_set::split_sum_func(THD *thd, Item **ref_pointer_array, void Item_func_make_set::fix_length_and_dec() { - max_length=arg_count-1; + uint32 char_length= arg_count - 1; /* Separators */ if (agg_arg_charsets_for_string_result(collation, args, arg_count)) return; for (uint i=0 ; i < arg_count ; i++) - max_length+=args[i]->max_length; - + char_length+= args[i]->max_char_length(); + fix_char_length(char_length); used_tables_cache|= item->used_tables(); not_null_tables_cache&= item->not_null_tables(); const_item_cache&= item->const_item(); @@ -2616,6 +2598,7 @@ inline String* alloc_buffer(String *res,String *str,String *tmp_value, void Item_func_repeat::fix_length_and_dec() { agg_arg_charsets_for_string_result(collation, args, 1); + DBUG_ASSERT(collation.collation != NULL); if (args[1]->const_item()) { /* must be longlong to avoid truncation */ @@ -2626,13 +2609,8 @@ void Item_func_repeat::fix_length_and_dec() if (count > INT_MAX32) count= INT_MAX32; - ulonglong max_result_length= (ulonglong) args[0]->max_length * count; - if (max_result_length >= MAX_BLOB_WIDTH) - { - max_result_length= MAX_BLOB_WIDTH; - maybe_null= 1; - } - max_length= (ulong) max_result_length; + ulonglong char_length= (ulonglong) args[0]->max_char_length() * count; + fix_char_length_ulonglong(char_length); } else { @@ -2703,26 +2681,13 @@ void Item_func_rpad::fix_length_and_dec() return; if (args[1]->const_item()) { - ulonglong length= 0; - - if (collation.collation->mbmaxlen > 0) - { - ulonglong temp= (ulonglong) args[1]->val_int(); - - /* Assumes that the maximum length of a String is < INT_MAX32. */ - /* Set here so that rest of code sees out-of-bound value as such. */ - if (temp > INT_MAX32) - temp = INT_MAX32; - - length= temp * collation.collation->mbmaxlen; - } - - if (length >= MAX_BLOB_WIDTH) - { - length= MAX_BLOB_WIDTH; - maybe_null= 1; - } - max_length= (ulong) length; + ulonglong char_length= (ulonglong) args[1]->val_int(); + DBUG_ASSERT(collation.collation->mbmaxlen > 0); + /* Assumes that the maximum length of a String is < INT_MAX32. */ + /* Set here so that rest of code sees out-of-bound value as such. */ + if (char_length > INT_MAX32) + char_length= INT_MAX32; + fix_char_length_ulonglong(char_length); } else { @@ -2806,26 +2771,13 @@ void Item_func_lpad::fix_length_and_dec() if (args[1]->const_item()) { - ulonglong length= 0; - - if (collation.collation->mbmaxlen > 0) - { - ulonglong temp= (ulonglong) args[1]->val_int(); - - /* Assumes that the maximum length of a String is < INT_MAX32. */ - /* Set here so that rest of code sees out-of-bound value as such. */ - if (temp > INT_MAX32) - temp= INT_MAX32; - - length= temp * collation.collation->mbmaxlen; - } - - if (length >= MAX_BLOB_WIDTH) - { - length= MAX_BLOB_WIDTH; - maybe_null= 1; - } - max_length= (ulong) length; + ulonglong char_length= (ulonglong) args[1]->val_int(); + DBUG_ASSERT(collation.collation->mbmaxlen > 0); + /* Assumes that the maximum length of a String is < INT_MAX32. */ + /* Set here so that rest of code sees out-of-bound value as such. */ + if (char_length > INT_MAX32) + char_length= INT_MAX32; + fix_char_length_ulonglong(char_length); } else { @@ -3309,8 +3261,8 @@ String* Item_func_export_set::val_str(String* str) void Item_func_export_set::fix_length_and_dec() { - uint length=max(args[1]->max_length,args[2]->max_length); - uint sep_length=(arg_count > 3 ? args[3]->max_length : 1); + uint32 length= max(args[1]->max_char_length(), args[2]->max_char_length()); + uint32 sep_length= (arg_count > 3 ? args[3]->max_char_length() : 1); if (agg_arg_charsets_for_string_result(collation, args + 1, min(4, arg_count) - 1)) -- cgit v1.2.1 From ac6026ce2774222b4607fcad4e36bbd5c25ff60a Mon Sep 17 00:00:00 2001 From: Alfranio Correia Date: Fri, 20 Aug 2010 03:59:58 +0100 Subject: BUG#53452 Inconsistent behavior of binlog_direct_non_transactional_updates with temp table This patch introduces two key changes in the replication's behavior. Firstly, it reverts part of BUG#51894 which puts any update to temporary tables into the trx-cache. Now, updates to temporary tables are handled according to the type of their engines as a regular table. Secondly, an unsafe mixed statement, (i.e. a statement that access transactional table as well non-transactional or temporary table, and writes to any of them), are written into the trx-cache in order to minimize errors in the execution when the statement logging format is in use. Such changes has a direct impact on which statements are classified as unsafe statements and thus part of BUG#53259 is reverted. --- sql/log.cc | 2 +- sql/log_event.cc | 51 ++++---- sql/mysqld.cc | 2 + sql/sql_base.cc | 9 +- sql/sql_base.h | 2 +- sql/sql_class.cc | 128 ++------------------ sql/sql_insert.cc | 4 +- sql/sql_lex.cc | 149 ++++++++++++++++++++++- sql/sql_lex.h | 203 ++++++++++++++++++++++++++------ sql/sql_table.cc | 347 +++++++++++++++++++++++++++++++----------------------- sql/sql_table.h | 3 +- 11 files changed, 569 insertions(+), 331 deletions(-) (limited to 'sql') diff --git a/sql/log.cc b/sql/log.cc index 86f8100be07..5ca22d154b1 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -4272,7 +4272,7 @@ bool use_trans_cache(const THD* thd, bool is_transactional) (binlog_cache_mngr*) thd_get_ha_data(thd, binlog_hton); return - ((thd->variables.binlog_format != BINLOG_FORMAT_STMT || + ((thd->is_current_stmt_binlog_format_row() || thd->variables.binlog_direct_non_trans_update) ? is_transactional : (is_transactional || !cache_mngr->trx_cache.empty())); } diff --git a/sql/log_event.cc b/sql/log_event.cc index d224f555cf2..c1f7836e08a 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -678,11 +678,11 @@ Log_event::Log_event(THD* thd_arg, uint16 flags_arg, bool using_trans) { server_id= thd->server_id; when= thd->start_time; - cache_type= ((using_trans || stmt_has_updated_trans_table(thd) || - (thd->lex->stmt_accessed_temp_table() && - trans_has_updated_trans_table(thd))) - ? Log_event::EVENT_TRANSACTIONAL_CACHE : - Log_event::EVENT_STMT_CACHE); + + if (using_trans) + cache_type= Log_event::EVENT_TRANSACTIONAL_CACHE; + else + cache_type= Log_event::EVENT_STMT_CACHE; } /** @@ -2520,21 +2520,18 @@ Query_log_event::Query_log_event(THD* thd_arg, const char* query_arg, LEX *lex= thd->lex; /* - TRUE defines that either a trx-cache or stmt-cache must be used - and wrapped by a BEGIN...COMMIT. Otherwise, the statement will - be written directly to the binary log without being wrapped by - a BEGIN...COMMIT. + Defines that the statement will be written directly to the binary log + without being wrapped by a BEGIN...COMMIT. Otherwise, the statement + will be written to either the trx-cache or stmt-cache. - Note that a cache will not be used if the parameter direct is - TRUE. + Note that a cache will not be used if the parameter direct is TRUE. */ bool use_cache= FALSE; /* - TRUE defines that the trx-cache must be used and by consequence - the use_cache is TRUE. + TRUE defines that the trx-cache must be used and by consequence the + use_cache is TRUE. - Note that a cache will not be used if the parameter direct is - TRUE. + Note that a cache will not be used if the parameter direct is TRUE. */ bool trx_cache= FALSE; cache_type= Log_event::EVENT_INVALID_CACHE; @@ -2542,16 +2539,14 @@ Query_log_event::Query_log_event(THD* thd_arg, const char* query_arg, switch (lex->sql_command) { case SQLCOM_DROP_TABLE: - use_cache= trx_cache= (lex->drop_temporary && - thd->in_multi_stmt_transaction_mode()); + use_cache= (lex->drop_temporary && thd->in_multi_stmt_transaction_mode()); break; case SQLCOM_CREATE_TABLE: - use_cache= trx_cache= - ((lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) && - thd->in_multi_stmt_transaction_mode()) || - (lex->select_lex.item_list.elements && + trx_cache= (lex->select_lex.item_list.elements && thd->is_current_stmt_binlog_format_row()); + use_cache= ((lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) && + thd->in_multi_stmt_transaction_mode()) || trx_cache; break; case SQLCOM_SET_OPTION: use_cache= trx_cache= (lex->autocommit ? FALSE : TRUE); @@ -2570,14 +2565,14 @@ Query_log_event::Query_log_event(THD* thd_arg, const char* query_arg, { cache_type= Log_event::EVENT_NO_CACHE; } + else if (using_trans || trx_cache || stmt_has_updated_trans_table(thd) || + thd->lex->is_mixed_stmt_unsafe(thd->in_multi_stmt_transaction_mode(), + thd->variables.binlog_direct_non_trans_update, + trans_has_updated_trans_table(thd), + thd->tx_isolation)) + cache_type= Log_event::EVENT_TRANSACTIONAL_CACHE; else - { - cache_type= ((using_trans || stmt_has_updated_trans_table(thd) || trx_cache || - (thd->lex->stmt_accessed_temp_table() && - trans_has_updated_trans_table(thd))) - ? Log_event::EVENT_TRANSACTIONAL_CACHE : - Log_event::EVENT_STMT_CACHE); - } + cache_type= Log_event::EVENT_STMT_CACHE; DBUG_ASSERT(cache_type != Log_event::EVENT_INVALID_CACHE); DBUG_PRINT("info",("Query_log_event has flags2: %lu sql_mode: %lu", (ulong) flags2, sql_mode)); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index eaa3e27cf29..a0eca1b78ff 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -4513,6 +4513,8 @@ int mysqld_main(int argc, char **argv) init_status_vars(); if (opt_bootstrap) /* If running with bootstrap, do not start replication. */ opt_skip_slave_start= 1; + + binlog_unsafe_map_init(); /* init_slave() must be called after the thread keys are created. Some parts of the code (e.g. SHOW STATUS LIKE 'slave_running' and other diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 243a551d24b..8be29eb12f5 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2042,13 +2042,17 @@ TABLE *find_temporary_table(THD *thd, TABLE_LIST *table_list) thd->temporary_tables list, it's impossible to tell here whether we're dealing with an internal or a user temporary table. + If is_trans is not null, we return the type of the table: + either transactional (e.g. innodb) as TRUE or non-transactional + (e.g. myisam) as FALSE. + @retval 0 the table was found and dropped successfully. @retval 1 the table was not found in the list of temporary tables of this thread @retval -1 the table is in use by a outer query */ -int drop_temporary_table(THD *thd, TABLE_LIST *table_list) +int drop_temporary_table(THD *thd, TABLE_LIST *table_list, bool *is_trans) { TABLE *table; DBUG_ENTER("drop_temporary_table"); @@ -2065,6 +2069,9 @@ int drop_temporary_table(THD *thd, TABLE_LIST *table_list) DBUG_RETURN(-1); } + if (is_trans != NULL) + *is_trans= table->file->has_transactions(); + /* If LOCK TABLES list is not empty and contains this table, unlock the table and remove the table from this list. diff --git a/sql/sql_base.h b/sql/sql_base.h index 2b08d8fa40e..e83c3cf8097 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -220,7 +220,7 @@ bool close_thread_table(THD *thd, TABLE **table_ptr); bool close_temporary_tables(THD *thd); TABLE_LIST *unique_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list, bool check_alias); -int drop_temporary_table(THD *thd, TABLE_LIST *table_list); +int drop_temporary_table(THD *thd, TABLE_LIST *table_list, bool *is_trans); void close_temporary_table(THD *thd, TABLE *table, bool free_share, bool delete_table); void close_temporary(TABLE *table, bool free_share, bool delete_table); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index eff7d8c1d90..bde56777d52 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -3775,124 +3775,16 @@ int THD::decide_logging_format(TABLE_LIST *tables) int error= 0; int unsafe_flags; - /* - Classify a statement as unsafe when there is a mixed statement and an - on-going transaction at any point of the execution if: - - 1. The mixed statement is about to update a transactional table and - a non-transactional table. - - 2. The mixed statement is about to update a temporary transactional - table and a non-transactional table. - - 3. The mixed statement is about to update a transactional table and - read from a non-transactional table. - - 4. The mixed statement is about to update a temporary transactional - table and read from a non-transactional table. - - 5. The mixed statement is about to update a non-transactional table - and read from a transactional table when the isolation level is - lower than repeatable read. - - After updating a transactional table if: - - 6. The mixed statement is about to update a non-transactional table - and read from a temporary transactional table. - - 7. The mixed statement is about to update a non-transactional table - and read from a temporary transactional table. - - 8. The mixed statement is about to update a non-transactionala table - and read from a temporary non-transactional table. - - 9. The mixed statement is about to update a temporary non-transactional - table and update a non-transactional table. - - 10. The mixed statement is about to update a temporary non-transactional - table and read from a non-transactional table. - - 11. A statement is about to update a non-transactional table and the - option variables.binlog_direct_non_trans_update is OFF. - - The reason for this is that locks acquired may not protected a concurrent - transaction of interfering in the current execution and by consequence in - the result. In particular, if there is an on-going transaction and a - transactional table was already updated, a temporary table must be written - to the binary log in the boundaries of the on-going transaction and as - such we artificially classify them as transactional. - */ - if (in_multi_stmt_transaction_mode()) - { - my_bool mixed_unsafe= FALSE; - my_bool non_trans_unsafe= FALSE; - - /* Case 1. */ - if (lex->stmt_accessed_table(LEX::STMT_WRITES_TRANS_TABLE) && - lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE)) - mixed_unsafe= TRUE; - /* Case 2. */ - else if (lex->stmt_accessed_table(LEX::STMT_WRITES_TEMP_TRANS_TABLE) && - lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE)) - mixed_unsafe= TRUE; - /* Case 3. */ - else if (lex->stmt_accessed_table(LEX::STMT_WRITES_TRANS_TABLE) && - lex->stmt_accessed_table(LEX::STMT_READS_NON_TRANS_TABLE)) - mixed_unsafe= TRUE; - /* Case 4. */ - else if (lex->stmt_accessed_table(LEX::STMT_WRITES_TEMP_TRANS_TABLE) && - lex->stmt_accessed_table(LEX::STMT_READS_NON_TRANS_TABLE)) - mixed_unsafe= TRUE; - /* Case 5. */ - else if (lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE) && - lex->stmt_accessed_table(LEX::STMT_READS_TRANS_TABLE) && - tx_isolation < ISO_REPEATABLE_READ) - /* - By default, InnoDB operates in REPEATABLE READ and with the option - --innodb-locks-unsafe-for-binlog disabled. In this case, InnoDB uses - next-key locks for searches and index scans, which prevents phantom - rows. - - This is scenario is safe for Innodb. However, there are no means to - transparently get this information. Therefore, we need to improve this - and change the storage engines to report somehow when an execution is - safe under an isolation level & binary logging format. - */ - mixed_unsafe= TRUE; - - if (trans_has_updated_trans_table(this)) - { - /* Case 6. */ - if (lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE) && - lex->stmt_accessed_table(LEX::STMT_READS_TRANS_TABLE)) - mixed_unsafe= TRUE; - /* Case 7. */ - else if (lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE) && - lex->stmt_accessed_table(LEX::STMT_READS_TEMP_TRANS_TABLE)) - mixed_unsafe= TRUE; - /* Case 8. */ - else if (lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE) && - lex->stmt_accessed_table(LEX::STMT_READS_TEMP_NON_TRANS_TABLE)) - mixed_unsafe= TRUE; - /* Case 9. */ - else if (lex->stmt_accessed_table(LEX::STMT_WRITES_TEMP_NON_TRANS_TABLE) && - lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE)) - mixed_unsafe= TRUE; - /* Case 10. */ - else if (lex->stmt_accessed_table(LEX::STMT_WRITES_TEMP_NON_TRANS_TABLE) && - lex->stmt_accessed_table(LEX::STMT_READS_NON_TRANS_TABLE)) - mixed_unsafe= TRUE; - /* Case 11. */ - else if (!variables.binlog_direct_non_trans_update && - lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE)) - non_trans_unsafe= TRUE; - } - - if (mixed_unsafe) - lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_MIXED_STATEMENT); - else if (non_trans_unsafe) - lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_NONTRANS_AFTER_TRANS); - } + bool multi_stmt_trans= in_multi_stmt_transaction_mode(); + bool trans_table= trans_has_updated_trans_table(this); + bool binlog_direct= variables.binlog_direct_non_trans_update; + + if (lex->is_mixed_stmt_unsafe(multi_stmt_trans, binlog_direct, + trans_table, tx_isolation)) + lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_MIXED_STATEMENT); + else if (multi_stmt_trans && trans_table && !binlog_direct && + lex->stmt_accessed_table(LEX::STMT_WRITES_NON_TRANS_TABLE)) + lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_NONTRANS_AFTER_TRANS); /* If more than one engine is involved in the statement and at diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 2314dbbeaee..79a5cfdbbe2 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3579,7 +3579,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, if (!mysql_create_table_no_lock(thd, create_table->db, create_table->table_name, create_info, alter_info, 0, - select_field_count)) + select_field_count, NULL)) { DBUG_EXECUTE_IF("sleep_create_select_before_open", my_sleep(6000000);); @@ -3611,7 +3611,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info, it preparable for open. But let us do close_temporary_table() here just in case. */ - drop_temporary_table(thd, create_table); + drop_temporary_table(thd, create_table, NULL); } else table= create_table->table; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 57417acdd93..ba508783133 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -2298,7 +2298,7 @@ void Query_tables_list::reset_query_tables_list(bool init) sroutines_list_own_last= sroutines_list.next; sroutines_list_own_elements= 0; binlog_stmt_flags= 0; - stmt_accessed_table_flag= 0; + stmt_accessed_table_flag= 0; } @@ -3143,3 +3143,150 @@ bool LEX::is_partition_management() const alter_info.flags == ALTER_REORGANIZE_PARTITION)); } + +#ifdef MYSQL_SERVER +uint binlog_unsafe_map[256]; + +#define UNSAFE(a, b, c) \ + { \ + DBUG_PRINT("unsafe_mixed_statement", ("SETTING BASE VALUES: %s, %s, %02X\n", \ + LEX::stmt_accessed_table_string(a), \ + LEX::stmt_accessed_table_string(b), \ + c)); \ + unsafe_mixed_statement(a, b, c); \ + } + +/* + Sets the combination given by "a" and "b" and automatically combinations + given by other types of access, i.e. 2^(8 - 2), as unsafe. + + It may happen a colision when automatically defining a combination as unsafe. + For that reason, a combination has its unsafe condition redefined only when + the new_condition is greater then the old. For instance, + + . (BINLOG_DIRECT_ON & TRX_CACHE_NOT_EMPTY) is never overwritten by + . (BINLOG_DIRECT_ON | BINLOG_DIRECT_OFF). +*/ +void unsafe_mixed_statement(LEX::enum_stmt_accessed_table a, + LEX::enum_stmt_accessed_table b, uint condition) +{ + int type= 0; + int index= (1U << a) | (1U << b); + + + for (type= 0; type < 256; type++) + { + if ((type & index) == index) + { + binlog_unsafe_map[type] |= condition; + } + } +} +/* + The BINLOG_* AND TRX_CACHE_* values can be combined by using '&' or '|', + which means that both conditions need to be satisfied or any of them is + enough. For example, + + . BINLOG_DIRECT_ON & TRX_CACHE_NOT_EMPTY means that the statment is + unsafe when the option is on and trx-cache is not empty; + + . BINLOG_DIRECT_ON | BINLOG_DIRECT_OFF means the statement is unsafe + in all cases. + + . TRX_CACHE_EMPTY | TRX_CACHE_NOT_EMPTY means the statement is unsafe + in all cases. Similar as above. +*/ +void binlog_unsafe_map_init() +{ + memset((void*) binlog_unsafe_map, 0, sizeof(uint) * 256); + + /* + Classify a statement as unsafe when there is a mixed statement and an + on-going transaction at any point of the execution if: + + 1. The mixed statement is about to update a transactional table and + a non-transactional table. + + 2. The mixed statement is about to update a transactional table and + read from a non-transactional table. + + 3. The mixed statement is about to update a non-transactional table + and temporary transactional table. + + 4. The mixed statement is about to update a temporary transactional + table and read from a non-transactional table. + + 5. The mixed statement is about to update a transactional table and + a temporary non-transactional table. + + 6. The mixed statement is about to update a transactional table and + read from a temporary non-transactional table. + + 7. The mixed statement is about to update a temporary transactional + table and temporary non-transactional table. + + 8. The mixed statement is about to update a temporary transactional + table and read from a temporary non-transactional table. + + After updating a transactional table if: + + 9. The mixed statement is about to update a non-transactional table + and read from a transactional table. + + 10. The mixed statement is about to update a non-transactional table + and read from a temporary transactional table. + + 11. The mixed statement is about to update a temporary non-transactional + table and read from a transactional table. + + 12. The mixed statement is about to update a temporary non-transactional + table and read from a temporary transactional table. + + 13. The mixed statement is about to update a temporary non-transactional + table and read from a non-transactional table. + + The reason for this is that locks acquired may not protected a concurrent + transaction of interfering in the current execution and by consequence in + the result. + */ + /* Case 1. */ + UNSAFE(LEX::STMT_WRITES_TRANS_TABLE, LEX::STMT_WRITES_NON_TRANS_TABLE, + BINLOG_DIRECT_ON | BINLOG_DIRECT_OFF); + /* Case 2. */ + UNSAFE(LEX::STMT_WRITES_TRANS_TABLE, LEX::STMT_READS_NON_TRANS_TABLE, + BINLOG_DIRECT_ON | BINLOG_DIRECT_OFF); + /* Case 3. */ + UNSAFE(LEX::STMT_WRITES_NON_TRANS_TABLE, LEX::STMT_WRITES_TEMP_TRANS_TABLE, + BINLOG_DIRECT_ON | BINLOG_DIRECT_OFF); + /* Case 4. */ + UNSAFE(LEX::STMT_WRITES_TEMP_TRANS_TABLE, LEX::STMT_READS_NON_TRANS_TABLE, + BINLOG_DIRECT_ON | BINLOG_DIRECT_OFF); + /* Case 5. */ + UNSAFE(LEX::STMT_WRITES_TRANS_TABLE, LEX::STMT_WRITES_TEMP_NON_TRANS_TABLE, + BINLOG_DIRECT_ON); + /* Case 6. */ + UNSAFE(LEX::STMT_WRITES_TRANS_TABLE, LEX::STMT_READS_TEMP_NON_TRANS_TABLE, + BINLOG_DIRECT_ON); + /* Case 7. */ + UNSAFE(LEX::STMT_WRITES_TEMP_TRANS_TABLE, LEX::STMT_WRITES_TEMP_NON_TRANS_TABLE, + BINLOG_DIRECT_ON); + /* Case 8. */ + UNSAFE(LEX::STMT_WRITES_TEMP_TRANS_TABLE, LEX::STMT_READS_TEMP_NON_TRANS_TABLE, + BINLOG_DIRECT_ON); + /* Case 9. */ + UNSAFE(LEX::STMT_WRITES_NON_TRANS_TABLE, LEX::STMT_READS_TRANS_TABLE, + (BINLOG_DIRECT_ON | BINLOG_DIRECT_OFF) & TRX_CACHE_NOT_EMPTY); + /* Case 10 */ + UNSAFE(LEX::STMT_WRITES_NON_TRANS_TABLE, LEX::STMT_READS_TEMP_TRANS_TABLE, + (BINLOG_DIRECT_ON | BINLOG_DIRECT_OFF) & TRX_CACHE_NOT_EMPTY); + /* Case 11. */ + UNSAFE(LEX::STMT_WRITES_TEMP_NON_TRANS_TABLE, LEX::STMT_READS_TRANS_TABLE, + BINLOG_DIRECT_ON & TRX_CACHE_NOT_EMPTY); + /* Case 12. */ + UNSAFE(LEX::STMT_WRITES_TEMP_NON_TRANS_TABLE, LEX::STMT_READS_TEMP_TRANS_TABLE, + BINLOG_DIRECT_ON & TRX_CACHE_NOT_EMPTY); + /* Case 13. */ + UNSAFE(LEX::STMT_WRITES_TEMP_NON_TRANS_TABLE, LEX::STMT_READS_NON_TRANS_TABLE, + BINLOG_DIRECT_OFF & TRX_CACHE_NOT_EMPTY); +} +#endif diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 0eb8e5de6d2..121b7622597 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -47,6 +47,53 @@ class Key; class File_parser; class Key_part_spec; +#ifdef MYSQL_SERVER +/* + There are 8 different type of table access so there is no more than + combinations 2^8 = 256: + + . STMT_READS_TRANS_TABLE + + . STMT_READS_NON_TRANS_TABLE + + . STMT_READS_TEMP_TRANS_TABLE + + . STMT_READS_TEMP_NON_TRANS_TABLE + + . STMT_WRITES_TRANS_TABLE + + . STMT_WRITES_NON_TRANS_TABLE + + . STMT_WRITES_TEMP_TRANS_TABLE + + . STMT_WRITES_TEMP_NON_TRANS_TABLE + + The unsafe conditions for each combination is represented within a byte + and stores the status of the option --binlog-direct-non-trans-updates, + whether the trx-cache is empty or not, and whether the isolation level + is lower than ISO_REPEATABLE_READ: + + . option (OFF/ON) + . trx-cache (empty/not empty) + . isolation (>= ISO_REPEATABLE_READ / < ISO_REPEATABLE_READ) + + bits 0 : . OFF, . empty, . >= ISO_REPEATABLE_READ + bits 1 : . OFF, . empty, . < ISO_REPEATABLE_READ + bits 2 : . OFF, . not empty, . >= ISO_REPEATABLE_READ + bits 3 : . OFF, . not empty, . < ISO_REPEATABLE_READ + bits 4 : . ON, . empty, . >= ISO_REPEATABLE_READ + bits 5 : . ON, . empty, . < ISO_REPEATABLE_READ + bits 6 : . ON, . not empty, . >= ISO_REPEATABLE_READ + bits 7 : . ON, . not empty, . < ISO_REPEATABLE_READ +*/ +extern uint binlog_unsafe_map[256]; +/* + Initializes the array with unsafe combinations and its respective + conditions. +*/ +void binlog_unsafe_map_init(); +#endif + /** used by the parser to store internal variable name */ @@ -1301,33 +1348,33 @@ public: a write implies a read. */ STMT_READS_TRANS_TABLE= 0, - /* - If a transactional table is about to be updated. - */ - STMT_WRITES_TRANS_TABLE, /* If a non-transactional table is about to be read. Note that a write implies a read. */ STMT_READS_NON_TRANS_TABLE, - /* - If a non-transactional table is about to be updated. - */ - STMT_WRITES_NON_TRANS_TABLE, /* If a temporary transactional table is about to be read. Note that a write implies a read. */ STMT_READS_TEMP_TRANS_TABLE, - /* - If a temporary transactional table is about to be updated. - */ - STMT_WRITES_TEMP_TRANS_TABLE, /* If a temporary non-transactional table is about to be read. Note that a write implies a read. */ STMT_READS_TEMP_NON_TRANS_TABLE, + /* + If a transactional table is about to be updated. + */ + STMT_WRITES_TRANS_TABLE, + /* + If a non-transactional table is about to be updated. + */ + STMT_WRITES_NON_TRANS_TABLE, + /* + If a temporary transactional table is about to be updated. + */ + STMT_WRITES_TEMP_TRANS_TABLE, /* If a temporary non-transactional table is about to be updated. */ @@ -1338,7 +1385,58 @@ public: */ STMT_ACCESS_TABLE_COUNT }; + + static inline const char *stmt_accessed_table_string(enum_stmt_accessed_table accessed_table) + { + switch (accessed_table) + { + case STMT_READS_TRANS_TABLE: + return "STMT_READS_TRANS_TABLE"; + break; + case STMT_READS_NON_TRANS_TABLE: + return "STMT_READS_NON_TRANS_TABLE"; + break; + case STMT_READS_TEMP_TRANS_TABLE: + return "STMT_READS_TEMP_TRANS_TABLE"; + break; + case STMT_READS_TEMP_NON_TRANS_TABLE: + return "STMT_READS_TEMP_NON_TRANS_TABLE"; + break; + case STMT_WRITES_TRANS_TABLE: + return "STMT_WRITES_TRANS_TABLE"; + break; + case STMT_WRITES_NON_TRANS_TABLE: + return "STMT_WRITES_NON_TRANS_TABLE"; + break; + case STMT_WRITES_TEMP_TRANS_TABLE: + return "STMT_WRITES_TEMP_TRANS_TABLE"; + break; + case STMT_WRITES_TEMP_NON_TRANS_TABLE: + return "STMT_WRITES_TEMP_NON_TRANS_TABLE"; + break; + case STMT_ACCESS_TABLE_COUNT: + default: + DBUG_ASSERT(0); + break; + } + } + #define BINLOG_DIRECT_ON 0xF0 /* unsafe when + --binlog-direct-non-trans-updates + is ON */ + + #define BINLOG_DIRECT_OFF 0xF /* unsafe when + --binlog-direct-non-trans-updates + is OFF */ + + #define TRX_CACHE_EMPTY 0x33 /* unsafe when trx-cache is empty */ + + #define TRX_CACHE_NOT_EMPTY 0xCC /* unsafe when trx-cache is not empty */ + + #define IL_LT_REPEATABLE 0xAA /* unsafe when < ISO_REPEATABLE_READ */ + + #define IL_GTE_REPEATABLE 0x55 /* unsafe when >= ISO_REPEATABLE_READ */ + /** Sets the type of table that is about to be accessed while executing a statement. @@ -1348,7 +1446,7 @@ public: */ inline void set_stmt_accessed_table(enum_stmt_accessed_table accessed_table) { - DBUG_ENTER("THD::set_stmt_accessed_table"); + DBUG_ENTER("LEX::set_stmt_accessed_table"); DBUG_ASSERT(accessed_table >= 0 && accessed_table < STMT_ACCESS_TABLE_COUNT); stmt_accessed_table_flag |= (1U << accessed_table); @@ -1361,7 +1459,7 @@ public: statement. @param accessed_table Enumeration type that defines the type of table, - e.g. temporary, transactional, non-transactional. + e.g. temporary, transactional, non-transactional. @return @retval TRUE if the type of the table is about to be accessed @@ -1369,7 +1467,7 @@ public: */ inline bool stmt_accessed_table(enum_stmt_accessed_table accessed_table) { - DBUG_ENTER("THD::stmt_accessed_table"); + DBUG_ENTER("LEX::stmt_accessed_table"); DBUG_ASSERT(accessed_table >= 0 && accessed_table < STMT_ACCESS_TABLE_COUNT); @@ -1377,40 +1475,79 @@ public: } /** - Checks if a temporary table is about to be accessed while executing a - statement. + Checks if a temporary non-transactional table is about to be accessed + while executing a statement. @return - @retval TRUE if a temporary table is about to be accessed + @retval TRUE if a temporary non-transactional table is about to be + accessed @retval FALSE otherwise */ - inline bool stmt_accessed_temp_table() + inline bool stmt_accessed_non_trans_temp_table() { - DBUG_ENTER("THD::stmt_accessed_temp_table"); + DBUG_ENTER("THD::stmt_accessed_non_trans_temp_table"); DBUG_RETURN((stmt_accessed_table_flag & - ((1U << STMT_READS_TEMP_TRANS_TABLE) | - (1U << STMT_WRITES_TEMP_TRANS_TABLE) | - (1U << STMT_READS_TEMP_NON_TRANS_TABLE) | + ((1U << STMT_READS_TEMP_NON_TRANS_TABLE) | (1U << STMT_WRITES_TEMP_NON_TRANS_TABLE))) != 0); } - /** - Checks if a temporary non-transactional table is about to be accessed - while executing a statement. + /* + Checks if a mixed statement is unsafe. + + @param in_multi_stmt_transaction_mode defines if there is an on-going + multi-transactional statement. + @param binlog_direct defines if --binlog-direct-non-trans-updates is + active. + @param trx_cache_is_not_empty defines if the trx-cache is empty or not. + @param trx_isolation defines the isolation level. + @return - @retval TRUE if a temporary non-transactional table is about to be - accessed + @retval TRUE if the mixed statement is unsafe @retval FALSE otherwise */ - inline bool stmt_accessed_non_trans_temp_table() + inline bool is_mixed_stmt_unsafe(bool in_multi_stmt_transaction_mode, + bool binlog_direct, + bool trx_cache_is_not_empty, + uint tx_isolation) { - DBUG_ENTER("THD::stmt_accessed_non_trans_temp_table"); + bool unsafe= FALSE; - DBUG_RETURN((stmt_accessed_table_flag & - ((1U << STMT_READS_TEMP_NON_TRANS_TABLE) | - (1U << STMT_WRITES_TEMP_NON_TRANS_TABLE))) != 0); + if (in_multi_stmt_transaction_mode) + { + uint condition= + (binlog_direct ? BINLOG_DIRECT_ON : BINLOG_DIRECT_OFF) & + (trx_cache_is_not_empty ? TRX_CACHE_NOT_EMPTY : TRX_CACHE_EMPTY) & + (tx_isolation >= ISO_REPEATABLE_READ ? IL_GTE_REPEATABLE : IL_LT_REPEATABLE); + + unsafe= (binlog_unsafe_map[stmt_accessed_table_flag] & condition); + +#if !defined(DBUG_OFF) + DBUG_PRINT("LEX::is_mixed_stmt_unsafe", ("RESULT %02X %02X %02X\n", condition, + binlog_unsafe_map[stmt_accessed_table_flag], + (binlog_unsafe_map[stmt_accessed_table_flag] & condition))); + + int type_in= 0; + for (; type_in < STMT_ACCESS_TABLE_COUNT; type_in++) + { + if (stmt_accessed_table((enum_stmt_accessed_table) type_in)) + DBUG_PRINT("LEX::is_mixed_stmt_unsafe", ("ACCESSED %s ", + stmt_accessed_table_string((enum_stmt_accessed_table) type_in))); + } +#endif + } + + if (stmt_accessed_table(STMT_WRITES_NON_TRANS_TABLE) && + stmt_accessed_table(STMT_READS_TRANS_TABLE) && + tx_isolation < ISO_REPEATABLE_READ) + unsafe= TRUE; + else if (stmt_accessed_table(STMT_WRITES_TEMP_NON_TRANS_TABLE) && + stmt_accessed_table(STMT_READS_TRANS_TABLE) && + tx_isolation < ISO_REPEATABLE_READ) + unsafe= TRUE; + + return(unsafe); } /** diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 224de005af3..de492a14c78 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1795,6 +1795,8 @@ end: clear_error is clear_error to be called query Query to log query_length Length of query + is_trans if the event changes either + a trans or non-trans engine. RETURN VALUES NONE @@ -1917,18 +1919,64 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, String wrong_tables; int error= 0; int non_temp_tables_count= 0; - bool some_tables_deleted=0, tmp_table_deleted=0, foreign_key_error=0; + bool foreign_key_error=0; + bool non_tmp_error= 0; + bool trans_tmp_table_deleted= 0, non_trans_tmp_table_deleted= 0; + bool non_tmp_table_deleted= 0; String built_query; - String built_tmp_query; + String built_trans_tmp_query, built_non_trans_tmp_query; DBUG_ENTER("mysql_rm_table_part2"); - if (thd->is_current_stmt_binlog_format_row() && !dont_log_query) + /* + Prepares the drop statements that will be written into the binary + log as follows: + + 1 - If we are not processing a "DROP TEMPORARY" it prepares a + "DROP". + + 2 - A "DROP" may result in a "DROP TEMPORARY" but the opposite is + not true. + + 3 - If the current format is row, the IF EXISTS token needs to be + appended because one does not know if CREATE TEMPORARY was previously + written to the binary log. + + 4 - Add the IF_EXISTS token if necessary, i.e. if_exists is TRUE. + + 5 - For temporary tables, there is a need to differentiate tables + in transactional and non-transactional storage engines. For that, + reason, two types of drop statements are prepared. + + The need to different the type of tables when dropping a temporary + table stems from the fact that such drop does not commit an ongoing + transaction and changes to non-transactional tables must be written + ahead of the transaction in some circumstances. + */ + if (!dont_log_query) { - built_query.set_charset(system_charset_info); - if (if_exists) - built_query.append("DROP TABLE IF EXISTS "); + if (!drop_temporary) + { + built_query.set_charset(system_charset_info); + if (if_exists) + built_query.append("DROP TABLE IF EXISTS "); + else + built_query.append("DROP TABLE "); + } + + if (thd->is_current_stmt_binlog_format_row() || if_exists) + { + built_trans_tmp_query.set_charset(system_charset_info); + built_trans_tmp_query.append("DROP TEMPORARY TABLE IF EXISTS "); + built_non_trans_tmp_query.set_charset(system_charset_info); + built_non_trans_tmp_query.append("DROP TEMPORARY TABLE IF EXISTS "); + } else - built_query.append("DROP TABLE "); + { + built_trans_tmp_query.set_charset(system_charset_info); + built_trans_tmp_query.append("DROP TEMPORARY TABLE "); + built_non_trans_tmp_query.set_charset(system_charset_info); + built_non_trans_tmp_query.append("DROP TEMPORARY TABLE "); + } } mysql_ha_rm_tables(thd, tables); @@ -1997,6 +2045,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, for (table= tables; table; table= table->next_local) { + bool is_trans; char *db=table->db; handlerton *table_type; enum legacy_db_type frm_db_type= DB_TYPE_UNKNOWN; @@ -2005,78 +2054,70 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, table->db, table->table_name, (long) table->table, table->table ? (long) table->table->s : (long) -1)); + /* + 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. + . -1 - a temporary table is used by an outer statement. + */ if (table->open_type == OT_BASE_ONLY) error= 1; - else - error= drop_temporary_table(thd, table); - - switch (error) { - case 0: - // removed temporary table - tmp_table_deleted= 1; + else if ((error= drop_temporary_table(thd, table, &is_trans)) == -1) + { + DBUG_ASSERT(thd->in_sub_stmt); + goto err; + } + + if ((drop_temporary && if_exists) || !error) + { /* - One needs to always log any temporary table drop if the current - statement logging format is set to row. This happens because one - might have created a temporary table while the statement logging - format was statement and then switched to mixed or row format. + This handles the case of temporary tables. We have the following cases: + + . "DROP TEMPORARY" was executed and a temporary table was affected + (i.e. drop_temporary && !error) or the if_exists was specified (i.e. + drop_temporary && if_exists). + + . "DROP" was executed but a temporary table was affected (.i.e + !error). */ - if (thd->is_current_stmt_binlog_format_row()) + if (!dont_log_query) { - if (built_tmp_query.is_empty()) - { - built_tmp_query.set_charset(system_charset_info); - built_tmp_query.append("DROP TEMPORARY TABLE IF EXISTS "); - } + /* + If there is an error, we don't know the type of the engine + at this point. So, we keep it in the trx-cache. + */ + is_trans= error ? TRUE : is_trans; + if (is_trans) + trans_tmp_table_deleted= TRUE; + else + non_trans_tmp_table_deleted= TRUE; - built_tmp_query.append("`"); + String *built_ptr_query= + (is_trans ? &built_trans_tmp_query : &built_non_trans_tmp_query); + /* + Don't write the database name if it is the current one (or if + thd->db is NULL). + */ + built_ptr_query->append("`"); if (thd->db == NULL || strcmp(db,thd->db) != 0) { - built_tmp_query.append(db); - built_tmp_query.append("`.`"); + built_ptr_query->append(db); + built_ptr_query->append("`.`"); } - built_tmp_query.append(table->table_name); - built_tmp_query.append("`,"); + built_ptr_query->append(table->table_name); + built_ptr_query->append("`,"); } - - continue; - case -1: - DBUG_ASSERT(thd->in_sub_stmt); - error= 1; - goto err; - default: - // temporary table not found - error= 0; - } - - /* Probably a non-temporary table. */ - if (!drop_temporary) - non_temp_tables_count++; - - /* - If row-based replication is used and the table is not a - temporary table, we add the table name to the drop statement - being built. The string always end in a comma and the comma - will be chopped off before being written to the binary log. - */ - if (!drop_temporary && thd->is_current_stmt_binlog_format_row() && !dont_log_query) - { /* - Don't write the database name if it is the current one (or if - thd->db is NULL). + This means that a temporary table was droped and as such there + is no need to proceed with the code that tries to drop a regular + table. */ - built_query.append("`"); - if (thd->db == NULL || strcmp(db,thd->db) != 0) - { - built_query.append(db); - built_query.append("`.`"); - } - - built_query.append(table->table_name); - built_query.append("`,"); + if (!error) continue; } - - if (!drop_temporary) + else if (!drop_temporary) { + non_temp_tables_count++; + if (thd->locked_tables_mode) { if (wait_while_table_is_used(thd, table->table, HA_EXTRA_FORCE_REOPEN)) @@ -2099,7 +2140,38 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, reg_ext, table->internal_tmp_table ? FN_IS_TMP : 0); + + /* + This handles the case where a "DROP" was executed and a regular + table "may be" dropped as drop_temporary is FALSE and error is + TRUE. If the error was FALSE a temporary table was dropped and + regardless of the status of drop_tempoary a "DROP TEMPORARY" + must be used. + */ + if (!dont_log_query) + { + /* + Note that unless if_exists is TRUE or a temporary table was deleted, + there is no means to know if the statement should be written to the + binary log. See further information on this variable in what follows. + */ + non_tmp_table_deleted= (if_exists ? TRUE : non_tmp_table_deleted); + /* + Don't write the database name if it is the current one (or if + thd->db is NULL). + */ + built_query.append("`"); + if (thd->db == NULL || strcmp(db,thd->db) != 0) + { + built_query.append(db); + built_query.append("`.`"); + } + + built_query.append(table->table_name); + built_query.append("`,"); + } } + /* TODO: Investigate what should be done to remove this lock completely. Is exclusive meta-data lock enough ? @@ -2108,19 +2180,29 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, DBUG_EXECUTE_IF("sleep_before_part2_delete_table", my_sleep(100000);); mysql_mutex_lock(&LOCK_open); + error= 0; if (drop_temporary || ((access(path, F_OK) && ha_create_table_from_engine(thd, db, alias)) || (!drop_view && dd_frm_type(thd, path, &frm_db_type) != FRMTYPE_TABLE))) { - // Table was not found on disk and table can't be created from engine + /* + One of the following cases happened: + . "DROP TEMPORARY" but a temporary table was not found. + . "DROP" but table was not found on disk and table can't be + created from engine. + . ./sql/datadict.cc +32 /Alfranio - TODO: We need to test this. + */ if (if_exists) push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_BAD_TABLE_ERROR, ER(ER_BAD_TABLE_ERROR), table->table_name); else + { + non_tmp_error = (drop_temporary ? non_tmp_error : TRUE); error= 1; + } } else { @@ -2153,7 +2235,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, if (error == HA_ERR_ROW_IS_REFERENCED) { /* the table is referenced by a foreign key constraint */ - foreign_key_error=1; + foreign_key_error= 1; } if (!error || error == ENOENT || error == HA_ERR_NO_SUCH_TABLE) { @@ -2162,12 +2244,13 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, strmov(end,reg_ext); if (!(new_error= mysql_file_delete(key_file_frm, path, MYF(MY_WME)))) { - some_tables_deleted=1; + non_tmp_table_deleted= TRUE; new_error= Table_triggers_list::drop_all_triggers(thd, db, table->table_name); } error|= new_error; } + non_tmp_error= error ? TRUE : non_tmp_error; } mysql_mutex_unlock(&LOCK_open); if (error) @@ -2183,11 +2266,12 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, my_printf_error(ER_BAD_TABLE_ERROR, ER(ER_BAD_TABLE_ERROR), MYF(0), table->table_name);); - } DEBUG_SYNC(thd, "rm_table_part2_before_binlog"); - thd->thread_specific_used|= tmp_table_deleted; + thd->thread_specific_used|= (trans_tmp_table_deleted || + non_trans_tmp_table_deleted); error= 0; +err: if (wrong_tables.length()) { if (!foreign_key_error) @@ -2198,85 +2282,48 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists, error= 1; } - if (some_tables_deleted || tmp_table_deleted || !error) + if (non_trans_tmp_table_deleted || + trans_tmp_table_deleted || non_tmp_table_deleted) { query_cache_invalidate3(thd, tables, 0); if (!dont_log_query && mysql_bin_log.is_open()) { - if (!thd->is_current_stmt_binlog_format_row() || - (non_temp_tables_count > 0 && !tmp_table_deleted)) + if (non_trans_tmp_table_deleted) { - /* - In this case, we are either using statement-based - replication or using row-based replication but have only - deleted one or more non-temporary tables (and no temporary - tables). In this case, we can write the original query into - the binary log. - */ - error |= write_bin_log(thd, !error, thd->query(), thd->query_length()); + /* Chop of the last comma */ + built_non_trans_tmp_query.chop(); + built_non_trans_tmp_query.append(" /* generated by server */"); + error |= thd->binlog_query(THD::STMT_QUERY_TYPE, + built_non_trans_tmp_query.ptr(), + built_non_trans_tmp_query.length(), + FALSE, FALSE, FALSE, 0); } - else if (thd->is_current_stmt_binlog_format_row() && - tmp_table_deleted) + if (trans_tmp_table_deleted) { - if (non_temp_tables_count > 0) - { - /* - In this case we have deleted both temporary and - non-temporary tables, so: - - since we have deleted a non-temporary table we have to - binlog the statement, but - - since we have deleted a temporary table we cannot binlog - the statement (since the table may have not been created on the - slave - check "if" branch below, this might cause the slave to - stop). - - Instead, we write a built statement, only containing the - non-temporary tables, to the binary log - */ - built_query.chop(); // Chop of the last comma + /* Chop of the last comma */ + built_trans_tmp_query.chop(); + built_trans_tmp_query.append(" /* generated by server */"); + error |= thd->binlog_query(THD::STMT_QUERY_TYPE, + built_trans_tmp_query.ptr(), + built_trans_tmp_query.length(), + TRUE, FALSE, FALSE, 0); + } + if (non_tmp_table_deleted) + { + /* Chop of the last comma */ + built_query.chop(); built_query.append(" /* generated by server */"); - error|= write_bin_log(thd, !error, built_query.ptr(), built_query.length()); - } - - /* - One needs to always log any temporary table drop if the current - statement logging format is set to row. This happens because one - might have created a temporary table while the statement logging - format was statement and then switched to mixed or row format. - */ - if (thd->is_current_stmt_binlog_format_row()) - { - /* - In this case we have deleted some temporary tables but we are using - row based logging for the statement. However, thread uses mixed mode - format, thence we need to log the dropping as we cannot tell for - sure whether the create was logged as statement previously or not, ie, - before switching to row mode. - */ - built_tmp_query.chop(); // Chop of the last comma - built_tmp_query.append(" /* generated by server */"); - /* - We cannot call the write_bin_log as we do not care about any errors - in the master as the statement is always DROP TEMPORARY TABLE IF EXISTS - and as such there will be no errors in the slave. - */ - error|= thd->binlog_query(THD::STMT_QUERY_TYPE, built_tmp_query.ptr(), - built_tmp_query.length(), FALSE, FALSE, FALSE, - 0); - } + int error_code = (non_tmp_error ? + (foreign_key_error ? ER_ROW_IS_REFERENCED : ER_BAD_TABLE_ERROR) : 0); + error |= thd->binlog_query(THD::STMT_QUERY_TYPE, + built_query.ptr(), + built_query.length(), + TRUE, FALSE, FALSE, + error_code); } - - /* - The remaining cases are: - - no tables were deleted and - - only temporary tables were deleted and row-based - replication is used. - In both these cases, nothing should be written to the binary - log. - */ } } -err: + if (!drop_temporary) { /* @@ -3791,6 +3838,8 @@ void sp_prepare_create_field(THD *thd, Create_field *sql_field) internal_tmp_table Set to 1 if this is an internal temporary table (From ALTER TABLE) select_field_count + is_trans identifies the type of engine where the table + was created: either trans or non-trans. DESCRIPTION If one creates a temporary table, this is automatically opened @@ -3815,7 +3864,8 @@ bool mysql_create_table_no_lock(THD *thd, HA_CREATE_INFO *create_info, Alter_info *alter_info, bool internal_tmp_table, - uint select_field_count) + uint select_field_count, + bool *is_trans) { char path[FN_REFLEN + 1]; uint path_length; @@ -4180,12 +4230,17 @@ bool mysql_create_table_no_lock(THD *thd, if (create_info->options & HA_LEX_CREATE_TMP_TABLE) { + TABLE *table= NULL; /* Open table and put in temporary table list */ - if (!(open_temporary_table(thd, path, db, table_name, 1))) + if (!(table= open_temporary_table(thd, path, db, table_name, 1))) { (void) rm_temporary_table(create_info->db_type, path); goto unlock_and_end; } + + if (is_trans != NULL) + *is_trans= table->file->has_transactions(); + thd->thread_specific_used= TRUE; } @@ -4236,9 +4291,10 @@ bool mysql_create_table(THD *thd, TABLE_LIST *create_table, /* Got lock. */ DEBUG_SYNC(thd, "locked_table_name"); + bool is_trans; result= mysql_create_table_no_lock(thd, create_table->db, create_table->table_name, create_info, - alter_info, FALSE, 0); + alter_info, FALSE, 0, &is_trans); /* Don't write statement if: @@ -4250,7 +4306,7 @@ bool mysql_create_table(THD *thd, TABLE_LIST *create_table, (!thd->is_current_stmt_binlog_format_row() || (thd->is_current_stmt_binlog_format_row() && !(create_info->options & HA_LEX_CREATE_TMP_TABLE)))) - result= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + result= write_bin_log(thd, TRUE, thd->query(), thd->query_length(), is_trans); end: DBUG_RETURN(result); @@ -4456,9 +4512,10 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, /* Reset auto-increment counter for the new table. */ local_create_info.auto_increment_value= 0; + bool is_trans; if ((res= mysql_create_table_no_lock(thd, table->db, table->table_name, &local_create_info, &local_alter_info, - FALSE, 0))) + FALSE, 0, &is_trans))) goto err; /* @@ -4539,7 +4596,7 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, Case 3 and 4 does nothing under RBR */ } - else if (write_bin_log(thd, TRUE, thd->query(), thd->query_length())) + else if (write_bin_log(thd, TRUE, thd->query(), thd->query_length(), is_trans)) goto err; err: @@ -6228,7 +6285,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, error= mysql_create_table_no_lock(thd, new_db, tmp_name, create_info, alter_info, - 1, 0); + 1, 0, NULL); reenable_binlog(thd); if (error) goto err; diff --git a/sql/sql_table.h b/sql/sql_table.h index 2e1cc89d585..ae5beefea37 100644 --- a/sql/sql_table.h +++ b/sql/sql_table.h @@ -138,7 +138,8 @@ bool mysql_create_table_no_lock(THD *thd, const char *db, const char *table_name, HA_CREATE_INFO *create_info, Alter_info *alter_info, - bool tmp_table, uint select_field_count); + bool tmp_table, uint select_field_count, + bool *is_trans); bool mysql_prepare_alter_table(THD *thd, TABLE *table, HA_CREATE_INFO *create_info, Alter_info *alter_info); -- cgit v1.2.1 From bbaae9a2dceacecdd23aba6b7bb686d6248ae7c1 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Fri, 20 Aug 2010 09:16:26 +0200 Subject: Bug #55973 Assertion `thd->transaction.stmt.is_empty()' on CREATE TABLE .. SELECT I_S.PART This assert was triggered if an InnoDB table was created using CREATE TABLE ... AS SELECT where the query used an I_S table, and a view existed in the database. It would also be triggered for any statement changing an InnoDB table (e.g. INSERT, UPDATE, DELETE) which had a subquery referencing an I_S table. The assert was triggered if open_normal_and_derived_tables() failed and a statement transaction had been started. This will usually not happen as tables are opened before a statement transaction is started. However, e.g. CREATE TABLE ... AS SELECT starts a transaction in order to insert tuples into the new table. And if the subquery references an I_S table, all current tables and views can be opened in order to fill the I_S table on the fly. If a view is discovered, open will fail as it is instructed to open tables only (OPEN_TABLE_ONLY). This would cause the assert to be triggered. The assert was added in the patch for Bug#52044 and was therefore not in any released versions of the server. This patch fixes the problem by adjusting the assert to take into consideration the possibility of tables being opened as part of an I_S query. This is similar to what is already done for close_tables_for_reopen(). Test case added to information_schema_inno.test. --- sql/sql_base.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'sql') diff --git a/sql/sql_base.cc b/sql/sql_base.cc index d69644dcf17..36aeabd3379 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -5411,8 +5411,14 @@ bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags) DBUG_RETURN(0); end: - /* No need to rollback statement transaction, it's not started. */ - DBUG_ASSERT(thd->transaction.stmt.is_empty()); + /* + No need to commit/rollback the statement transaction: it's + either not started or we're filling in an INFORMATION_SCHEMA + table on the fly, and thus mustn't manipulate with the + transaction of the enclosing statement. + */ + DBUG_ASSERT(thd->transaction.stmt.is_empty() || + (thd->state_flags & Open_tables_state::BACKUPS_AVAIL)); close_thread_tables(thd); /* Don't keep locks for a failed statement. */ thd->mdl_context.rollback_to_savepoint(mdl_savepoint); -- cgit v1.2.1 From 84ee0a9fa40f13eaeec66ce2c9d3b8dcf2b9c67d Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Fri, 20 Aug 2010 15:14:11 +0400 Subject: Bug#55912 FORMAT with locale set fails for numbers < 1000 Problems: - dot character was always printed as decimal point instead of localized decimal point for short numbers without thousands - Item_func_format::val_str always returned values in ASCII format, regargless of @@character_set_connection, which in case of utf32 led to crash in debug build, or to incorrect values in release build. Fix: - Adding a piece of code to replace dot character to localized decimal point in short numbers. - Changing parent class for Item_func_format to Item_str_ascii_func, because its val_str() implementation is heavily ASCII oriented. --- sql/item_strfunc.cc | 15 +++++++++++---- sql/item_strfunc.h | 8 ++++---- 2 files changed, 15 insertions(+), 8 deletions(-) (limited to 'sql') diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index 1a772950303..6d3514bf356 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -2217,7 +2217,7 @@ const int FORMAT_MAX_DECIMALS= 30; MY_LOCALE *Item_func_format::get_locale(Item *item) { DBUG_ASSERT(arg_count == 3); - String tmp, *locale_name= args[2]->val_str(&tmp); + String tmp, *locale_name= args[2]->val_str_ascii(&tmp); MY_LOCALE *lc; if (!locale_name || !(lc= my_locale_by_name(locale_name->c_ptr_safe()))) @@ -2250,7 +2250,7 @@ void Item_func_format::fix_length_and_dec() are stored in more than one byte */ -String *Item_func_format::val_str(String *str) +String *Item_func_format::val_str_ascii(String *str) { uint32 str_length; /* Number of decimal digits */ @@ -2290,8 +2290,7 @@ String *Item_func_format::val_str(String *str) if ((null_value=args[0]->null_value)) return 0; /* purecov: inspected */ nr= my_double_round(nr, (longlong) dec, FALSE, FALSE); - /* Here default_charset() is right as this is not an automatic conversion */ - str->set_real(nr, dec, default_charset()); + str->set_real(nr, dec, &my_charset_numeric); if (isnan(nr)) return str; str_length=str->length(); @@ -2341,6 +2340,14 @@ String *Item_func_format::val_str(String *str) /* Put the rest of the integer part without grouping */ str->copy(dst, buf + sizeof(buf) - dst, &my_charset_latin1); } + else if (dec_length && lc->decimal_point != '.') + { + /* + For short values without thousands (<1000) + replace decimal point to localized value. + */ + ((char*) str->ptr())[str_length - dec_length]= lc->decimal_point; + } return str; } diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h index 4461373f7b3..5dcef2e671f 100644 --- a/sql/item_strfunc.h +++ b/sql/item_strfunc.h @@ -539,17 +539,17 @@ public: }; -class Item_func_format :public Item_str_func +class Item_func_format :public Item_str_ascii_func { String tmp_str; MY_LOCALE *locale; public: - Item_func_format(Item *org, Item *dec): Item_str_func(org, dec) {} + Item_func_format(Item *org, Item *dec): Item_str_ascii_func(org, dec) {} Item_func_format(Item *org, Item *dec, Item *lang): - Item_str_func(org, dec, lang) {} + Item_str_ascii_func(org, dec, lang) {} MY_LOCALE *get_locale(Item *item); - String *val_str(String *); + String *val_str_ascii(String *); void fix_length_and_dec(); const char *func_name() const { return "format"; } virtual void print(String *str, enum_query_type query_type); -- cgit v1.2.1 From b51c8cab3e0afccd6b44fea70e59243b24844158 Mon Sep 17 00:00:00 2001 From: Sergey Vojtovich Date: Fri, 20 Aug 2010 13:58:28 +0400 Subject: BUG#54989 - With null_audit installed, server hangs on an attempt to install a plugin twice Server crashes when [UN]INSTALL PLUGIN fails (returns an error) and general log is disabled and there are audit plugins interested in MYSQL_AUDIT_GENERAL_CLASS. When audit event is triggered, audit subsystem acquires interested plugins by walking through plugin list. Evidently plugin list iterator protects plugin list by acquiring LOCK_plugin, see plugin_foreach_with_mask(). On the other hand [UN]INSTALL PLUGIN is acquiring LOCK_plugin rather for a long time. When audit event is triggered during [UN]INSTALL PLUGIN, plugin list iterator acquires the same lock (within the same thread) second time. Repeatable only with general_log disabled, because general_log triggers MYSQL_AUDIT_GENERAL_LOG event, which acquires audit plugins before [UN]INSTALL PLUGIN acquired LOCK_plugin. With this fix we pre-acquire audit plugins for events that may potentially occur during [UN]INSTALL PLUGIN. This hack should be removed when LOCK_plugin is fixed so it protects only what it supposed to protect. No test case for this fix - we do not have facility to test audit plugins yet. sql/sql_audit.cc: Move "acquire audit plugin" logics to a separate function. sql/sql_audit.h: Move "acquire audit plugin" logics to a separate function. sql/sql_plugin.cc: Pre-acquire audit plugins for events that may potentially occur during [UN]INSTALL PLUGIN. --- sql/sql_audit.cc | 44 ++++++++++++++++++++++++++++++-------------- sql/sql_audit.h | 1 + sql/sql_plugin.cc | 44 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 74 insertions(+), 15 deletions(-) (limited to 'sql') diff --git a/sql/sql_audit.cc b/sql/sql_audit.cc index 7d269d455a9..b7d363dc09a 100644 --- a/sql/sql_audit.cc +++ b/sql/sql_audit.cc @@ -137,6 +137,30 @@ static my_bool acquire_plugins(THD *thd, plugin_ref plugin, void *arg) } +/** + @brief Acquire audit plugins + + @param[in] thd MySQL thread handle + @param[in] event_class Audit event class + + @details Ensure that audit plugins interested in given event + class are locked by current thread. +*/ +void mysql_audit_acquire_plugins(THD *thd, uint event_class) +{ + unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE]; + DBUG_ENTER("mysql_audit_acquire_plugins"); + set_audit_mask(event_class_mask, event_class); + if (thd && !check_audit_mask(mysql_global_audit_mask, event_class_mask) && + check_audit_mask(thd->audit_class_mask, event_class_mask)) + { + plugin_foreach(thd, acquire_plugins, MYSQL_AUDIT_PLUGIN, &event_class); + add_audit_mask(thd->audit_class_mask, event_class_mask); + } + DBUG_VOID_RETURN; +} + + /** Notify the audit system of an event @@ -151,21 +175,8 @@ void mysql_audit_notify(THD *thd, uint event_class, uint event_subtype, ...) { va_list ap; audit_handler_t *handlers= audit_handlers + event_class; - unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE]; - DBUG_ASSERT(event_class < audit_handlers_count); - - set_audit_mask(event_class_mask, event_class); - /* - Check to see if we have acquired the audit plugins for the - required audit event classes. - */ - if (thd && check_audit_mask(thd->audit_class_mask, event_class_mask)) - { - plugin_foreach(thd, acquire_plugins, MYSQL_AUDIT_PLUGIN, &event_class); - add_audit_mask(thd->audit_class_mask, event_class_mask); - } - + mysql_audit_acquire_plugins(thd, event_class); va_start(ap, event_subtype); (*handlers)(thd, event_subtype, ap); va_end(ap); @@ -448,6 +459,11 @@ static void event_class_dispatch(THD *thd, const struct mysql_event *event) #else /* EMBEDDED_LIBRARY */ +void mysql_audit_acquire_plugins(THD *thd, uint event_class) +{ +} + + void mysql_audit_initialize() { } diff --git a/sql/sql_audit.h b/sql/sql_audit.h index 5b6962b9ecb..953e41f1f06 100644 --- a/sql/sql_audit.h +++ b/sql/sql_audit.h @@ -29,6 +29,7 @@ extern void mysql_audit_finalize(); extern void mysql_audit_init_thd(THD *thd); extern void mysql_audit_free_thd(THD *thd); +extern void mysql_audit_acquire_plugins(THD *thd, uint event_class); extern void mysql_audit_notify(THD *thd, uint event_class, diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index e3323260373..2d317eb56ef 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -29,7 +29,7 @@ #include "records.h" // init_read_record, end_read_record #include #include -#include +#include "sql_audit.h" #include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT #define REPORT_TO_LOG 1 #define REPORT_TO_USER 2 @@ -1703,6 +1703,27 @@ bool mysql_install_plugin(THD *thd, const LEX_STRING *name, const LEX_STRING *dl MYSQL_LOCK_IGNORE_TIMEOUT))) DBUG_RETURN(TRUE); + /* + Pre-acquire audit plugins for events that may potentially occur + during [UN]INSTALL PLUGIN. + + When audit event is triggered, audit subsystem acquires interested + plugins by walking through plugin list. Evidently plugin list + iterator protects plugin list by acquiring LOCK_plugin, see + plugin_foreach_with_mask(). + + On the other hand [UN]INSTALL PLUGIN is acquiring LOCK_plugin + rather for a long time. + + When audit event is triggered during [UN]INSTALL PLUGIN, plugin + list iterator acquires the same lock (within the same thread) + second time. + + This hack should be removed when LOCK_plugin is fixed so it + protects only what it supposed to protect. + */ + mysql_audit_acquire_plugins(thd, MYSQL_AUDIT_GENERAL_CLASS); + mysql_mutex_lock(&LOCK_plugin); mysql_rwlock_wrlock(&LOCK_system_variables_hash); @@ -1783,6 +1804,27 @@ bool mysql_uninstall_plugin(THD *thd, const LEX_STRING *name) if (! (table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT))) DBUG_RETURN(TRUE); + /* + Pre-acquire audit plugins for events that may potentially occur + during [UN]INSTALL PLUGIN. + + When audit event is triggered, audit subsystem acquires interested + plugins by walking through plugin list. Evidently plugin list + iterator protects plugin list by acquiring LOCK_plugin, see + plugin_foreach_with_mask(). + + On the other hand [UN]INSTALL PLUGIN is acquiring LOCK_plugin + rather for a long time. + + When audit event is triggered during [UN]INSTALL PLUGIN, plugin + list iterator acquires the same lock (within the same thread) + second time. + + This hack should be removed when LOCK_plugin is fixed so it + protects only what it supposed to protect. + */ + mysql_audit_acquire_plugins(thd, MYSQL_AUDIT_GENERAL_CLASS); + mysql_mutex_lock(&LOCK_plugin); if (!(plugin= plugin_find_internal(name, MYSQL_ANY_PLUGIN))) { -- cgit v1.2.1 From 8df0bf13ab5930333ae1148856533e7bff156296 Mon Sep 17 00:00:00 2001 From: Mattias Jonsson Date: Fri, 20 Aug 2010 19:15:48 +0200 Subject: Bug#54747: Deadlock between REORGANIZE PARTITION and SELECT is not detected The ALTER PARTITION and SELECT seemed to be deadlocked when having innodb_thread_concurrency = 1. Problem was that there was unreleased latches in the ALTER PARTITION thread which was needed by the SELECT thread to be able to continue. Solution was to release the latches by commit before requesting upgrade to exclusive MDL lock. Updated according to reviewers comments (3). mysql-test/r/partition_innodb.result: updated test result mysql-test/t/partition_innodb.test: added test sql/sql_partition.cc: Moved implicit commit into mysql_change_partition so that if latches are taken, they are always released before waiting on exclusive lock. sql/sql_table.cc: refactored the code to prepare and commit around copy_data_between_tables, to be able to reuse it in mysql_change_partitions sql/sql_table.h: exporting mysql_trans_prepare/commit_alter_copy_data --- sql/sql_partition.cc | 21 ++++++++-------- sql/sql_table.cc | 71 ++++++++++++++++++++++++++++++++++++---------------- sql/sql_table.h | 2 ++ 3 files changed, 63 insertions(+), 31 deletions(-) (limited to 'sql') diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 5bbe946179e..b72816f8ce3 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -63,6 +63,7 @@ #include "sql_table.h" // build_table_filename, // build_table_shadow_filename, // table_to_filename + // mysql_*_alter_copy_data #include "opt_range.h" // store_key_image_to_rec #include "sql_analyse.h" // append_escaped @@ -4377,7 +4378,6 @@ static int fast_end_partition(THD *thd, ulonglong copied, ALTER_PARTITION_PARAM_TYPE *lpt, bool written_bin_log) { - int error; char tmp_name[80]; DBUG_ENTER("fast_end_partition"); @@ -4386,13 +4386,6 @@ static int fast_end_partition(THD *thd, ulonglong copied, if (!is_empty) query_cache_invalidate3(thd, table_list, 0); - error= trans_commit_stmt(thd); - if (trans_commit_implicit(thd)) - error= 1; - - if (error) - DBUG_RETURN(TRUE); /* The error has been reported */ - if ((!is_empty) && (!written_bin_log) && (!thd->lex->no_write_to_binlog) && write_bin_log(thd, FALSE, thd->query(), thd->query_length())) @@ -5535,17 +5528,25 @@ static bool mysql_change_partitions(ALTER_PARTITION_PARAM_TYPE *lpt) char path[FN_REFLEN+1]; int error; handler *file= lpt->table->file; + THD *thd= lpt->thd; DBUG_ENTER("mysql_change_partitions"); build_table_filename(path, sizeof(path) - 1, lpt->db, lpt->table_name, "", 0); + + if(mysql_trans_prepare_alter_copy_data(thd)) + DBUG_RETURN(TRUE); + if ((error= file->ha_change_partitions(lpt->create_info, path, &lpt->copied, &lpt->deleted, lpt->pack_frm_data, lpt->pack_frm_len))) { file->print_error(error, MYF(error != ER_OUTOFMEMORY ? 0 : ME_FATALERROR)); - DBUG_RETURN(TRUE); } - DBUG_RETURN(FALSE); + + if (mysql_trans_commit_alter_copy_data(thd)) + DBUG_RETURN(TRUE); /* The error has been reported */ + + DBUG_RETURN(test(error)); } diff --git a/sql/sql_table.cc b/sql/sql_table.cc index bd5f381b8b8..3bb7f7667ea 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -6740,6 +6740,54 @@ err_with_mdl: } /* mysql_alter_table */ + + +/** + Prepare the transaction for the alter table's copy phase. +*/ + +bool mysql_trans_prepare_alter_copy_data(THD *thd) +{ + DBUG_ENTER("mysql_prepare_alter_copy_data"); + /* + Turn off recovery logging since rollback of an alter table is to + delete the new table so there is no need to log the changes to it. + + This needs to be done before external_lock. + */ + if (ha_enable_transaction(thd, FALSE)) + DBUG_RETURN(TRUE); + DBUG_RETURN(FALSE); +} + + +/** + Commit the copy phase of the alter table. +*/ + +bool mysql_trans_commit_alter_copy_data(THD *thd) +{ + bool error= FALSE; + DBUG_ENTER("mysql_commit_alter_copy_data"); + + if (ha_enable_transaction(thd, TRUE)) + DBUG_RETURN(TRUE); + + /* + Ensure that the new table is saved properly to disk before installing + the new .frm. + And that InnoDB's internal latches are released, to avoid deadlock + when waiting on other instances of the table before rename (Bug#54747). + */ + if (trans_commit_stmt(thd)) + error= TRUE; + if (trans_commit_implicit(thd)) + error= TRUE; + + DBUG_RETURN(error); +} + + static int copy_data_between_tables(TABLE *from,TABLE *to, List &create, @@ -6766,14 +6814,7 @@ copy_data_between_tables(TABLE *from,TABLE *to, ulonglong prev_insert_id; DBUG_ENTER("copy_data_between_tables"); - /* - Turn off recovery logging since rollback of an alter table is to - delete the new table so there is no need to log the changes to it. - - This needs to be done before external_lock - */ - error= ha_enable_transaction(thd, FALSE); - if (error) + if (mysql_trans_prepare_alter_copy_data(thd)) DBUG_RETURN(-1); if (!(copy= new Copy_field[to->s->fields])) @@ -6932,20 +6973,8 @@ copy_data_between_tables(TABLE *from,TABLE *to, } to->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); - if (ha_enable_transaction(thd, TRUE)) - { + if (mysql_trans_commit_alter_copy_data(thd)) error= 1; - goto err; - } - - /* - Ensure that the new table is saved properly to disk so that we - can do a rename - */ - if (trans_commit_stmt(thd)) - error=1; - if (trans_commit_implicit(thd)) - error=1; err: thd->variables.sql_mode= save_sql_mode; diff --git a/sql/sql_table.h b/sql/sql_table.h index ae5beefea37..eb0b1aa94dd 100644 --- a/sql/sql_table.h +++ b/sql/sql_table.h @@ -143,6 +143,8 @@ bool mysql_create_table_no_lock(THD *thd, const char *db, bool mysql_prepare_alter_table(THD *thd, TABLE *table, HA_CREATE_INFO *create_info, Alter_info *alter_info); +bool mysql_trans_prepare_alter_copy_data(THD *thd); +bool mysql_trans_commit_alter_copy_data(THD *thd); bool mysql_alter_table(THD *thd, char *new_db, char *new_name, HA_CREATE_INFO *create_info, TABLE_LIST *table_list, -- cgit v1.2.1 From 1ed02deea0986ee2aefd7b8bc61390f3675c2e94 Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Mon, 23 Aug 2010 13:56:21 +0400 Subject: Bug#52121 partition by key on utf32 enum field cause debug assertion: (length % 4) == 0 Problem: ENUM columns are sorted and distributed according to their numeric value, but Field::hash() incorrectly passed string character set (utf32) in combination with numeric value to the hash function, which made assertion fail. Fix: pass "binary" character set in combination with numeric value to the hash function. mysql-test/suite/parts/r/part_ctype_utf32.result Adding tests mysql-test/suite/parts/t/part_ctype_utf32.test Adding test sql/field.cc Pass correct character set pointer to the hash function. --- sql/field.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sql') diff --git a/sql/field.cc b/sql/field.cc index 3c93ffadac5..fc55426b177 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -1329,7 +1329,7 @@ void Field::hash(ulong *nr, ulong *nr2) else { uint len= pack_length(); - CHARSET_INFO *cs= charset(); + CHARSET_INFO *cs= sort_charset(); cs->coll->hash_sort(cs, ptr, len, nr, nr2); } } -- cgit v1.2.1 From 3592489cae0115c245ef3e692bf0ec276a3e010d Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Mon, 23 Aug 2010 17:42:53 +0200 Subject: Bug #54332 Deadlock with two connections doing LOCK TABLE+INSERT DELAYED The problem was that deadlocks involving INSERT DELAYED were not detected. The reason for this is that two threads are involved in INSERT DELAYED: the connection thread and the handler thread. The connection thread would wait while the handler thread acquired locks and opened the table. In essence, this adds an edge to the wait-for-graph between the connection thread and the handler thread that the deadlock detector is unaware of. Therefore many deadlocks involving INSERT DELAYED were not detected. This patch fixes the problem by having the connection thread acquire the metadata lock the table before starting the handler thread. This allows the deadlock detector to detect any possible deadlocks resulting from trying to acquire a metadata lock the table. If a metadata lock is successfully acquired, the handler thread is started and given a copy of the ticket representing the metadata lock. When the handler thread then tries to lock and open the table, it will find that it already has the metadata lock and therefore not acquire any new metadata locks. Test cases added to delayed.test. --- sql/sql_insert.cc | 73 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 10 deletions(-) (limited to 'sql') diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 6c23b47997a..d9340cf843a 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -548,10 +548,34 @@ bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list) DBUG_RETURN(TRUE); } - if (delayed_get_table(thd, table_list)) + /* + 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. + */ + MDL_ticket *mdl_savepoint= thd->mdl_context.mdl_savepoint(); + if (thd->mdl_context.acquire_lock(&table_list->mdl_request, + thd->variables.lock_wait_timeout)) + /* + If a lock can't be acquired, it makes no sense to try normal insert. + Therefore we just abort the statement. + */ DBUG_RETURN(TRUE); - if (table_list->table) + /* + If a lock was acquired above, we should release it after delayed_get_table() + has cloned the ticket for the handler thread. Note that acquire_lock() can + succeed because of a lock already held by the connection. In this case we + should not release it here. + */ + MDL_ticket *table_ticket = mdl_savepoint == thd->mdl_context.mdl_savepoint() ? + NULL: thd->mdl_context.mdl_savepoint(); + + bool error= FALSE; + if (delayed_get_table(thd, table_list)) + error= TRUE; + else if (table_list->table) { /* Open tables used for sub-selects or in stored functions, will also @@ -560,16 +584,30 @@ bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list) if (open_and_lock_tables(thd, table_list->next_global, TRUE, 0)) { end_delayed_insert(thd); - DBUG_RETURN(TRUE); + error= TRUE; + } + else + { + /* + First table was not processed by open_and_lock_tables(), + we need to set updatability flag "by hand". + */ + if (!table_list->derived && !table_list->view) + table_list->updatable= 1; // usual table } - /* - First table was not processed by open_and_lock_tables(), - we need to set updatability flag "by hand". - */ - if (!table_list->derived && !table_list->view) - table_list->updatable= 1; // usual table - DBUG_RETURN(FALSE); } + + if (table_ticket) + thd->mdl_context.release_lock(table_ticket); + /* + Clone_ticket() in delayed_get_table() causes TABLE_LIST::MDL_REQUEST::ticket + to be overwritten with the cloned ticket. Reset the ticket here in case + we end up having to use normal insert. + */ + table_list->mdl_request.ticket= NULL; + + if (error || table_list->table) + DBUG_RETURN(error); #endif /* * This is embedded library and we don't have auxiliary @@ -2025,6 +2063,20 @@ 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; + + /* + 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. + */ + if (di->thd.mdl_context.clone_ticket(&table_list->mdl_request)) + { + delete di; + my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR)); + goto end_create; + } + di->lock(); mysql_mutex_lock(&di->mutex); if ((error= mysql_thread_create(key_thread_delayed_insert, @@ -2036,6 +2088,7 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list) error)); mysql_mutex_unlock(&di->mutex); di->unlock(); + di->thd.mdl_context.release_lock(table_list->mdl_request.ticket); delete di; my_error(ER_CANT_CREATE_THREAD, MYF(ME_FATALERROR), error); goto end_create; -- cgit v1.2.1 From dd1890f48ce27d1cad0d514e388d4704684a17a7 Mon Sep 17 00:00:00 2001 From: Evgeny Potemkin Date: Mon, 23 Aug 2010 19:59:56 +0400 Subject: Bug#56120: Failed assertion on MIX/MAX on negative time value The Item_cache_datetime::val_str function wasn't taking into account that time could be negative. This led to failed assertion. Now Item_cache_datetime::val_str correctly converts negative time values from integer to string representation. mysql-test/r/func_group.result: Added a test case for the bug#56120. mysql-test/t/func_group.test: Added a test case for the bug#56120. sql/item.cc: Bug#56120: Failed assertion on MIX/MAX on negative time value Now Item_cache_datetime::val_str correctly converts negative time values from integer to string representation. --- sql/item.cc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'sql') diff --git a/sql/item.cc b/sql/item.cc index 24f57342668..427146e11dc 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -7510,9 +7510,14 @@ String *Item_cache_datetime::val_str(String *str) return NULL; if (cached_field_type == MYSQL_TYPE_TIME) { - ulonglong time= int_value; - DBUG_ASSERT(time <= TIME_MAX_VALUE); + longlong time= int_value; set_zero_time(<ime, MYSQL_TIMESTAMP_TIME); + if (time < 0) + { + time= -time; + ltime.neg= TRUE; + } + DBUG_ASSERT(time <= TIME_MAX_VALUE); ltime.second= time % 100; time/= 100; ltime.minute= time % 100; -- cgit v1.2.1 From ee07ed22868f002701fd0a3b729caae220aafd18 Mon Sep 17 00:00:00 2001 From: Alfranio Correia Date: Mon, 23 Aug 2010 23:31:12 +0100 Subject: Post-fix push for BUG#53452. --- sql/log_event.cc | 3 ++- sql/sql_table.cc | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'sql') diff --git a/sql/log_event.cc b/sql/log_event.cc index c1f7836e08a..16290c58685 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -674,7 +674,8 @@ const char* Log_event::get_type_str() #ifndef MYSQL_CLIENT Log_event::Log_event(THD* thd_arg, uint16 flags_arg, bool using_trans) - :log_pos(0), temp_buf(0), exec_time(0), flags(flags_arg), thd(thd_arg) + :log_pos(0), temp_buf(0), exec_time(0), flags(flags_arg), + cache_type(Log_event::EVENT_INVALID_CACHE), thd(thd_arg) { server_id= thd->server_id; when= thd->start_time; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 3bb7f7667ea..3f0a0326c84 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4268,6 +4268,7 @@ bool mysql_create_table(THD *thd, TABLE_LIST *create_table, Alter_info *alter_info) { bool result; + bool is_trans= FALSE; DBUG_ENTER("mysql_create_table"); /* @@ -4282,7 +4283,6 @@ bool mysql_create_table(THD *thd, TABLE_LIST *create_table, /* Got lock. */ DEBUG_SYNC(thd, "locked_table_name"); - bool is_trans; result= mysql_create_table_no_lock(thd, create_table->db, create_table->table_name, create_info, alter_info, FALSE, 0, &is_trans); @@ -4454,6 +4454,7 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, HA_CREATE_INFO local_create_info; Alter_info local_alter_info; bool res= TRUE; + bool is_trans= FALSE; uint not_used; DBUG_ENTER("mysql_create_like_table"); @@ -4503,7 +4504,6 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, /* Reset auto-increment counter for the new table. */ local_create_info.auto_increment_value= 0; - bool is_trans; if ((res= mysql_create_table_no_lock(thd, table->db, table->table_name, &local_create_info, &local_alter_info, FALSE, 0, &is_trans))) -- cgit v1.2.1 From 1c09847b5f252baa59a3af78d003b4146473a974 Mon Sep 17 00:00:00 2001 From: Jon Olav Hauglid Date: Tue, 24 Aug 2010 16:00:17 +0200 Subject: Follow-up to Bug #54332 Deadlock with two connections doing LOCK TABLE+INSERT DELAYED The problem was that the server could crash if the insert delayed handler thread was killed due to a conflicting shared metadata lock. This could happen because the metadata lock ticket was added to the handler thread before it was properly initialized. This patch moves the cloning of the acquired metadata lock ticket until after the handler thread has been properly initialized. --- sql/sql_insert.cc | 88 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 33 deletions(-) (limited to 'sql') diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index d9340cf843a..5f6ab43e2ea 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -563,15 +563,6 @@ bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list) */ DBUG_RETURN(TRUE); - /* - If a lock was acquired above, we should release it after delayed_get_table() - has cloned the ticket for the handler thread. Note that acquire_lock() can - succeed because of a lock already held by the connection. In this case we - should not release it here. - */ - MDL_ticket *table_ticket = mdl_savepoint == thd->mdl_context.mdl_savepoint() ? - NULL: thd->mdl_context.mdl_savepoint(); - bool error= FALSE; if (delayed_get_table(thd, table_list)) error= TRUE; @@ -597,12 +588,18 @@ bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list) } } - if (table_ticket) - thd->mdl_context.release_lock(table_ticket); /* - Clone_ticket() in delayed_get_table() causes TABLE_LIST::MDL_REQUEST::ticket - to be overwritten with the cloned ticket. Reset the ticket here in case - we end up having to use normal insert. + 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. + */ + 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 + therefore will reopen the table and reacquire the metadata lock. */ table_list->mdl_request.ticket= NULL; @@ -1839,14 +1836,25 @@ 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. + 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 + connection thread to exit. + Access to handler_thread_initialized is protected by di->mutex. + */ + bool handler_thread_initialized; COPY_INFO info; I_List rows; ulong group_count; TABLE_LIST table_list; // Argument Delayed_insert() - :locks_in_memory(0), - table(0),tables_in_use(0),stacked_inserts(0), status(0), group_count(0) + :locks_in_memory(0), table(0),tables_in_use(0),stacked_inserts(0), + status(0), handler_thread_initialized(FALSE), group_count(0) { DBUG_ENTER("Delayed_insert constructor"); thd.security_ctx->user=thd.security_ctx->priv_user=(char*) delayed_user; @@ -2063,19 +2071,9 @@ 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; - - /* - 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. - */ - if (di->thd.mdl_context.clone_ticket(&table_list->mdl_request)) - { - delete di; - my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR)); - goto end_create; - } + /* We need the ticket so that it can be cloned in handle_delayed_insert */ + init_mdl_requests(&di->table_list); + di->table_list.mdl_request.ticket= table_list->mdl_request.ticket; di->lock(); mysql_mutex_lock(&di->mutex); @@ -2088,15 +2086,20 @@ bool delayed_get_table(THD *thd, TABLE_LIST *table_list) error)); mysql_mutex_unlock(&di->mutex); di->unlock(); - di->thd.mdl_context.release_lock(table_list->mdl_request.ticket); delete di; my_error(ER_CANT_CREATE_THREAD, MYF(ME_FATALERROR), error); goto end_create; } - /* Wait until table is open */ + /* + Wait until table is open unless the handler thread or the connection + thread has been killed. Note that we in all cases must wait until the + handler thread has been properly initialized before exiting. Otherwise + we risk doing clone_ticket() on a ticket that is no longer valid. + */ thd_proc_info(thd, "waiting for handler open"); - while (!di->thd.killed && !di->table && !thd->killed) + while (!di->handler_thread_initialized || + (!di->thd.killed && !di->table && !thd->killed)) { mysql_cond_wait(&di->cond_client, &di->mutex); } @@ -2524,6 +2527,7 @@ pthread_handler_t handle_delayed_insert(void *arg) /* Can't use my_error since store_globals has not yet been called */ thd->stmt_da->set_error_status(thd, ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), NULL); + di->handler_thread_initialized= TRUE; } else { @@ -2534,6 +2538,7 @@ pthread_handler_t handle_delayed_insert(void *arg) /* Can't use my_error since store_globals has perhaps failed */ thd->stmt_da->set_error_status(thd, ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), NULL); + di->handler_thread_initialized= TRUE; thd->fatal_error(); goto err; } @@ -2546,7 +2551,24 @@ pthread_handler_t handle_delayed_insert(void *arg) thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_INSERT_DELAYED); thd->set_current_stmt_binlog_format_row_if_mixed(); - init_mdl_requests(&di->table_list); + /* + 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. + */ + if (thd->mdl_context.clone_ticket(&di->table_list.mdl_request)) + { + di->handler_thread_initialized= TRUE; + goto err; + } + + /* + Now that the ticket has been cloned, it is safe for the connection + thread to exit. + */ + di->handler_thread_initialized= TRUE; + di->table_list.mdl_request.ticket= NULL; if (di->open_and_lock_table()) goto err; -- cgit v1.2.1 From e7b268827130b6bc5fa6c2c0f90a850ee309668c Mon Sep 17 00:00:00 2001 From: Alexey Kopytov Date: Tue, 24 Aug 2010 19:51:32 +0400 Subject: Bug #54802: 'NOT BETWEEN' evaluation is incorrect Queries involving predicates of the form "const NOT BETWEEN not_indexed_column AND indexed_column" could return wrong data due to incorrect handling by the range optimizer. For "c NOT BETWEEN f1 AND f2" predicates, get_mm_tree() produces a disjunction of the SEL_ARG trees for "f1 > c" and "f2 < c". If one of the trees is empty (i.e. one of the arguments is not sargable) the resulting tree should be empty as well, since the whole expression in this case is not sargable. The above logic is implemented in get_mm_tree() as follows. The initial state of the resulting tree is NULL (aka empty). We then iterate through arguments and compute the corresponding SEL_ARG tree (either "f1 > c" or "f2 < c"). If the resulting tree is NULL, it is simply replaced by the generated tree. Otherwise it is replaced by a disjunction of itself and the generated tree. The obvious flaw in this implementation is that if the first argument is not sargable and thus produces a NULL tree, the resulting tree will simply be replaced by the tree for the second argument. As a result, "c NOT BETWEEN f1 AND f2" will end up as just "f2 < c". Fixed by adding a check so that when the first argument produces an empty tree for the NOT BETWEEN case, the loop is aborted with an empty tree as a result. The whole idea of using a loop for 2 arguments does not make much sense, but it was probably used to avoid code duplication for several BETWEEN variants. --- sql/opt_range.cc | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'sql') diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 1f42d0567a7..eae79e63c19 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -5526,7 +5526,11 @@ static SEL_TREE *get_mm_tree(RANGE_OPT_PARAM *param,COND *cond) SEL_TREE *tmp= get_full_func_mm_tree(param, cond_func, field_item, (Item*)(intptr)i, inv); if (inv) + { tree= !tree ? tmp : tree_or(param, tree, tmp); + if (tree == NULL) + break; + } else tree= tree_and(param, tree, tmp); } -- cgit v1.2.1 From 19e67ad27c394aff12d6caa0ec85dc2343f99de0 Mon Sep 17 00:00:00 2001 From: Marc Alff Date: Tue, 24 Aug 2010 18:21:43 -0600 Subject: Bug#55576 Two perfschema tests failed on mysql-next-mr-innodb PB2 tests Before this fix, some tests failed due to lack of instrumentation slots in the performance schema, because the default sizing was too low. Now that more code has been instrumented, the default sizing has to be adjusted to match the current instrumentation consumption. This change: - increases the number of rwlock classes from 20 to 30, - increases the number of rwlock and mutex instances to 1 million. Both are to account for the volume of data instrumented when the innodb storage engine is used (because of the innodb buffer pool). Adjusted the test output accordingly. --- sql/sys_vars.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sql') diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index fdda514986b..f63a43ccd76 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -145,7 +145,7 @@ static Sys_var_ulong Sys_pfs_max_mutex_instances( "performance_schema_max_mutex_instances", "Maximum number of instrumented MUTEX objects.", READ_ONLY GLOBAL_VAR(pfs_param.m_mutex_sizing), - CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 1024*1024), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 100*1024*1024), DEFAULT(PFS_MAX_MUTEX), BLOCK_SIZE(1), PFS_TRAILING_PROPERTIES); @@ -161,7 +161,7 @@ static Sys_var_ulong Sys_pfs_max_rwlock_instances( "performance_schema_max_rwlock_instances", "Maximum number of instrumented RWLOCK objects.", READ_ONLY GLOBAL_VAR(pfs_param.m_rwlock_sizing), - CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 1024*1024), + CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 100*1024*1024), DEFAULT(PFS_MAX_RWLOCK), BLOCK_SIZE(1), PFS_TRAILING_PROPERTIES); -- cgit v1.2.1 From 800feb16cb9e3fcb363f0856581c8822bd29d549 Mon Sep 17 00:00:00 2001 From: Dmitry Shulga Date: Wed, 25 Aug 2010 15:47:45 +0700 Subject: Fixed bug #29751 - do not rename the error log at FLUSH LOGS. Added open log file with FILE_SHARE_DELETE flag on Windows. sql/log.cc: added reopen_fstreams(); modified redirect_std_streams(): call to sequence of freopen() replaced to reopen_fstreams(); modified flush_error_log(): removed file rename for flushed error log file. sql/mysqld.cc: modified main() and init_server_components(): do open log error file over call to reopen_fstreams(). --- sql/log.cc | 117 +++++++++++++++++++++++++++++++++++----------------------- sql/mysqld.cc | 19 ++++++---- 2 files changed, 82 insertions(+), 54 deletions(-) (limited to 'sql') diff --git a/sql/log.cc b/sql/log.cc index 3f41bf1c929..156c293e3aa 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -5063,70 +5063,93 @@ void sql_perror(const char *message) } +#ifdef __WIN__ +extern "C" my_bool reopen_fstreams(const char *filename, + FILE *outstream, FILE *errstream) +{ + int handle_fd; + int stream_fd; + HANDLE osfh; + + DBUG_ASSERT(filename && (outstream || errstream)); + + if ((osfh= CreateFile(filename, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | + FILE_SHARE_DELETE, NULL, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, + NULL)) == INVALID_HANDLE_VALUE) + return TRUE; + + if ((handle_fd= _open_osfhandle((intptr_t)osfh, + _O_APPEND | _O_TEXT)) == -1) + { + CloseHandle(osfh); + return TRUE; + } + + if (outstream) + { + stream_fd= _fileno(outstream); + if (_dup2(handle_fd, stream_fd) < 0) + { + CloseHandle(osfh); + return TRUE; + } + } + + if (errstream) + { + stream_fd= _fileno(errstream); + if (_dup2(handle_fd, stream_fd) < 0) + { + CloseHandle(osfh); + return TRUE; + } + } + + _close(handle_fd); + return FALSE; +} +#else +extern "C" my_bool reopen_fstreams(const char *filename, + FILE *outstream, FILE *errstream) +{ + if (outstream && !freopen(filename, "a+", outstream)) + return TRUE; + + if (errstream && !freopen(filename, "a+", errstream)) + return TRUE; + + return FALSE; +} +#endif + + /* Unfortunately, there seems to be no good way to restore the original streams upon failure. */ static bool redirect_std_streams(const char *file) { - if (freopen(file, "a+", stdout) && freopen(file, "a+", stderr)) - { - setbuf(stderr, NULL); - return FALSE; - } + if (reopen_fstreams(file, stdout, stderr)) + return TRUE; - return TRUE; + setbuf(stderr, NULL); + return FALSE; } bool flush_error_log() { - bool result=0; + bool result= 0; if (opt_error_log) { - char err_renamed[FN_REFLEN], *end; - end= strmake(err_renamed,log_error_file,FN_REFLEN-5); - strmov(end, "-old"); VOID(pthread_mutex_lock(&LOCK_error_log)); -#ifdef __WIN__ - char err_temp[FN_REFLEN+5]; - /* - On Windows is necessary a temporary file for to rename - the current error file. - */ - strxmov(err_temp, err_renamed,"-tmp",NullS); - (void) my_delete(err_temp, MYF(0)); - if (freopen(err_temp,"a+",stdout)) - { - int fd; - size_t bytes; - uchar buf[IO_SIZE]; - - freopen(err_temp,"a+",stderr); - setbuf(stderr, NULL); - (void) my_delete(err_renamed, MYF(0)); - my_rename(log_error_file,err_renamed,MYF(0)); - redirect_std_streams(log_error_file); - - if ((fd = my_open(err_temp, O_RDONLY, MYF(0))) >= 0) - { - while ((bytes= my_read(fd, buf, IO_SIZE, MYF(0))) && - bytes != MY_FILE_ERROR) - my_fwrite(stderr, buf, bytes, MYF(0)); - my_close(fd, MYF(0)); - } - (void) my_delete(err_temp, MYF(0)); - } - else - result= 1; -#else - my_rename(log_error_file,err_renamed,MYF(0)); - if (redirect_std_streams(log_error_file)) - result= 1; -#endif + if (redirect_std_streams(log_error_file)) + result= 1; VOID(pthread_mutex_unlock(&LOCK_error_log)); } - return result; + return result; } void MYSQL_BIN_LOG::signal_update() diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 08407d52e09..d9c4c7fc3f5 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -199,6 +199,9 @@ typedef fp_except fp_except_t; # endif #endif +extern "C" my_bool reopen_fstreams(const char *filename, + FILE *outstream, FILE *errstream); + inline void setup_fpu() { #if defined(__FreeBSD__) && defined(HAVE_IEEEFP_H) @@ -3821,13 +3824,15 @@ static int init_server_components() opt_error_log= 1; // Too long file name else { + my_bool res; #ifndef EMBEDDED_LIBRARY - if (freopen(log_error_file, "a+", stdout)) + res= reopen_fstreams(log_error_file, stdout, stderr); +#else + res= reopen_fstreams(log_error_file, NULL, stderr); #endif - { - if (freopen(log_error_file, "a+", stderr)) - setbuf(stderr, NULL); - } + + if (!res) + setbuf(stderr, NULL); } } @@ -4475,8 +4480,8 @@ we force server id to 2, but this MySQL server will not act as a slave."); #ifdef __WIN__ if (!opt_console) { - freopen(log_error_file,"a+",stdout); - freopen(log_error_file,"a+",stderr); + if (reopen_fstreams(log_error_file, stdout, stderr)) + unireg_abort(1); setbuf(stderr, NULL); FreeConsole(); // Remove window } -- cgit v1.2.1 From 0d9b6fbd795339064cb30992b61948675e159d69 Mon Sep 17 00:00:00 2001 From: Alexander Nozdrin Date: Wed, 25 Aug 2010 13:17:45 +0400 Subject: Revert patch for Bug#56120 temporarily. --- sql/item.cc | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) (limited to 'sql') diff --git a/sql/item.cc b/sql/item.cc index 427146e11dc..24f57342668 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -7510,14 +7510,9 @@ String *Item_cache_datetime::val_str(String *str) return NULL; if (cached_field_type == MYSQL_TYPE_TIME) { - longlong time= int_value; - set_zero_time(<ime, MYSQL_TIMESTAMP_TIME); - if (time < 0) - { - time= -time; - ltime.neg= TRUE; - } + ulonglong time= int_value; DBUG_ASSERT(time <= TIME_MAX_VALUE); + set_zero_time(<ime, MYSQL_TIMESTAMP_TIME); ltime.second= time % 100; time/= 100; ltime.minute= time % 100; -- cgit v1.2.1 From 04ae1aa9547fdad274127d97a244738c26f7e7e6 Mon Sep 17 00:00:00 2001 From: Alexey Kopytov Date: Wed, 25 Aug 2010 19:57:53 +0400 Subject: Bug#55077: Assertion failed: width > 0 && to != ((void *)0), file .\dtoa.c The assertion failure was correct because the 'width' argument of my_gcvt() has the signed integer type, whereas the unsigned value UINT_MAX32 was being passed by the caller (Field_double::val_str()) leading to a negative width in my_gcvt(). The following chain of problems was found by further analysis: 1. The display width for a floating point number is calculated in Field_double::val_str() as either field_length or the maximum possible length of string representation of a floating point number, whichever is greater. Since in the bug's test case field_length is UINT_MAX32, we get the same value as the display width. This does not make any sense because for numeric values field_length only matters for ZEROFILL columns, otherwise it does not make sense to allocate that much memory just to print a number. Field_float::val_str() has a similar problem. 2. Even if the above wasn't the case, we would still get a crash on a slightly different test case when trying to allocate UINT_MAX32 bytes with String::alloc() because the latter does not handle such large input values correctly due to alignment overflows. 3. Even when String::alloc() is fixed to return an error when an alignment overflow occurs, there is still a problem because almost no callers check its return value, and Field_double::val_str() is not an exception (same for Field_float::val_str()). 4. Even if all of the above wasn't the case, creating a Field_double object with UINT_MAX32 as its field_length does not make much sense either, since the .frm code limits it to MAX_FIELD_CHARLENGTH (255) bytes. Such a beast can only be created by create_tmp_field_from_item() from an Item with REAL_RESULT as its result_type() and UINT_MAX32 as its max_length. 5. For the bug's test case, the above condition (REAL_RESULT Item with max_length = UINT_MAX32) was a result of Item_func_if::fix_length_and_dec() "shortcutting" aggregation of argument types when one of the arguments was a constant NULL. In this case, the attributes of the aggregated type were simply copied from the other, non-NULL argument, but max_length was still calculated as per the general, non-shortcut case, by choosing the greatest of argument's max_length, which is obviously not correct. The patch addresses all of the above problems, even though fixing the assertion failure for the particular test case would require only a subset of the above problems to be solved. client/sql_string.cc: Return an error in case of uint32 overflow in alignment. Also assert there was no overflow to help find such conditions in debug builds, since almost no callers check the return value of String::alloc(). mysql-test/r/func_if.result: Add a test case for bug #55077. mysql-test/t/func_if.test: Add a test case for bug #55077. sql/field.cc: - Assert we don't operate with fields wider than 255 (MAX_FIELD_CHARLENGTH) bytes in both Field_float and Field_double. - Don't take field_length into account when calculating the output buffer length. - Check the return value of String::alloc() sql/item_cmpfunc.cc: When shortcutting type aggregation, don't take the NULL argument's max_length into account. sql/sql_string.cc: Return an error in case of uint32 overflow in alignment. Also assert there was no overflow to help find such conditions in debug builds, since almost no callers check the return value of String::alloc(). --- sql/field.cc | 21 ++++++++++++++++----- sql/item_cmpfunc.cc | 27 +++++++++++++++------------ sql/sql_string.cc | 10 ++++++++-- 3 files changed, 39 insertions(+), 19 deletions(-) (limited to 'sql') diff --git a/sql/field.cc b/sql/field.cc index fc55426b177..0e55b624633 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -4189,6 +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); float nr; #ifdef WORDS_BIGENDIAN if (table->s->db_low_byte_first) @@ -4199,8 +4200,13 @@ String *Field_float::val_str(String *val_buffer, #endif memcpy(&nr, ptr, sizeof(nr)); - uint to_length=max(field_length,70); - val_buffer->alloc(to_length); + uint to_length= 70; + if (val_buffer->alloc(to_length)) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return val_buffer; + } + char *to=(char*) val_buffer->ptr(); size_t len; @@ -4209,7 +4215,7 @@ String *Field_float::val_str(String *val_buffer, else { /* - We are safe here because the buffer length is >= 70, and + We are safe here because the buffer length is 70, and fabs(float) < 10^39, dec < NOT_FIXED_DEC. So the resulting string will be not longer than 69 chars + terminating '\0'. */ @@ -4506,6 +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); double nr; #ifdef WORDS_BIGENDIAN if (table->s->db_low_byte_first) @@ -4515,9 +4522,13 @@ String *Field_double::val_str(String *val_buffer, else #endif doubleget(nr,ptr); + uint to_length= DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE; + if (val_buffer->alloc(to_length)) + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + return val_buffer; + } - uint to_length=max(field_length, DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE); - val_buffer->alloc(to_length); char *to=(char*) val_buffer->ptr(); size_t len; diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 641d3726aca..8c0f22b0947 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -2560,27 +2560,30 @@ Item_func_if::fix_length_and_dec() cached_result_type= arg2_type; collation.set(args[2]->collation.collation); cached_field_type= args[2]->field_type(); + max_length= args[2]->max_length; + return; } - else if (null2) + + if (null2) { cached_result_type= arg1_type; collation.set(args[1]->collation.collation); cached_field_type= args[1]->field_type(); + max_length= args[1]->max_length; + return; + } + + agg_result_type(&cached_result_type, args + 1, 2); + if (cached_result_type == STRING_RESULT) + { + if (agg_arg_charsets_for_string_result(collation, args + 1, 2)) + return; } else { - agg_result_type(&cached_result_type, args+1, 2); - if (cached_result_type == STRING_RESULT) - { - if (agg_arg_charsets_for_string_result(collation, args + 1, 2)) - return; - } - else - { - collation.set_numeric(); // Number - } - cached_field_type= agg_field_type(args + 1, 2); + collation.set_numeric(); // Number } + cached_field_type= agg_field_type(args + 1, 2); uint32 char_length; if ((cached_result_type == DECIMAL_RESULT ) diff --git a/sql/sql_string.cc b/sql/sql_string.cc index 762eebba031..4b7dab243d2 100644 --- a/sql/sql_string.cc +++ b/sql/sql_string.cc @@ -31,9 +31,12 @@ ** String functions *****************************************************************************/ -bool String::real_alloc(uint32 arg_length) +bool String::real_alloc(uint32 length) { - arg_length=ALIGN_SIZE(arg_length+1); + uint32 arg_length= ALIGN_SIZE(length + 1); + DBUG_ASSERT(arg_length > length); + if (arg_length <= length) + return TRUE; /* Overflow */ str_length=0; if (Alloced_length < arg_length) { @@ -56,6 +59,9 @@ bool String::real_alloc(uint32 arg_length) bool String::realloc(uint32 alloc_length) { uint32 len=ALIGN_SIZE(alloc_length+1); + DBUG_ASSERT(len > alloc_length); + if (len <= alloc_length) + return TRUE; /* Overflow */ if (Alloced_length < len) { char *new_ptr; -- cgit v1.2.1 From 257045499ff2f90349a92df363f3e78ea9c04721 Mon Sep 17 00:00:00 2001 From: Marc Alff Date: Wed, 25 Aug 2010 13:00:38 -0600 Subject: Bug#52312 lost Handler_read_last status variable Before this fix, the ha_read_last_count status variable was defined and updated internally, for never exposed as a system variable. This fix exposes the system variable as "Handler_read_last", for completness of the Handler_read_* system variables interface. Adjusted tests results accordingly. --- sql/mysqld.cc | 1 + 1 file changed, 1 insertion(+) (limited to 'sql') diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 0cd3ecdf47d..d144f7e82aa 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -6439,6 +6439,7 @@ SHOW_VAR status_vars[]= { {"Handler_prepare", (char*) offsetof(STATUS_VAR, ha_prepare_count), SHOW_LONG_STATUS}, {"Handler_read_first", (char*) offsetof(STATUS_VAR, ha_read_first_count), SHOW_LONG_STATUS}, {"Handler_read_key", (char*) offsetof(STATUS_VAR, ha_read_key_count), SHOW_LONG_STATUS}, + {"Handler_read_last", (char*) offsetof(STATUS_VAR, ha_read_last_count), SHOW_LONG_STATUS}, {"Handler_read_next", (char*) offsetof(STATUS_VAR, ha_read_next_count), SHOW_LONG_STATUS}, {"Handler_read_prev", (char*) offsetof(STATUS_VAR, ha_read_prev_count), SHOW_LONG_STATUS}, {"Handler_read_rnd", (char*) offsetof(STATUS_VAR, ha_read_rnd_count), SHOW_LONG_STATUS}, -- cgit v1.2.1 From 47c6b6a6cf0fba56f42380f5be7529da9773bb7c Mon Sep 17 00:00:00 2001 From: Marc Alff Date: Wed, 25 Aug 2010 18:59:28 -0600 Subject: Bug#55873 short startup options do not work in 5.5 Before this fix, the server did not recognize 'short' (as in -a) options but only 'long' (as in --ansi) options in the startup command line, due to earlier changes in 5.5 introduced for the performance schema. The root cause is that handle_options() did not honor the my_getopt_skip_unknown flag when parsing 'short' options. The fix changes handle_options(), so that my_getopt_skip_unknown is honored in all cases. Note that there are limitations to this, see the added doxygen documentation in handle_options(). The current usage of handle_options() by the server to parse early performance schema options fits within the limitations. This has been enforced by an assert for PARSE_EARLY options, for safety. --- sql/set_var.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'sql') diff --git a/sql/set_var.cc b/sql/set_var.cc index 9daaf883ea8..501c6382056 100644 --- a/sql/set_var.cc +++ b/sql/set_var.cc @@ -153,6 +153,17 @@ sys_var::sys_var(sys_var_chain *chain, const char *name_arg, guard(lock), offset(off), on_check(on_check_func), on_update(on_update_func), is_os_charset(FALSE) { + /* + There is a limitation in handle_options() related to short options: + - either all short options should be declared when parsing in multiple stages, + - or none should be declared. + Because a lot of short options are used in the normal parsing phase + for mysqld, we enforce here that no short option is present + in the first (PARSE_EARLY) stage. + See handle_options() for details. + */ + DBUG_ASSERT(parse_flag == PARSE_NORMAL || getopt_id <= 0 || getopt_id >= 255); + name.str= name_arg; name.length= strlen(name_arg); DBUG_ASSERT(name.length <= NAME_CHAR_LEN); -- cgit v1.2.1 From b4dc600af919f8a7fa8403535847256161abc1db Mon Sep 17 00:00:00 2001 From: Evgeny Potemkin Date: Thu, 26 Aug 2010 13:31:04 +0400 Subject: Bug #55656: mysqldump can be slower after bug 39653 fix. After fix for bug 39653 the shortest available secondary index was used for full table scan. Primary clustered key was used only if no secondary index can be used. However, when chosen secondary index includes all fields of the table being scanned it's better to use primary index since the amount of data to scan is the same but the primary index is clustered. Now the find_shortest_key function takes this into account. mysql-test/suite/innodb/r/innodb_mysql.result: Added a test case for the bug#55656. mysql-test/suite/innodb/t/innodb_mysql.test: Added a test case for the bug#55656. sql/sql_select.cc: Bug #55656: mysqldump can be slower after bug #39653 fix. The find_shortest_key function now prefers clustered primary key if found secondary key includes all fields of the table. --- sql/sql_select.cc | 60 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 18 deletions(-) (limited to 'sql') diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 7ee1762295f..4a32ca34790 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -13017,6 +13017,34 @@ static int test_if_order_by_key(ORDER *order, TABLE *table, uint idx, } +/** + Find shortest key suitable for full table scan. + + @param table Table to scan + @param usable_keys Allowed keys + + @note + As far as + 1) clustered primary key entry data set is a set of all record + fields (key fields and not key fields) and + 2) secondary index entry data is a union of its key fields and + primary key fields (at least InnoDB and its derivatives don't + duplicate primary key fields there, even if the primary and + the secondary keys have a common subset of key fields), + then secondary index entry data is always a subset of primary key entry. + Unfortunately, key_info[nr].key_length doesn't show the length + of key/pointer pair but a sum of key field lengths only, thus + we can't estimate index IO volume comparing only this key_length + value of secondary keys and clustered PK. + So, try secondary keys first, and choose PK only if there are no + usable secondary covering keys or found best secondary key include + all table fields (i.e. same as PK): + + @return + MAX_KEY no suitable key found + key index otherwise +*/ + uint find_shortest_key(TABLE *table, const key_map *usable_keys) { uint best= MAX_KEY; @@ -13029,23 +13057,6 @@ uint find_shortest_key(TABLE *table, const key_map *usable_keys) uint min_length= (uint) ~0; for (uint nr=0; nr < table->s->keys ; nr++) { - /* - As far as - 1) clustered primary key entry data set is a set of all record - fields (key fields and not key fields) and - 2) secondary index entry data is a union of its key fields and - primary key fields (at least InnoDB and its derivatives don't - duplicate primary key fields there, even if the primary and - the secondary keys have a common subset of key fields), - then secondary index entry data is always a subset of primary key - entry, and the PK is always longer. - Unfortunately, key_info[nr].key_length doesn't show the length - of key/pointer pair but a sum of key field lengths only, thus - we can't estimate index IO volume comparing only this key_length - value of seconday keys and clustered PK. - So, try secondary keys first, and choose PK only if there are no - usable secondary covering keys: - */ if (nr == usable_clustered_pk) continue; if (usable_keys->is_set(nr)) @@ -13058,7 +13069,20 @@ uint find_shortest_key(TABLE *table, const key_map *usable_keys) } } } - return best != MAX_KEY ? best : usable_clustered_pk; + if (usable_clustered_pk != MAX_KEY) + { + /* + If the primary key is clustered and found shorter key covers all table + fields then primary key scan normally would be faster because amount of + data to scan is the same but PK is clustered. + It's safe to compare key parts with table fields since duplicate key + parts aren't allowed. + */ + if (best == MAX_KEY || + table->key_info[best].key_parts >= table->s->fields) + best= usable_clustered_pk; + } + return best; } /** -- cgit v1.2.1 From 7ebd2cd797f25013e2d2492ef6faa01fbf3cefc2 Mon Sep 17 00:00:00 2001 From: Ramil Kalimullin Date: Fri, 27 Aug 2010 11:44:06 +0400 Subject: Fix for bug #54253: memory leak when using I_S plugins w/o deinit method Free memory allocated by the server for all plugins, with or without deinit() method. --- sql/sql_show.cc | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'sql') diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 3e66cda16e1..e074461b452 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -6930,13 +6930,16 @@ int finalize_schema_table(st_plugin_int *plugin) ST_SCHEMA_TABLE *schema_table= (ST_SCHEMA_TABLE *)plugin->data; DBUG_ENTER("finalize_schema_table"); - if (schema_table && plugin->plugin->deinit) + if (schema_table) { - DBUG_PRINT("info", ("Deinitializing plugin: '%s'", plugin->name.str)); - if (plugin->plugin->deinit(NULL)) + if (plugin->plugin->deinit) { - DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.", - plugin->name.str)); + DBUG_PRINT("info", ("Deinitializing plugin: '%s'", plugin->name.str)); + if (plugin->plugin->deinit(NULL)) + { + DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.", + plugin->name.str)); + } } my_free(schema_table, MYF(0)); } -- cgit v1.2.1 From d7d0f6390b8d95b34ffb79f1a77ab11657e064a8 Mon Sep 17 00:00:00 2001 From: Alexey Kopytov Date: Fri, 27 Aug 2010 13:44:35 +0400 Subject: Bug #54465: assert: field_types == 0 || field_types[field_pos] == MYSQL_TYPE_LONGLONG A MIN/MAX() function with a subquery as its argument could lead to a debug assertion on debug builds or wrong data on release ones. The problem was a combination of the following factors: - Item_sum_hybrid::fix_fields() might use the argument (args[0]) to calculate 'hybrid_field_type' which was later used to decide how the data should be sent to the client. - Item_sum::make_field() might use the argument again to calculate the field's type when sending result set metadata to the client. - The argument could be changed in between these two calls via Item::set_arg() leading to inconsistent metadata being reported. Here is what was happening for the bug's test case: 1. Item_sum_hybrid::fix_fields() calculates hybrid_field_type as MYSQL_TYPE_LONGLONG based on args[0] which is an Item::SUBSELECT_ITEM at that time. 2. A temporary table is created to execute the query. create_tmp_field_from_item() creates a Field_long object according to the subselect's max_length. 3. The subselect item in Item_sum_hybrid is replaced by the Item_field object referencing the newly created Field_long. 4. Item_sum::make_field() rightfully returns the MYSQL_TYPE_LONG type when calculating the result set metadata. 5. When sending the actual data, Item::send() relies on the virtual field_type() function which in our case returns previously calculated hybrid_field_type == MYSQL_TYPE_LONGLONG. It looks like the only solution is to never refer to the argument's metadata after the result metadata has been calculated in fix_fields(), since the argument itself may be different by then. In this sense, Item_sum::make_field() should never be used, because it may rely on the argument's metadata and is only called after fix_fields(). The "default" implementation in Item::make_field() should be used instead as it relies only on field_type(), but not on the argument's type. Fixed by removing Item_sum::make_field() so that the superclass implementation Item::make_field() is always used. mysql-test/r/func_group.result: Added a test case for bug #54465. mysql-test/t/func_group.test: Added a test case for bug #54465. sql/item_sum.cc: Removed Item_sum::make_field() so that the superclass implementation Item::make_field() is always used. sql/item_sum.h: Removed Item_sum::make_field() so that the superclass implementation Item::make_field() is always used. --- sql/item_sum.cc | 20 -------------------- sql/item_sum.h | 1 - 2 files changed, 21 deletions(-) (limited to 'sql') diff --git a/sql/item_sum.cc b/sql/item_sum.cc index 25b3bd5d91d..ae9e46e2abf 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -417,26 +417,6 @@ void Item_sum::mark_as_sum_func() } -void Item_sum::make_field(Send_field *tmp_field) -{ - if (args[0]->type() == Item::FIELD_ITEM && keep_field_type()) - { - ((Item_field*) args[0])->field->make_field(tmp_field); - /* For expressions only col_name should be non-empty string. */ - char *empty_string= (char*)""; - tmp_field->db_name= empty_string; - tmp_field->org_table_name= empty_string; - tmp_field->table_name= empty_string; - tmp_field->org_col_name= empty_string; - tmp_field->col_name= name; - if (maybe_null) - tmp_field->flags&= ~NOT_NULL_FLAG; - } - else - init_make_field(tmp_field, field_type()); -} - - void Item_sum::print(String *str, enum_query_type query_type) { /* orig_args is not filled with valid values until fix_fields() */ diff --git a/sql/item_sum.h b/sql/item_sum.h index fe05858ab1d..26290a812f4 100644 --- a/sql/item_sum.h +++ b/sql/item_sum.h @@ -339,7 +339,6 @@ public: forced_const= TRUE; } virtual bool const_item() const { return forced_const; } - void make_field(Send_field *field); virtual void print(String *str, enum_query_type query_type); void fix_num_length_and_dec(); -- cgit v1.2.1 From ccab4d8771b728e334b26d0be1150b4ecf0bc7a6 Mon Sep 17 00:00:00 2001 From: Gleb Shchepa Date: Tue, 31 Aug 2010 02:16:38 +0400 Subject: Bug #53034: Multiple-table DELETE statements not accepting "Access compatibility" syntax The "wild" "DELETE FROM table_name.* ... USING ..." syntax for multi-table DELETE statements is documented but it was lost in the fix for the bug 30234. The table_ident_opt_wild parser rule has been added to restore the lost syntax. mysql-test/r/delete.result: Test case for bug #53034. mysql-test/t/delete.test: Test case for bug #53034. sql/sql_yacc.yy: Bug #53034: Multiple-table DELETE statements not accepting "Access compatibility" syntax The table_ident_opt_wild parser rule has been added to restore the lost syntax. Note: simple extending of table_ident with opt_wild in the table_alias_ref rule is not acceptable, because a) it adds one conflict more and b) this conflict resolves in the inappropriate way. --- sql/sql_yacc.yy | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'sql') diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index ed367582ba5..aa41a408e5b 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1297,6 +1297,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %type table_ident table_ident_nodb references xid + table_ident_opt_wild %type remember_name remember_end opt_ident opt_db text_or_password @@ -9622,7 +9623,7 @@ table_alias_ref_list: ; table_alias_ref: - table_ident + table_ident_opt_wild { if (!Select->add_table_to_list(YYTHD, $1, NULL, TL_OPTION_UPDATING | TL_OPTION_ALIAS, @@ -11405,6 +11406,21 @@ table_ident: } ; +table_ident_opt_wild: + ident opt_wild + { + $$= new Table_ident($1); + if ($$ == NULL) + MYSQL_YYABORT; + } + | ident '.' ident opt_wild + { + $$= new Table_ident(YYTHD, $1,$3,0); + if ($$ == NULL) + MYSQL_YYABORT; + } + ; + table_ident_nodb: ident { -- cgit v1.2.1 From e924c91e57846821d0a1a6d786ddbef412565f7f Mon Sep 17 00:00:00 2001 From: Daniel Fischer Date: Tue, 31 Aug 2010 13:06:56 +0200 Subject: Bug#56387 Create initial database in the proper location --- sql/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'sql') diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 224cf67af7f..9d307da364d 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -271,8 +271,7 @@ IF(WIN32 AND MYSQLD_EXECUTABLE) COMMAND ${CMAKE_COMMAND} ${CONFIG_PARAM} -P ${CMAKE_CURRENT_BINARY_DIR}/create_initial_db.cmake WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/data - COMMAND ${CMAKE_COMMAND} -E touch initdb.dep - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/initdb.dep DEPENDS mysqld ) ADD_CUSTOM_TARGET(initial_database -- cgit v1.2.1 From 02890f0b9082e41b32122e1ef3beb45872eb3b0b Mon Sep 17 00:00:00 2001 From: Alexander Nozdrin Date: Tue, 31 Aug 2010 17:47:10 +0400 Subject: Cherry-pick patch for Bug#56120 from mysql-5.5-bugfixing. --- sql/item.cc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'sql') diff --git a/sql/item.cc b/sql/item.cc index 24f57342668..427146e11dc 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -7510,9 +7510,14 @@ String *Item_cache_datetime::val_str(String *str) return NULL; if (cached_field_type == MYSQL_TYPE_TIME) { - ulonglong time= int_value; - DBUG_ASSERT(time <= TIME_MAX_VALUE); + longlong time= int_value; set_zero_time(<ime, MYSQL_TIMESTAMP_TIME); + if (time < 0) + { + time= -time; + ltime.neg= TRUE; + } + DBUG_ASSERT(time <= TIME_MAX_VALUE); ltime.second= time % 100; time/= 100; ltime.minute= time % 100; -- cgit v1.2.1 From 4283a7045845704a6748155a3c1362bf5729b96c Mon Sep 17 00:00:00 2001 From: Alexander Nozdrin Date: Tue, 31 Aug 2010 17:49:41 +0400 Subject: Cherry-picking patch for Bug#56137 from mysql-5.5-runtime. ------------------------------------------------------------ revno: 3124 revision-id: dlenev@mysql.com-20100831090419-rzr5ktekby2gspm1 parent: alik@sun.com-20100827083901-x4wvtc10u9p7gcs9 committer: Dmitry Lenev branch nick: mysql-5.5-rt-56137 timestamp: Tue 2010-08-31 13:04:19 +0400 message: Bug #56137 "Assertion `thd->lock == 0' failed on upgrading from 5.1.50 to 5.5.6". Debug builds of the server aborted due to an assertion failure when DROP DATABASE statement was run on an installation which had outdated or corrupt mysql.proc table. Particularly this affected the mysql_upgrade tool which is run as part of 5.1 to 5.5 upgrade. The problem was that sp_drop_db_routines(), which was invoked during dropping of the database, could have returned without closing and unlocking mysql.proc table in cases when this table was not up-to-date with the current server. As a result further attempt to open and lock the mysql.event table, which was necessary to complete dropping of the database, ended up with an assert. This patch solves this problem by ensuring that sp_drop_db_routines() always closes mysql.proc table and releases metadata locks on it. This is achieved by changing open_proc_table_for_update() function to close tables and release metadata locks acquired by it in case of failure. This step also makes behavior of the latter function consistent with behavior of open_proc_table_for_read()/ open_and_lock_tables(). Test case for this bug was added to sp-destruct.test. ------------------------------------------------------------ --- sql/sp.cc | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'sql') diff --git a/sql/sp.cc b/sql/sp.cc index 0265ef45a2a..8821dc9365d 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -440,6 +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(); DBUG_ENTER("open_proc_table_for_update"); table_list.init_one_table("mysql", 5, "proc", 4, "proc", TL_WRITE); @@ -450,6 +451,9 @@ static TABLE *open_proc_table_for_update(THD *thd) if (!proc_table_intact.check(table, &proc_table_def)) DBUG_RETURN(table); + close_thread_tables(thd); + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); + DBUG_RETURN(NULL); } -- cgit v1.2.1 From f052fa3aad6b5720424f2f07a4e0175901f49b9e Mon Sep 17 00:00:00 2001 From: Dmitry Lenev Date: Mon, 6 Sep 2010 21:29:02 +0400 Subject: A temporary workaround for bug #56405 "Deadlock in the MDL deadlock detector". Deadlock could have occurred when workload containing mix of DML, DDL and FLUSH TABLES statements affecting same set of tables was executed in heavily concurrent environment. This deadlock occurred when several connections tried to perform deadlock detection in metadata locking subsystem. The first connection started traversing wait-for graph, encountered sub-graph representing wait for flush, acquired LOCK_open and dived into sub-graph inspection. When it has encounterd sub-graph corresponding to wait for metadata lock and blocked while trying to acquire rd-lock on MDL_lock::m_rwlock (*) protecting this subgraph, since some other thread had wr-lock on it. When this wr-lock was released it could have happened (if there was other pending wr-lock against this rwlock) that rd-lock from the first connection was left unsatisfied but at the same time new rd-lock request from the second connection sneaked in and was satisfied (for this to be possible second rd- request should come exactly after wr-lock is released but before pending wr-lock manages to grab rwlock, which is possible both on Linux and in our own rwlock implementation). If this second connection continued traversing wait-for graph and encountered sub-graph representing wait for flush it tried to acquire LOCK_open and thus deadlock was created. This patch tries to workaround this problem but not allowing deadlock detector to lock LOCK_open mutex if some other thread doing deadlock detection already owns it and current search depth is greater than 0. Instead deadlock is reported. Other possible solutions are either known to have negative effects on performance or require much more time for proper implementation and testing. No test case is provided as this bug is very hard to repeat in MTR environment but is repeatable with the help of RQG tests. sql/mdl.cc: Moved Deadlock_detection_visitor::m_current_search_depth to parent class to make it available in TABLE_SHARE::visit_subgraph(). Added MDL_wait_for_graph_visitor::abort_traversal() method which allows to abort traversal of a wait-for graph and report a deadlock. sql/mdl.h: Moved Deadlock_detection_visitor::m_current_search_depth to parent class to make it available in TABLE_SHARE::visit_subgraph(). Added MDL_wait_for_graph_visitor::abort_traversal() method which allows to abort traversal of a wait-for graph and report a deadlock. sql/sql_base.cc: Added dd_owns_lock_open counter and mutex protecting it to track number of connections which do deadlock detection and own or try to acquire LOCK_open. sql/sql_base.h: Added dd_owns_lock_open counter and mutex protecting it to track number of connections which do deadlock detection and own or try to acquire LOCK_open. sql/table.cc: Workaround bug #56405 but not allowing MDL deadlock detector to lock LOCK_open mutex if some other thread doing deadlock detection already owns it and current search depth is greater than 0. Instead report deadlock. --- sql/mdl.cc | 34 ++++++++++++++++++++++++---------- sql/mdl.h | 16 +++++++++++++++- sql/sql_base.cc | 4 ++++ sql/sql_base.h | 2 ++ sql/table.cc | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 78 insertions(+), 11 deletions(-) (limited to 'sql') diff --git a/sql/mdl.cc b/sql/mdl.cc index d53ddcee0c8..aa7c2a4b7f2 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -124,7 +124,6 @@ public: Deadlock_detection_visitor(MDL_context *start_node_arg) : m_start_node(start_node_arg), m_victim(NULL), - m_current_search_depth(0), m_found_deadlock(FALSE) {} virtual bool enter_node(MDL_context *node); @@ -133,6 +132,8 @@ public: virtual bool inspect_edge(MDL_context *dest); MDL_context *get_victim() const { return m_victim; } + + void abort_traversal(MDL_context *node); private: /** Change the deadlock victim to a new one if it has lower deadlock @@ -147,13 +148,6 @@ private: MDL_context *m_start_node; /** If a deadlock is found, the context that identifies the victim. */ MDL_context *m_victim; - /** Set to the 0 at start. Increased whenever - we descend into another MDL context (aka traverse to the next - wait-for graph node). When MAX_SEARCH_DEPTH is reached, we - assume that a deadlock is found, even if we have not found a - loop. - */ - uint m_current_search_depth; /** TRUE if we found a deadlock. */ bool m_found_deadlock; /** @@ -187,7 +181,7 @@ private: bool Deadlock_detection_visitor::enter_node(MDL_context *node) { - m_found_deadlock= ++m_current_search_depth >= MAX_SEARCH_DEPTH; + m_found_deadlock= m_current_search_depth >= MAX_SEARCH_DEPTH; if (m_found_deadlock) { DBUG_ASSERT(! m_victim); @@ -207,7 +201,6 @@ bool Deadlock_detection_visitor::enter_node(MDL_context *node) void Deadlock_detection_visitor::leave_node(MDL_context *node) { - --m_current_search_depth; if (m_found_deadlock) opt_change_victim_to(node); } @@ -251,6 +244,21 @@ Deadlock_detection_visitor::opt_change_victim_to(MDL_context *new_victim) } +/** + Abort traversal of a wait-for graph and report a deadlock. + + @param node Node which we were about to visit when abort + was initiated. +*/ + +void Deadlock_detection_visitor::abort_traversal(MDL_context *node) +{ + DBUG_ASSERT(! m_victim); + m_found_deadlock= TRUE; + opt_change_victim_to(node); +} + + /** Get a bit corresponding to enum_mdl_type value in a granted/waiting bitmaps and compatibility matrices. @@ -2056,8 +2064,13 @@ bool MDL_lock::visit_subgraph(MDL_ticket *waiting_ticket, are visiting it but this is OK: in the worst case we might do some extra work and one more context might be chosen as a victim. */ + ++gvisitor->m_current_search_depth; + if (gvisitor->enter_node(src_ctx)) + { + --gvisitor->m_current_search_depth; goto end; + } /* We do a breadth-first search first -- that is, inspect all @@ -2114,6 +2127,7 @@ bool MDL_lock::visit_subgraph(MDL_ticket *waiting_ticket, end_leave_node: gvisitor->leave_node(src_ctx); + --gvisitor->m_current_search_depth; end: mysql_prlock_unlock(&m_rwlock); diff --git a/sql/mdl.h b/sql/mdl.h index 7938d833eac..e1d4cf74dd6 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -385,7 +385,10 @@ public: virtual bool inspect_edge(MDL_context *dest) = 0; virtual ~MDL_wait_for_graph_visitor(); - MDL_wait_for_graph_visitor() :m_lock_open_count(0) {} + MDL_wait_for_graph_visitor() :m_lock_open_count(0), + m_current_search_depth(0) + { } + virtual void abort_traversal(MDL_context *node) = 0; public: /** XXX, hack: During deadlock search, we may need to @@ -396,6 +399,17 @@ public: LOCK_open since it has significant performance impacts. */ uint m_lock_open_count; + /** + Set to the 0 at start. Increased whenever + we descend into another MDL context (aka traverse to the next + wait-for graph node). When MAX_SEARCH_DEPTH is reached, we + assume that a deadlock is found, even if we have not found a + loop. + + XXX: This member belongs to this class only temporarily until + bug #56405 is fixed. + */ + uint m_current_search_depth; }; /** diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 01ab0b6dec5..b168f561954 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -100,6 +100,8 @@ bool No_such_table_error_handler::safely_trapped_errors() TABLE_SHAREs, refresh_version and the table id counter. */ mysql_mutex_t LOCK_open; +mysql_mutex_t LOCK_dd_owns_lock_open; +uint dd_owns_lock_open= 0; #ifdef HAVE_PSI_INTERFACE static PSI_mutex_key key_LOCK_open; @@ -298,6 +300,7 @@ bool table_def_init(void) init_tdc_psi_keys(); #endif mysql_mutex_init(key_LOCK_open, &LOCK_open, MY_MUTEX_INIT_FAST); + mysql_mutex_init(NULL, &LOCK_dd_owns_lock_open, MY_MUTEX_INIT_FAST); oldest_unused_share= &end_of_unused_share; end_of_unused_share.prev= &oldest_unused_share; @@ -341,6 +344,7 @@ void table_def_free(void) table_def_inited= 0; /* Free table definitions. */ my_hash_free(&table_def_cache); + mysql_mutex_destroy(&LOCK_dd_owns_lock_open); mysql_mutex_destroy(&LOCK_open); } DBUG_VOID_RETURN; diff --git a/sql/sql_base.h b/sql/sql_base.h index ff935c3fc09..2e4554313e5 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -71,6 +71,8 @@ enum enum_tdc_remove_table_type {TDC_RT_REMOVE_ALL, TDC_RT_REMOVE_NOT_OWN, bool check_dup(const char *db, const char *name, TABLE_LIST *tables); extern mysql_mutex_t LOCK_open; +extern mysql_mutex_t LOCK_dd_owns_lock_open; +extern uint dd_owns_lock_open; bool table_cache_init(void); void table_cache_free(void); bool table_def_init(void); diff --git a/sql/table.cc b/sql/table.cc index f08a0aa84ca..030de6719c7 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -3085,7 +3085,30 @@ bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush, holding a write-lock on MDL_lock::m_rwlock. */ if (gvisitor->m_lock_open_count++ == 0) + { + /* + To circumvent bug #56405 "Deadlock in the MDL deadlock detector" + we don't try to lock LOCK_open mutex if some thread doing + deadlock detection already owns it and current search depth is + greater than 0. Instead we report a deadlock. + + TODO/FIXME: The proper fix for this bug is to use rwlocks for + protection of table shares/instead of LOCK_open. + Unfortunately it requires more effort/has significant + performance effect. + */ + mysql_mutex_lock(&LOCK_dd_owns_lock_open); + if (gvisitor->m_current_search_depth > 0 && dd_owns_lock_open > 0) + { + mysql_mutex_unlock(&LOCK_dd_owns_lock_open); + --gvisitor->m_lock_open_count; + gvisitor->abort_traversal(src_ctx); + return TRUE; + } + ++dd_owns_lock_open; + mysql_mutex_unlock(&LOCK_dd_owns_lock_open); mysql_mutex_lock(&LOCK_open); + } I_P_List_iterator tables_it(used_tables); @@ -3100,8 +3123,12 @@ bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush, goto end; } + ++gvisitor->m_current_search_depth; if (gvisitor->enter_node(src_ctx)) + { + --gvisitor->m_current_search_depth; goto end; + } while ((table= tables_it++)) { @@ -3124,10 +3151,16 @@ bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush, end_leave_node: gvisitor->leave_node(src_ctx); + --gvisitor->m_current_search_depth; end: if (gvisitor->m_lock_open_count-- == 1) + { mysql_mutex_unlock(&LOCK_open); + mysql_mutex_lock(&LOCK_dd_owns_lock_open); + --dd_owns_lock_open; + mysql_mutex_unlock(&LOCK_dd_owns_lock_open); + } return result; } -- cgit v1.2.1