summaryrefslogtreecommitdiffstats
path: root/sql/sql_reload.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/sql_reload.cc')
-rw-r--r--sql/sql_reload.cc656
1 files changed, 656 insertions, 0 deletions
diff --git a/sql/sql_reload.cc b/sql/sql_reload.cc
new file mode 100644
index 00000000..8f0f15a9
--- /dev/null
+++ b/sql/sql_reload.cc
@@ -0,0 +1,656 @@
+/* Copyright (c) 2010, 2016, Oracle and/or its affiliates.
+ Copyright (c) 2011, 2016, MariaDB
+
+ 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-1335 USA */
+
+#include "mariadb.h"
+#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 "thread_cache.h"
+#include "sql_base.h" // close_cached_tables
+#include "sql_parse.h" // check_single_table_access
+#include "sql_db.h" // my_dbopt_cleanup
+#include "hostname.h" // hostname_cache_refresh
+#include "sql_repl.h" // reset_master, reset_slave
+#include "rpl_mi.h" // Master_info::data_lock
+#include "sql_show.h"
+#include "debug_sync.h"
+#include "des_key_file.h"
+#include "transaction.h"
+#ifdef WITH_WSREP
+#include "wsrep_mysqld.h"
+#endif
+
+static void disable_checkpoints(THD *thd);
+
+/**
+ 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 < 0 if there was an error while interacting with the binary log inside
+ reload_acl_and_cache,
+ 0 if we should not write to the binary log,
+ > 0 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 long options,
+ TABLE_LIST *tables, int *write_to_binlog)
+{
+ bool result=0;
+ select_errors=0; /* Write if more errors */
+ int tmp_write_to_binlog= *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 (unlikely(!thd) && (thd= (tmp_thd= new THD(0))))
+ {
+ thd->thread_stack= (char*) &tmp_thd;
+ thd->store_globals();
+ }
+
+ if (likely(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));
+ }
+ }
+ opt_noacl= 0;
+
+ if (unlikely(tmp_thd))
+ {
+ delete tmp_thd;
+ 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 (unlikely(flush_error_log()))
+ result= 1;
+
+ if ((options & REFRESH_SLOW_LOG) && global_system_variables.sql_log_slow)
+ logger.flush_slow_log();
+
+ if ((options & REFRESH_GENERAL_LOG) && opt_log)
+ logger.flush_general_log();
+
+ if (options & REFRESH_ENGINE_LOG)
+ if (ha_flush_logs())
+ 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())
+ {
+ DYNAMIC_ARRAY *drop_gtid_domain=
+ (thd && (thd->lex->delete_gtid_domain.elements > 0)) ?
+ &thd->lex->delete_gtid_domain : NULL;
+ if (mysql_bin_log.rotate_and_purge(true, drop_gtid_domain))
+ *write_to_binlog= -1;
+
+ /* Note that WSREP(thd) might not be true here e.g. during
+ SST. */
+ if (WSREP_ON)
+ {
+ /* Wait for last binlog checkpoint event to be logged. */
+ mysql_bin_log.wait_for_last_checkpoint_event();
+ }
+ }
+ }
+ if (options & REFRESH_RELAY_LOG)
+ {
+#ifdef HAVE_REPLICATION
+ LEX_CSTRING connection_name;
+ Master_info *mi;
+ if (thd)
+ connection_name= thd->lex->relay_log_connection_name;
+ else
+ {
+ connection_name.str= (char*) "";
+ connection_name.length= 0;
+ }
+
+ /*
+ Writing this command to the binlog may cause problems as the
+ slave is not likely to have the same connection names.
+ */
+ tmp_write_to_binlog= 0;
+ if (connection_name.length == 0)
+ {
+ if (master_info_index->flush_all_relay_logs())
+ *write_to_binlog= -1;
+ }
+ else if (!(mi= (get_master_info(&connection_name,
+ Sql_condition::WARN_LEVEL_ERROR))))
+ {
+ result= 1;
+ }
+ else
+ {
+ mysql_mutex_lock(&mi->data_lock);
+ if (rotate_relay_log(mi))
+ *write_to_binlog= -1;
+ mysql_mutex_unlock(&mi->data_lock);
+ mi->release();
+ }
+#endif
+ }
+#ifdef HAVE_QUERY_CACHE
+ if (options & REFRESH_QUERY_CACHE_FREE)
+ {
+ query_cache.pack(thd); // 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->ull_hash.records ||
+ thd->global_read_lock.is_acquired() ||
+ thd->mdl_backup_lock ||
+ thd->current_backup_stage != BACKUP_FINISHED
+ );
+
+ /*
+ 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)
+ {
+ DBUG_ASSERT(!(options & REFRESH_FAST) && !tables);
+ /*
+ 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 (flush_tables(thd, FLUSH_ALL))
+ {
+ /*
+ NOTE: my_error() has been already called by reopen_tables() within
+ close_cached_tables().
+ */
+ thd->global_read_lock.unlock_global_read_lock(thd);
+ return 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;
+ }
+ if (options & REFRESH_CHECKPOINT)
+ disable_checkpoints(thd);
+ /*
+ We need to do it second time after wsrep appliers were blocked in
+ make_global_read_lock_block_commit(thd) above since they could have
+ modified the tables too.
+ */
+ if (WSREP(thd) && flush_tables(thd, FLUSH_ALL))
+ result= 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)
+ {
+ int err;
+ for (TABLE_LIST *t= tables; t; t= t->next_local)
+ if (!find_table_for_mdl_upgrade(thd, t->db.str, t->table_name.str, &err))
+ {
+ if (is_locked_view(thd, t))
+ t->next_local= t->next_global;
+ else
+ {
+ my_error(err, MYF(0), t->table_name.str);
+ return 1;
+ }
+ }
+ }
+ else
+ {
+ /*
+ It is not safe to upgrade the metadata lock without GLOBAL IX lock.
+ This can happen with FLUSH TABLES <list> WITH READ LOCK as we in
+ these cases don't take a GLOBAL IX lock in order to be compatible
+ with global read lock.
+ */
+ if (thd->open_tables &&
+ !thd->mdl_context.is_lock_owner(MDL_key::BACKUP, "", "",
+ MDL_BACKUP_DDL))
+ {
+ my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0),
+ thd->open_tables->s->table_name.str);
+ return true;
+ }
+
+ 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;
+ }
+ }
+ }
+ }
+
+#ifdef WITH_WSREP
+ /* In case of applier thread, do not call flush tables */
+ if (!thd || !thd->wsrep_applier)
+#endif /* WITH_WSREP */
+ {
+ if (close_cached_tables(thd, tables,
+ ((options & REFRESH_FAST) ? FALSE : TRUE),
+ (thd ? thd->variables.lock_wait_timeout :
+ LONG_TIMEOUT)))
+ {
+ /*
+ NOTE: my_error() has been already called by reopen_tables() within
+ close_cached_tables().
+ */
+ result= 1;
+ }
+ }
+ }
+ my_dbopt_cleanup();
+ }
+ if (options & REFRESH_HOSTS)
+ hostname_cache_refresh();
+ if (thd && (options & REFRESH_STATUS))
+ refresh_status(thd);
+ if (options & REFRESH_THREADS)
+ thread_cache.flush();
+#ifdef HAVE_REPLICATION
+ if (options & REFRESH_MASTER)
+ {
+ DBUG_ASSERT(thd);
+ tmp_write_to_binlog= 0;
+ if (reset_master(thd, NULL, 0, thd->lex->next_binlog_file_number))
+ {
+ /* NOTE: my_error() has been already called by reset_master(). */
+ result= 1;
+ }
+ }
+#endif
+#ifdef HAVE_OPENSSL
+ if (options & REFRESH_DES_KEY_FILE)
+ {
+ if (des_key_file && load_des_key_file(des_key_file))
+ {
+ /* NOTE: my_error() has been already called by load_des_key_file(). */
+ result= 1;
+ }
+ }
+#endif
+#ifdef HAVE_REPLICATION
+ if (options & REFRESH_SLAVE)
+ {
+ LEX_MASTER_INFO* lex_mi= &thd->lex->mi;
+ Master_info *mi;
+ tmp_write_to_binlog= 0;
+
+ if (!(mi= get_master_info(&lex_mi->connection_name,
+ Sql_condition::WARN_LEVEL_ERROR)))
+ {
+ result= 1;
+ }
+ else
+ {
+ /* The following will fail if slave is running */
+ if (reset_slave(thd, mi))
+ {
+ mi->release();
+ /* NOTE: my_error() has been already called by reset_slave(). */
+ result= 1;
+ }
+ else if (mi->connection_name.length && thd->lex->reset_slave_info.all)
+ {
+ /* If not default connection and 'all' is used */
+ mi->release();
+ mysql_mutex_lock(&LOCK_active_mi);
+ if (master_info_index->remove_master_info(mi, 0))
+ result= 1;
+ mysql_mutex_unlock(&LOCK_active_mi);
+ }
+ else
+ mi->release();
+ }
+ }
+#endif
+ if (options & REFRESH_USER_RESOURCES)
+ reset_mqh((LEX_USER *) NULL, 0); /* purecov: inspected */
+ if (options & REFRESH_SSL)
+ {
+ if (reinit_ssl())
+ result= 1;
+#ifdef WITH_WSREP
+ if (!result &&
+ WSREP_ON && wsrep_reload_ssl())
+ {
+ my_message(ER_UNKNOWN_ERROR, "Failed to refresh WSREP SSL.", MYF(0));
+ result= 1;
+ }
+#endif
+ }
+ if (options & REFRESH_GENERIC)
+ {
+ List_iterator_fast<LEX_CSTRING> li(thd->lex->view_list);
+ LEX_CSTRING *ls;
+ while ((ls= li++))
+ {
+ ST_SCHEMA_TABLE *table= find_schema_table(thd, ls);
+ if (table->reset_table())
+ result= 1;
+ }
+ }
+ if (*write_to_binlog != -1)
+ *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 <table_list> WITH READ LOCK
+ and FLUSH TABLES <table_list> FOR EXPORT
+
+ 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 <list> 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 <list>
+ -------------------------------------
+ - you can't flush WITH READ LOCK a non-existent table
+ - you can't flush WITH READ LOCK under LOCK TABLES
+
+ 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.
+
+ Handling of MERGE tables
+ ------------------------
+ For MERGE table this statement will open and lock child tables
+ for read (it is impossible to lock parent table without it).
+ Child tables won't be flushed unless they are explicitly present
+ in the statement's table list.
+
+ 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;
+
+ /*
+ 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;
+ }
+
+ if (thd->current_backup_stage != BACKUP_FINISHED)
+ {
+ my_error(ER_BACKUP_LOCK_IS_ACTIVE, MYF(0));
+ goto error;
+ }
+
+ /* Should not flush tables while BACKUP LOCK is active */
+ if (thd->mdl_backup_lock)
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ goto error;
+ }
+
+ if (thd->lex->type & REFRESH_READ_LOCK)
+ {
+ /*
+ Acquire SNW locks on tables to be flushed. Don't acquire global
+ IX and database-scope IX locks on the tables as this will make
+ this statement incompatible with FLUSH TABLES WITH READ LOCK.
+ */
+ if (lock_table_names(thd, all_tables, NULL,
+ thd->variables.lock_wait_timeout,
+ MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK))
+ goto error;
+
+ DEBUG_SYNC(thd,"flush_tables_with_read_lock_after_acquire_locks");
+
+ /* Reset ticket to satisfy asserts in open_tables(). */
+ for (auto table_list= all_tables; table_list;
+ table_list= table_list->next_global)
+ table_list->mdl_request.ticket= NULL;
+ }
+
+ thd->variables.option_bits|= OPTION_TABLE_LOCK;
+
+ /*
+ 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_OPEN_IGNORE_FLUSH flag to it is important.
+ Also we don't pass MYSQL_OPEN_HAS_MDL_LOCK flag as we want
+ to open underlying tables if merge table is flushed.
+ For underlying tables of the merge the below call has to
+ acquire SNW locks to ensure that they can be locked for
+ read without further waiting.
+ */
+ if (open_and_lock_tables(thd, all_tables, FALSE,
+ MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK,
+ &lock_tables_prelocking_strategy))
+ goto error_reset_bits;
+
+ if (thd->lex->type & (REFRESH_FOR_EXPORT|REFRESH_READ_LOCK))
+ {
+ for (TABLE_LIST *table_list= all_tables; table_list;
+ table_list= table_list->next_global)
+ {
+ if (table_list->belong_to_view &&
+ check_single_table_access(thd, PRIV_LOCK_TABLES, table_list, FALSE))
+ {
+ table_list->hide_view_error(thd);
+ goto error_reset_bits;
+ }
+ if (table_list->is_view_or_derived())
+ continue;
+ if (thd->lex->type & REFRESH_FOR_EXPORT &&
+ table_list->table &&
+ !(table_list->table->file->ha_table_flags() & HA_CAN_EXPORT))
+ {
+ my_error(ER_ILLEGAL_HA, MYF(0),table_list->table->file->table_type(),
+ table_list->db.str, table_list->table_name.str);
+ goto error_reset_bits;
+ }
+ if (thd->lex->type & REFRESH_READ_LOCK &&
+ table_list->table &&
+ table_list->table->file->extra(HA_EXTRA_FLUSH))
+ goto error_reset_bits;
+ }
+ }
+
+ if (thd->locked_tables_list.init_locked_tables(thd))
+ goto error_reset_bits;
+
+
+ /*
+ 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_reset_bits:
+ trans_rollback_stmt(thd);
+ close_thread_tables(thd);
+ thd->variables.option_bits&= ~OPTION_TABLE_LOCK;
+error:
+ return TRUE;
+}
+
+
+/**
+ Disable checkpoints for all handlers
+ This is released in unlock_global_read_lock()
+*/
+
+static void disable_checkpoints(THD *thd)
+{
+ if (!thd->global_disable_checkpoint)
+ {
+ thd->global_disable_checkpoint= 1;
+ if (!global_disable_checkpoint++)
+ ha_checkpoint_state(1); // Disable checkpoints
+ }
+}