/* Copyright (c) 2000, 2019, 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, version 2.0, as published by the Free Software Foundation. This program is also distributed with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have included with MySQL. 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, version 2.0, 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/sql_class.h" #include #include #include #include #include #include #include "m_ctype.h" #include "m_string.h" #include "my_compiler.h" #include "my_dbug.h" #include "mysql/components/services/log_builtins.h" // LogErr #include "mysql/components/services/psi_error_bits.h" #include "mysql/psi/mysql_cond.h" #include "mysql/psi/mysql_error.h" #include "mysql/psi/mysql_ps.h" #include "mysql/psi/mysql_stage.h" #include "mysql/psi/mysql_statement.h" #include "mysql/service_mysql_alloc.h" #include "mysys_err.h" // EE_OUTOFMEMORY #include "pfs_statement_provider.h" #include "sql/auth/sql_security_ctx.h" #include "sql/binlog.h" #include "sql/check_stack.h" #include "sql/conn_handler/connection_handler_manager.h" // Connection_handler_manager #include "sql/current_thd.h" #include "sql/dd/cache/dictionary_client.h" // Dictionary_client #include "sql/dd/dd_kill_immunizer.h" // dd:DD_kill_immunizer #include "sql/debug_sync.h" // DEBUG_SYNC #include "sql/derror.h" // ER_THD #include "sql/error_handler.h" // Internal_error_handler #include "sql/item_func.h" // user_var_entry #include "sql/lock.h" // mysql_lock_abort_for_thread #include "sql/locking_service.h" // release_all_locking_service_locks #include "sql/log_event.h" #include "sql/mdl_context_backup.h" // MDL context backup for XA #include "sql/mysqld.h" // global_system_variables ... #include "sql/mysqld_thd_manager.h" // Global_THD_manager #include "sql/parse_location.h" #include "sql/protocol.h" #include "sql/protocol_classic.h" #include "sql/psi_memory_key.h" #include "sql/query_result.h" #include "sql/rpl_rli.h" // Relay_log_info #include "sql/rpl_slave.h" // rpl_master_erroneous_autoinc #include "sql/rpl_transaction_write_set_ctx.h" #include "sql/sp_cache.h" // sp_cache_clear #include "sql/sp_head.h" // sp_head #include "sql/sql_audit.h" // mysql_audit_free_thd #include "sql/sql_backup_lock.h" // release_backup_lock #include "sql/sql_base.h" // close_temporary_tables #include "sql/sql_callback.h" // MYSQL_CALLBACK #include "sql/sql_handler.h" // mysql_ha_cleanup #include "sql/sql_lex.h" #include "sql/sql_parse.h" // is_update_query #include "sql/sql_plugin.h" // plugin_thdvar_init #include "sql/sql_prepare.h" // Prepared_statement #include "sql/sql_profile.h" #include "sql/sql_time.h" // my_timeval_trunc #include "sql/sql_timer.h" // thd_timer_destroy #include "sql/strfunc.h" #include "sql/table.h" #include "sql/tc_log.h" #include "sql/thr_malloc.h" #include "sql/transaction.h" // trans_rollback #include "sql/transaction_info.h" #include "sql/xa.h" #include "thr_mutex.h" using std::max; using std::min; using std::unique_ptr; /* The following is used to initialise Table_ident with a internal table name */ char empty_c_string[1] = {0}; /* used for not defined db */ LEX_STRING NULL_STR = {NULL, 0}; LEX_CSTRING EMPTY_CSTR = {"", 0}; LEX_CSTRING NULL_CSTR = {NULL, 0}; const char *const THD::DEFAULT_WHERE = "field list"; extern PSI_stage_info stage_waiting_for_disk_space; void THD::Transaction_state::backup(THD *thd) { this->m_sql_command = thd->lex->sql_command; this->m_trx = thd->get_transaction(); thd->backup_ha_data(&this->m_ha_data); this->m_tx_isolation = thd->tx_isolation; this->m_tx_read_only = thd->tx_read_only; this->m_thd_option_bits = thd->variables.option_bits; this->m_sql_mode = thd->variables.sql_mode; this->m_transaction_psi = thd->m_transaction_psi; this->m_server_status = thd->server_status; this->m_in_lock_tables = thd->in_lock_tables; this->m_time_zone_used = thd->time_zone_used; this->m_transaction_rollback_request = thd->transaction_rollback_request; } void THD::Transaction_state::restore(THD *thd) { thd->set_transaction(this->m_trx); thd->restore_ha_data(this->m_ha_data); thd->tx_isolation = this->m_tx_isolation; thd->variables.sql_mode = this->m_sql_mode; thd->tx_read_only = this->m_tx_read_only; thd->variables.option_bits = this->m_thd_option_bits; thd->m_transaction_psi = this->m_transaction_psi; thd->server_status = this->m_server_status; thd->lex->sql_command = this->m_sql_command; thd->in_lock_tables = this->m_in_lock_tables; thd->time_zone_used = this->m_time_zone_used; thd->transaction_rollback_request = this->m_transaction_rollback_request; } THD::Attachable_trx::Attachable_trx(THD *thd, Attachable_trx *prev_trx) : m_thd(thd), m_reset_lex(RESET_LEX), m_prev_attachable_trx(prev_trx), m_trx_state() { // Save the transaction state. m_trx_state.backup(m_thd); // Save and reset query-tables-list and reset the sql-command. // // NOTE: ha_innobase::store_lock() takes the current sql-command into account. // It must be SQLCOM_SELECT. // // Do NOT reset LEX if we're running tests. LEX is used by SELECT statements. bool reset = (m_reset_lex == RESET_LEX ? true : false); if (DBUG_EVALUATE_IF("use_attachable_trx", false, reset)) { m_thd->lex->reset_n_backup_query_tables_list( m_trx_state.m_query_tables_list); m_thd->lex->sql_command = SQLCOM_SELECT; } // Save and reset open-tables. m_thd->reset_n_backup_open_tables_state(&m_trx_state.m_open_tables_state, Open_tables_state::SYSTEM_TABLES); // Reset transaction state. m_thd->m_transaction.release(); // it's been backed up. DBUG_EXECUTE_IF("after_delete_wait", { const char act[] = "now SIGNAL leader_reached WAIT_FOR leader_proceed"; DBUG_ASSERT(!debug_sync_set_action(m_thd, STRING_WITH_LEN(act))); DBUG_SET("-d,after_delete_wait"); DBUG_SET("-d,block_leader_after_delete"); };); m_thd->m_transaction.reset(new Transaction_ctx()); // Prepare for a new attachable transaction for read-only DD-transaction. // LOCK_thd_data must be locked to prevent e.g. KILL CONNECTION from // reading ha_data after clear() but before resize(). mysql_mutex_lock(&m_thd->LOCK_thd_data); m_thd->ha_data.clear(); m_thd->ha_data.resize(m_thd->ha_data.capacity()); mysql_mutex_unlock(&m_thd->LOCK_thd_data); // The attachable transaction must used READ COMMITTED isolation level. m_thd->tx_isolation = ISO_READ_COMMITTED; // The attachable transaction must be read-only. m_thd->tx_read_only = true; // The attachable transaction must be AUTOCOMMIT. m_thd->variables.option_bits |= OPTION_AUTOCOMMIT; m_thd->variables.option_bits &= ~OPTION_NOT_AUTOCOMMIT; m_thd->variables.option_bits &= ~OPTION_BEGIN; // Nothing should be binlogged from attachable transactions and disabling // the binary log allows skipping some code related to figuring out what // log format should be used. m_thd->variables.option_bits &= ~OPTION_BIN_LOG; // Possible parent's involvement to multi-statement transaction is masked m_thd->server_status &= ~SERVER_STATUS_IN_TRANS; m_thd->server_status &= ~SERVER_STATUS_IN_TRANS_READONLY; // Reset SQL_MODE during system operations. m_thd->variables.sql_mode = 0; // Reset transaction instrumentation. m_thd->m_transaction_psi = NULL; // Reset THD::in_lock_tables so InnoDB won't start acquiring table locks. m_thd->in_lock_tables = false; // Reset @@session.time_zone usage indicator for consistency. m_thd->time_zone_used = false; /* InnoDB can ask to start attachable transaction while rolling back the regular transaction. Reset rollback request flag to avoid it influencing attachable transaction we are initiating. */ m_thd->transaction_rollback_request = false; } THD::Attachable_trx::~Attachable_trx() { // Ensure that the SE didn't request rollback in the attachable transaction. // Having THD::transaction_rollback_request set most likely means that we've // experienced some sort of deadlock/timeout while processing the attachable // transaction. That is not possible by the definition of an attachable // transaction. DBUG_ASSERT(!m_thd->transaction_rollback_request); // Commit the attachable transaction before discarding transaction state. // This is mostly needed to properly reset transaction state in SE. // Note: We can't rely on InnoDB hack which auto-magically commits InnoDB // transaction when the last table for a statement in auto-commit mode is // unlocked. Apparently it doesn't work correctly in some corner cases // (for example, when statement is killed just after tables are locked but // before any other operations on the table happes). We try not to rely on // it in other places on SQL-layer as well. trans_commit_attachable(m_thd); // Close all the tables that are open till now. close_thread_tables(m_thd); // Cleanup connection specific state which was created for attachable // transaction (for InnoDB removes cached transaction object). // // Note that we need to call handlerton::close_connection for all SEs // and not only SEs which participated in attachable transaction since // connection specific state can be created when TABLE object is simply // expelled from the Table Cache (e.g. this happens for MyISAM). ha_close_connection(m_thd); // Restore the transaction state. m_trx_state.restore(m_thd); m_thd->restore_backup_open_tables_state(&m_trx_state.m_open_tables_state); bool reset = (m_reset_lex == RESET_LEX ? true : false); if (DBUG_EVALUATE_IF("use_attachable_trx", false, reset)) { m_thd->lex->restore_backup_query_tables_list( m_trx_state.m_query_tables_list); } } THD::Attachable_trx_rw::Attachable_trx_rw(THD *thd) : Attachable_trx(thd, nullptr) { m_thd->tx_read_only = false; m_thd->lex->sql_command = SQLCOM_END; thd->get_transaction()->xid_state()->set_state(XID_STATE::XA_NOTR); } void THD::enter_stage(const PSI_stage_info *new_stage, PSI_stage_info *old_stage, const char *calling_func MY_ATTRIBUTE((unused)), const char *calling_file, const unsigned int calling_line) { DBUG_PRINT("THD::enter_stage", ("'%s' %s:%d", new_stage ? new_stage->m_name : "", calling_file, calling_line)); if (old_stage != NULL) { old_stage->m_key = m_current_stage_key; old_stage->m_name = proc_info; } if (new_stage != NULL) { const char *msg = new_stage->m_name; #if defined(ENABLED_PROFILING) profiling->status_change(msg, calling_func, calling_file, calling_line); #endif m_current_stage_key = new_stage->m_key; proc_info = msg; m_stage_progress_psi = MYSQL_SET_STAGE(m_current_stage_key, calling_file, calling_line); } else { m_stage_progress_psi = NULL; } return; } void Open_tables_state::set_open_tables_state(Open_tables_state *state) { this->open_tables = state->open_tables; this->temporary_tables = state->temporary_tables; this->lock = state->lock; this->extra_lock = state->extra_lock; this->locked_tables_mode = state->locked_tables_mode; this->state_flags = state->state_flags; this->m_reprepare_observers = state->m_reprepare_observers; } void Open_tables_state::reset_open_tables_state() { open_tables = NULL; temporary_tables = NULL; lock = NULL; extra_lock = NULL; locked_tables_mode = LTM_NONE; state_flags = 0U; reset_reprepare_observers(); } THD::THD(bool enable_plugins) : Query_arena(&main_mem_root, STMT_REGULAR_EXECUTION), mark_used_columns(MARK_COLUMNS_READ), want_privilege(0), main_lex(new LEX), lex(main_lex.get()), m_dd_client(new dd::cache::Dictionary_client(this)), m_query_string(NULL_CSTR), m_db(NULL_CSTR), rli_fake(0), rli_slave(NULL), initial_status_var(NULL), status_var_aggregated(false), m_current_query_cost(0), m_current_query_partial_plans(0), m_main_security_ctx(this), m_security_ctx(&m_main_security_ctx), protocol_text(new Protocol_text), protocol_binary(new Protocol_binary), query_plan(this), m_current_stage_key(0), current_mutex(NULL), current_cond(NULL), m_is_admin_conn(false), in_sub_stmt(0), fill_status_recursion_level(0), fill_variables_recursion_level(0), ha_data(PSI_NOT_INSTRUMENTED, ha_data.initial_capacity), binlog_row_event_extra_data(NULL), skip_readonly_check(false), binlog_unsafe_warning_flags(0), binlog_table_maps(0), binlog_accessed_db_names(NULL), m_trans_log_file(NULL), m_trans_fixed_log_file(NULL), m_trans_end_pos(0), m_transaction(new Transaction_ctx()), m_attachable_trx(NULL), table_map_for_update(0), m_examined_row_count(0), #if defined(ENABLED_PROFILING) profiling(new PROFILING), #endif m_stage_progress_psi(NULL), m_digest(NULL), m_statement_psi(NULL), m_transaction_psi(NULL), m_idle_psi(NULL), m_server_idle(false), user_var_events(key_memory_user_var_entry), next_to_commit(NULL), binlog_need_explicit_defaults_ts(false), kill_immunizer(NULL), m_is_fatal_error(false), transaction_rollback_request(0), is_fatal_sub_stmt_error(false), rand_used(0), time_zone_used(0), in_lock_tables(0), derived_tables_processing(false), parsing_system_view(false), sp_runtime_ctx(NULL), m_parser_state(NULL), work_part_info(NULL), // No need to instrument, highly unlikely to have that many plugins. audit_class_plugins(PSI_NOT_INSTRUMENTED), audit_class_mask(PSI_NOT_INSTRUMENTED), #if defined(ENABLED_DEBUG_SYNC) debug_sync_control(0), #endif /* defined(ENABLED_DEBUG_SYNC) */ m_enable_plugins(enable_plugins), #ifdef HAVE_GTID_NEXT_LIST owned_gtid_set(global_sid_map), #endif skip_gtid_rollback(false), is_commit_in_middle_of_statement(false), has_gtid_consistency_violation(false), main_da(false), m_parser_da(false), m_query_rewrite_plugin_da(false), m_query_rewrite_plugin_da_ptr(&m_query_rewrite_plugin_da), m_stmt_da(&main_da), duplicate_slave_id(false), is_a_srv_session_thd(false), m_is_plugin_fake_ddl(false) { main_lex->reset(); set_psi(NULL); mdl_context.init(this); init_sql_alloc(key_memory_thd_main_mem_root, &main_mem_root, global_system_variables.query_alloc_block_size, global_system_variables.query_prealloc_size); stmt_arena = this; thread_stack = 0; m_catalog.str = "std"; m_catalog.length = 3; password = 0; query_start_usec_used = false; check_for_truncated_fields = CHECK_FIELD_IGNORE; killed = NOT_KILLED; col_access = 0; is_slave_error = thread_specific_used = false; tmp_table = 0; num_truncated_fields = 0L; m_sent_row_count = 0L; current_found_rows = 0; previous_found_rows = 0; is_operating_gtid_table_implicitly = false; is_operating_substatement_implicitly = false; m_row_count_func = -1; statement_id_counter = 0UL; // Must be reset to handle error with THD's created for init of mysqld lex->thd = NULL; lex->set_current_select(0); utime_after_lock = 0L; current_linfo = 0; slave_thread = 0; memset(&variables, 0, sizeof(variables)); m_thread_id = Global_THD_manager::reserved_thread_id; file_id = 0; query_id = 0; query_name_consts = 0; db_charset = global_system_variables.collation_database; is_killable = false; binlog_evt_union.do_union = false; enable_slow_log = 0; commit_error = CE_NONE; tx_commit_pending = false; durability_property = HA_REGULAR_DURABILITY; #ifndef DBUG_OFF dbug_sentry = THD_SENTRY_MAGIC; #endif mysql_audit_init_thd(this); net.vio = 0; system_thread = NON_SYSTEM_THREAD; cleanup_done = 0; m_release_resources_done = false; peer_port = 0; // For SHOW PROCESSLIST get_transaction()->m_flags.enabled = true; m_resource_group_ctx.m_cur_resource_group = nullptr; m_resource_group_ctx.m_switch_resource_group_str[0] = '\0'; m_resource_group_ctx.m_warn = 0; mysql_mutex_init(key_LOCK_thd_data, &LOCK_thd_data, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_LOCK_thd_query, &LOCK_thd_query, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_LOCK_thd_sysvar, &LOCK_thd_sysvar, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_LOCK_thd_protocol, &LOCK_thd_protocol, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_LOCK_query_plan, &LOCK_query_plan, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_LOCK_current_cond, &LOCK_current_cond, MY_MUTEX_INIT_FAST); mysql_cond_init(key_COND_thr_lock, &COND_thr_lock); /* Variables with default values */ proc_info = "login"; where = THD::DEFAULT_WHERE; server_id = ::server_id; unmasked_server_id = server_id; set_command(COM_CONNECT); *scramble = '\0'; /* Call to init() below requires fully initialized Open_tables_state. */ reset_open_tables_state(); init(); #if defined(ENABLED_PROFILING) profiling->set_thd(this); #endif m_user_connect = NULL; user_vars.clear(); sp_proc_cache = NULL; sp_func_cache = NULL; /* Protocol */ m_protocol = protocol_text.get(); // Default protocol protocol_text->init(this); protocol_binary->init(this); protocol_text->set_client_capabilities(0); // minimalistic client /* Make sure thr_lock_info_init() is called for threads which do not get assigned a proper thread_id value but keep using reserved_thread_id. */ thr_lock_info_init(&lock_info, m_thread_id, &COND_thr_lock); m_internal_handler = NULL; m_binlog_invoker = false; memset(&m_invoker_user, 0, sizeof(m_invoker_user)); memset(&m_invoker_host, 0, sizeof(m_invoker_host)); binlog_next_event_pos.file_name = NULL; binlog_next_event_pos.pos = 0; timer = NULL; timer_cache = NULL; m_token_array = NULL; if (max_digest_length > 0) { m_token_array = (unsigned char *)my_malloc(PSI_INSTRUMENT_ME, max_digest_length, MYF(MY_WME)); } #ifndef DBUG_OFF debug_binlog_xid_last.reset(); #endif set_system_user(false); } void THD::set_transaction(Transaction_ctx *transaction_ctx) { DBUG_ASSERT(is_attachable_ro_transaction_active()); delete m_transaction.release(); m_transaction.reset(transaction_ctx); } bool THD::set_db(const LEX_CSTRING &new_db) { bool result; /* Acquiring mutex LOCK_thd_data as we either free the memory allocated for the database and reallocating the memory for the new db or memcpy the new_db to the db. */ mysql_mutex_lock(&LOCK_thd_data); /* Do not reallocate memory if current chunk is big enough. */ if (m_db.str && new_db.str && m_db.length >= new_db.length) memcpy(const_cast(m_db.str), new_db.str, new_db.length + 1); else { my_free(const_cast(m_db.str)); m_db = NULL_CSTR; if (new_db.str) m_db.str = my_strndup(key_memory_THD_db, new_db.str, new_db.length, MYF(MY_WME | ME_FATALERROR)); } m_db.length = m_db.str ? new_db.length : 0; mysql_mutex_unlock(&LOCK_thd_data); result = new_db.str && !m_db.str; #ifdef HAVE_PSI_THREAD_INTERFACE if (!result) PSI_THREAD_CALL(set_thread_db)(new_db.str, static_cast(new_db.length)); #endif return result; } void THD::push_internal_handler(Internal_error_handler *handler) { if (m_internal_handler) { handler->m_prev_internal_handler = m_internal_handler; m_internal_handler = handler; } else m_internal_handler = handler; } bool THD::handle_condition(uint sql_errno, const char *sqlstate, Sql_condition::enum_severity_level *level, const char *msg) { if (!m_internal_handler) return false; for (Internal_error_handler *error_handler = m_internal_handler; error_handler; error_handler = error_handler->m_prev_internal_handler) { if (error_handler->handle_condition(this, sql_errno, sqlstate, level, msg)) return true; } return false; } Internal_error_handler *THD::pop_internal_handler() { DBUG_ASSERT(m_internal_handler != NULL); Internal_error_handler *popped_handler = m_internal_handler; m_internal_handler = m_internal_handler->m_prev_internal_handler; return popped_handler; } void THD::raise_error(uint sql_errno) { const char *msg = ER_THD_NONCONST(this, sql_errno); (void)raise_condition(sql_errno, NULL, Sql_condition::SL_ERROR, msg); } void THD::raise_error_printf(uint sql_errno, ...) { va_list args; char ebuff[MYSQL_ERRMSG_SIZE]; DBUG_TRACE; DBUG_PRINT("my", ("nr: %d errno: %d", sql_errno, errno)); const char *format = ER_THD_NONCONST(this, sql_errno); va_start(args, sql_errno); vsnprintf(ebuff, sizeof(ebuff), format, args); va_end(args); (void)raise_condition(sql_errno, NULL, Sql_condition::SL_ERROR, ebuff); } void THD::raise_warning(uint sql_errno) { const char *msg = ER_THD_NONCONST(this, sql_errno); (void)raise_condition(sql_errno, NULL, Sql_condition::SL_WARNING, msg); } void THD::raise_warning_printf(uint sql_errno, ...) { va_list args; char ebuff[MYSQL_ERRMSG_SIZE]; DBUG_TRACE; DBUG_PRINT("enter", ("warning: %u", sql_errno)); const char *format = ER_THD_NONCONST(this, sql_errno); va_start(args, sql_errno); vsnprintf(ebuff, sizeof(ebuff), format, args); va_end(args); (void)raise_condition(sql_errno, NULL, Sql_condition::SL_WARNING, ebuff); } void THD::raise_note(uint sql_errno) { DBUG_TRACE; DBUG_PRINT("enter", ("code: %d", sql_errno)); if (!(variables.option_bits & OPTION_SQL_NOTES)) return; const char *msg = ER_THD_NONCONST(this, sql_errno); (void)raise_condition(sql_errno, NULL, Sql_condition::SL_NOTE, msg); } void THD::raise_note_printf(uint sql_errno, ...) { va_list args; char ebuff[MYSQL_ERRMSG_SIZE]; DBUG_TRACE; DBUG_PRINT("enter", ("code: %u", sql_errno)); if (!(variables.option_bits & OPTION_SQL_NOTES)) return; const char *format = ER_THD_NONCONST(this, sql_errno); va_start(args, sql_errno); vsnprintf(ebuff, sizeof(ebuff), format, args); va_end(args); (void)raise_condition(sql_errno, NULL, Sql_condition::SL_NOTE, ebuff); } struct timeval THD::query_start_timeval_trunc(uint decimals) { struct timeval tv; tv.tv_sec = start_time.tv_sec; if (decimals) { tv.tv_usec = start_time.tv_usec; my_timeval_trunc(&tv, decimals); query_start_usec_used = true; } else { tv.tv_usec = 0; } return tv; } Sql_condition *THD::raise_condition(uint sql_errno, const char *sqlstate, Sql_condition::enum_severity_level level, const char *msg, bool fatal_error) { DBUG_TRACE; if (!(variables.option_bits & OPTION_SQL_NOTES) && (level == Sql_condition::SL_NOTE)) return NULL; DBUG_ASSERT(sql_errno != 0); if (sql_errno == 0) /* Safety in release build */ sql_errno = ER_UNKNOWN_ERROR; if (msg == NULL) msg = ER_THD_NONCONST(this, sql_errno); if (sqlstate == NULL) sqlstate = mysql_errno_to_sqlstate(sql_errno); if (fatal_error) { // Only makes sense for errors DBUG_ASSERT(level == Sql_condition::SL_ERROR); this->fatal_error(); } MYSQL_LOG_ERROR(sql_errno, PSI_ERROR_OPERATION_RAISED); if (handle_condition(sql_errno, sqlstate, &level, msg)) return NULL; Diagnostics_area *da = get_stmt_da(); if (level == Sql_condition::SL_ERROR) { /* Reporting an error invokes audit API call that notifies the error to the plugin. Audit API that generate the error adds a protection (condition handler) that prevents entering infinite recursion, when a plugin signals error, when already handling the error. mysql_audit_notify() must therefore be called after handle_condition(). */ mysql_audit_notify(this, AUDIT_EVENT(MYSQL_AUDIT_GENERAL_ERROR), sql_errno, msg, strlen(msg)); is_slave_error = true; // needed to catch query errors during replication if (!da->is_error()) { set_row_count_func(-1); da->set_error_status(sql_errno, msg, sqlstate); } } /* Avoid pushing a condition for fatal out of memory errors as this will require memory allocation and therefore might fail. Non fatal out of memory errors can occur if raised by SIGNAL/RESIGNAL statement. */ Sql_condition *cond = NULL; if (!(is_fatal_error() && (sql_errno == EE_OUTOFMEMORY || sql_errno == ER_OUTOFMEMORY || sql_errno == ER_STD_BAD_ALLOC_ERROR))) { cond = da->push_warning(this, sql_errno, sqlstate, level, msg); } return cond; } /* Init common variables that has to be reset on start and on cleanup_connection */ void THD::init(void) { plugin_thdvar_init(this, m_enable_plugins); /* variables= global_system_variables above has reset variables.pseudo_thread_id to 0. We need to correct it here to avoid temporary tables replication failure. */ variables.pseudo_thread_id = m_thread_id; /* NOTE: reset_connection command will reset the THD to its default state. All system variables whose scope is SESSION ONLY should be set to their default values here. */ reset_first_successful_insert_id(); user_time.tv_sec = user_time.tv_usec = 0; start_time.tv_sec = start_time.tv_usec = 0; set_time(); auto_inc_intervals_forced.empty(); { ulong tmp; tmp = sql_rnd_with_mutex(); randominit(&rand, tmp + (ulong)&rand, tmp + (ulong)::atomic_global_query_id); } server_status = SERVER_STATUS_AUTOCOMMIT; if (variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES) server_status |= SERVER_STATUS_NO_BACKSLASH_ESCAPES; get_transaction()->reset_unsafe_rollback_flags(Transaction_ctx::SESSION); get_transaction()->reset_unsafe_rollback_flags(Transaction_ctx::STMT); open_options = ha_open_options; update_lock_default = (variables.low_priority_updates ? TL_WRITE_LOW_PRIORITY : TL_WRITE); insert_lock_default = (variables.low_priority_updates ? TL_WRITE_LOW_PRIORITY : TL_WRITE_CONCURRENT_INSERT); tx_isolation = (enum_tx_isolation)variables.transaction_isolation; tx_read_only = variables.transaction_read_only; tx_priority = 0; thd_tx_priority = 0; update_charset(); reset_current_stmt_binlog_format_row(); reset_binlog_local_stmt_filter(); memset(&status_var, 0, sizeof(status_var)); binlog_row_event_extra_data = 0; if (variables.sql_log_bin) variables.option_bits |= OPTION_BIN_LOG; else variables.option_bits &= ~OPTION_BIN_LOG; #if defined(ENABLED_DEBUG_SYNC) /* Initialize the Debug Sync Facility. See debug_sync.cc. */ debug_sync_init_thread(this); #endif /* defined(ENABLED_DEBUG_SYNC) */ /* Initialize session_tracker and create all tracker objects */ session_tracker.init(this->charset()); session_tracker.enable(this); owned_gtid.clear(); owned_sid.clear(); m_se_gtid_flags.reset(); owned_gtid.dbug_print(NULL, "set owned_gtid (clear) in THD::init"); // This will clear the writeset session history. rpl_thd_ctx.dependency_tracker_ctx().set_last_session_sequence_number(0); /* This variable is used to temporarily disable the password validation plugin when a RANDOM PASSWORD is generated during SET PASSWORD,CREATE USER or ALTER USER statements. */ m_disable_password_validation = false; } void THD::init_query_mem_roots() { mem_root->set_block_size(variables.query_alloc_block_size); get_transaction()->init_mem_root_defaults(variables.trans_alloc_block_size, variables.trans_prealloc_size); } void THD::set_new_thread_id() { m_thread_id = Global_THD_manager::get_instance()->get_new_thread_id(); variables.pseudo_thread_id = m_thread_id; thr_lock_info_init(&lock_info, m_thread_id, &COND_thr_lock); } /* Do what's needed when one invokes change user SYNOPSIS cleanup_connection() IMPLEMENTATION Reset all resources that are connection specific */ void THD::cleanup_connection(void) { mysql_mutex_lock(&LOCK_status); add_to_status(&global_status_var, &status_var); reset_system_status_vars(&status_var); mysql_mutex_unlock(&LOCK_status); cleanup(); #if defined(ENABLED_DEBUG_SYNC) /* End the Debug Sync Facility. See debug_sync.cc. */ debug_sync_end_thread(this); #endif /* defined(ENABLED_DEBUG_SYNC) */ killed = NOT_KILLED; cleanup_done = 0; init(); stmt_map.reset(); user_vars.clear(); sp_cache_clear(&sp_proc_cache); sp_cache_clear(&sp_func_cache); clear_error(); // clear the warnings get_stmt_da()->reset_condition_info(this); // clear profiling information #if defined(ENABLED_PROFILING) profiling->cleanup(); #endif #ifndef DBUG_OFF /* DEBUG code only (begin) */ bool check_cleanup = false; DBUG_EXECUTE_IF("debug_test_cleanup_connection", check_cleanup = true;); if (check_cleanup) { /* isolation level should be default */ DBUG_ASSERT(variables.transaction_isolation == ISO_REPEATABLE_READ); /* check autocommit is ON by default */ DBUG_ASSERT(server_status == SERVER_STATUS_AUTOCOMMIT); /* check prepared stmts are cleaned up */ DBUG_ASSERT(prepared_stmt_count == 0); /* check diagnostic area is cleaned up */ DBUG_ASSERT(get_stmt_da()->status() == Diagnostics_area::DA_EMPTY); /* check if temp tables are deleted */ DBUG_ASSERT(temporary_tables == NULL); /* check if tables are unlocked */ DBUG_ASSERT(locked_tables_list.locked_tables() == NULL); } /* DEBUG code only (end) */ #endif } /* Do what's needed when one invokes change user. Also used during THD::release_resources, i.e. prior to THD destruction. */ void THD::cleanup(void) { Transaction_ctx *trn_ctx = get_transaction(); XID_STATE *xs = trn_ctx->xid_state(); DBUG_TRACE; DBUG_ASSERT(cleanup_done == 0); DEBUG_SYNC(this, "thd_cleanup_start"); killed = KILL_CONNECTION; if (trn_ctx->xid_state()->has_state(XID_STATE::XA_PREPARED)) { /* Return error is not an option as XA is in prepared state and connection is gone. Log the error and continue. */ if (MDL_context_backup_manager::instance().create_backup( &mdl_context, xs->get_xid()->key(), xs->get_xid()->key_length())) { LogErr(ERROR_LEVEL, ER_XA_CANT_CREATE_MDL_BACKUP); } transaction_cache_detach(trn_ctx); } else { xs->set_state(XID_STATE::XA_NOTR); trans_rollback(this); transaction_cache_delete(trn_ctx); } locked_tables_list.unlock_locked_tables(this); mysql_ha_cleanup(this); DBUG_ASSERT(open_tables == NULL); /* If the thread was in the middle of an ongoing transaction (rolled back a few lines above) or under LOCK TABLES (unlocked the tables and left the mode a few lines above), there will be outstanding metadata locks. Release them. */ mdl_context.release_transactional_locks(); /* Release the global read lock, if acquired. */ if (global_read_lock.is_acquired()) global_read_lock.unlock_global_read_lock(this); mysql_ull_cleanup(this); /* All locking service locks must be released on disconnect. */ release_all_locking_service_locks(this); /* If Backup Lock was acquired it must be released on disconnect. */ release_backup_lock(this); /* All metadata locks must have been released by now. */ DBUG_ASSERT(!mdl_context.has_locks()); /* Protects user_vars. */ mysql_mutex_lock(&LOCK_thd_data); user_vars.clear(); mysql_mutex_unlock(&LOCK_thd_data); /* When we call drop table for temporary tables, the user_var_events container is not cleared this might cause error if the container was filled before the drop table command is called. So call this before calling close_temporary_tables. */ user_var_events.clear(); close_temporary_tables(this); sp_cache_clear(&sp_proc_cache); sp_cache_clear(&sp_func_cache); /* Actions above might generate events for the binary log, so we commit the current transaction coordinator after executing cleanup actions. */ if (tc_log && !trn_ctx->xid_state()->has_state(XID_STATE::XA_PREPARED)) tc_log->commit(this, true); /* Destroy trackers only after finishing manipulations with transaction state to avoid issues with Transaction_state_tracker. */ session_tracker.deinit(); /* If we have a Security_context, make sure it is "logged out" */ cleanup_done = 1; } /** Release most resources, prior to THD destruction. */ void THD::release_resources() { DBUG_ASSERT(m_release_resources_done == false); Global_THD_manager::get_instance()->release_thread_id(m_thread_id); /* Ensure that no one is using THD */ mysql_mutex_lock(&LOCK_thd_data); mysql_mutex_lock(&LOCK_query_plan); /* Close connection */ if (is_classic_protocol() && get_protocol_classic()->get_vio()) { vio_delete(get_protocol_classic()->get_vio()); get_protocol_classic()->end_net(); } /* modification plan for UPDATE/DELETE should be freed. */ DBUG_ASSERT(query_plan.get_modification_plan() == NULL); mysql_mutex_unlock(&LOCK_query_plan); mysql_mutex_unlock(&LOCK_thd_data); mysql_mutex_lock(&LOCK_thd_query); mysql_mutex_unlock(&LOCK_thd_query); stmt_map.reset(); /* close all prepared statements */ if (!cleanup_done) cleanup(); mdl_context.destroy(); ha_close_connection(this); /* Debug sync system must be closed after ha_close_connection, because DEBUG_SYNC is used in InnoDB connection handlerton close. */ #if defined(ENABLED_DEBUG_SYNC) /* End the Debug Sync Facility. See debug_sync.cc. */ debug_sync_end_thread(this); #endif /* defined(ENABLED_DEBUG_SYNC) */ plugin_thdvar_cleanup(this, m_enable_plugins); DBUG_ASSERT(timer == NULL); if (timer_cache) thd_timer_destroy(timer_cache); if (rli_fake) { rli_fake->end_info(); delete rli_fake; rli_fake = NULL; } mysql_audit_free_thd(this); if (current_thd == this) restore_globals(); mysql_mutex_lock(&LOCK_status); /* Add thread status to the global totals. */ add_to_status(&global_status_var, &status_var); #ifdef HAVE_PSI_THREAD_INTERFACE /* Aggregate thread status into the Performance Schema. */ if (m_psi != NULL) { PSI_THREAD_CALL(aggregate_thread_status)(m_psi); } #endif /* HAVE_PSI_THREAD_INTERFACE */ /* Ensure that the thread status is not re-aggregated to the global totals. */ status_var_aggregated = true; mysql_mutex_unlock(&LOCK_status); m_release_resources_done = true; } THD::~THD() { THD_CHECK_SENTRY(this); DBUG_TRACE; DBUG_PRINT("info", ("THD dtor, this %p", this)); if (!m_release_resources_done) release_resources(); clear_next_event_pos(); /* Ensure that no one is using THD */ mysql_mutex_lock(&LOCK_thd_data); mysql_mutex_unlock(&LOCK_thd_data); mysql_mutex_lock(&LOCK_thd_query); mysql_mutex_unlock(&LOCK_thd_query); DBUG_ASSERT(!m_attachable_trx); my_free(const_cast(m_db.str)); m_db = NULL_CSTR; get_transaction()->free_memory(MYF(0)); mysql_mutex_destroy(&LOCK_query_plan); mysql_mutex_destroy(&LOCK_thd_data); mysql_mutex_destroy(&LOCK_thd_query); mysql_mutex_destroy(&LOCK_thd_sysvar); mysql_mutex_destroy(&LOCK_thd_protocol); mysql_mutex_destroy(&LOCK_current_cond); mysql_cond_destroy(&COND_thr_lock); #ifndef DBUG_OFF dbug_sentry = THD_SENTRY_GONE; #endif if (variables.gtid_next_list.gtid_set != NULL) { #ifdef HAVE_GTID_NEXT_LIST delete variables.gtid_next_list.gtid_set; variables.gtid_next_list.gtid_set = NULL; variables.gtid_next_list.is_non_null = false; #else DBUG_ASSERT(0); #endif } if (rli_slave) rli_slave->cleanup_after_session(); free_root(&main_mem_root, MYF(0)); if (m_token_array != NULL) { my_free(m_token_array); } } /** Awake a thread. @param[in] state_to_set value for THD::killed This is normally called from another thread's THD object. @note Do always call this while holding LOCK_thd_data. */ void THD::awake(THD::killed_state state_to_set) { DBUG_TRACE; DBUG_PRINT("enter", ("this: %p current_thd: %p", this, current_thd)); THD_CHECK_SENTRY(this); mysql_mutex_assert_owner(&LOCK_thd_data); /* Shutdown clone vio always, to wake up clone waiting for remote. */ shutdown_clone_vio(); /* If THD is in kill immune mode (i.e. operation on new DD tables is in progress) then just save state_to_set with THD::kill_immunizer object. While exiting kill immune mode, awake() is called again with the killed state saved in THD::kill_immunizer object. */ if (kill_immunizer && kill_immunizer->is_active()) { kill_immunizer->save_killed_state(state_to_set); return; } /* Set killed flag if the connection is being killed (state_to_set is KILL_CONNECTION) or the connection is processing a query (state_to_set is KILL_QUERY and m_server_idle flag is not set). If the connection is idle and state_to_set is KILL QUERY, the the killed flag is not set so that it doesn't affect the next command incorrectly. */ if (this->m_server_idle && state_to_set == KILL_QUERY) { /* nothing */ } else { killed = state_to_set; } if (state_to_set != THD::KILL_QUERY && state_to_set != THD::KILL_TIMEOUT) { if (this != current_thd || kill_immunizer) { DBUG_ASSERT(!kill_immunizer || !kill_immunizer->is_active()); /* Before sending a signal, let's close the socket of the thread that is being killed ("this", which is not the current thread). This is to make sure it does not block if the signal is lost. This needs to be done only on platforms where signals are not a reliable interruption mechanism. Note that the downside of this mechanism is that we could close the connection while "this" target thread is in the middle of sending a result to the application, thus violating the client- server protocol. On the other hand, without closing the socket we have a race condition. If "this" target thread passes the check of thd->killed, and then the current thread runs through THD::awake(), sets the 'killed' flag and completes the signaling, and then the target thread runs into read(), it will block on the socket. As a result of the discussions around Bug#37780, it has been decided that we accept the race condition. A second KILL awakes the target from read(). If we are killing ourselves, we know that we are not blocked. We also know that we will check thd->killed before we go for reading the next statement. */ shutdown_active_vio(); } /* Send an event to the scheduler that a thread should be killed. */ if (!slave_thread) MYSQL_CALLBACK(Connection_handler_manager::event_functions, post_kill_notification, (this)); } /* Interrupt target waiting inside a storage engine. */ if (state_to_set != THD::NOT_KILLED) ha_kill_connection(this); if (state_to_set == THD::KILL_TIMEOUT) { DBUG_ASSERT(!status_var_aggregated); status_var.max_execution_time_exceeded++; } /* Broadcast a condition to kick the target if it is waiting on it. */ if (is_killable) { mysql_mutex_lock(&LOCK_current_cond); /* This broadcast could be up in the air if the victim thread exits the cond in the time between read and broadcast, but that is ok since all we want to do is to make the victim thread get out of waiting on current_cond. If we see a non-zero current_cond: it cannot be an old value (because then exit_cond() should have run and it can't because we have mutex); so it is the true value but maybe current_mutex is not yet non-zero (we're in the middle of enter_cond() and there is a "memory order inversion"). So we test the mutex too to not lock 0. Note that there is a small chance we fail to kill. If victim has locked current_mutex, but hasn't yet entered enter_cond() (which means that current_cond and current_mutex are 0), then the victim will not get a signal and it may wait "forever" on the cond (until we issue a second KILL or the status it's waiting for happens). It's true that we have set its thd->killed but it may not see it immediately and so may have time to reach the cond_wait(). However, where possible, we test for killed once again after enter_cond(). This should make the signaling as safe as possible. However, there is still a small chance of failure on platforms with instruction or memory write reordering. */ if (current_cond.load() && current_mutex.load()) { DBUG_EXECUTE_IF("before_dump_thread_acquires_current_mutex", { const char act[] = "now signal dump_thread_signal wait_for go_dump_thread"; DBUG_ASSERT(!debug_sync_set_action(current_thd, STRING_WITH_LEN(act))); };); mysql_mutex_lock(current_mutex); mysql_cond_broadcast(current_cond); mysql_mutex_unlock(current_mutex); } mysql_mutex_unlock(&LOCK_current_cond); } } /** Close the Vio associated this session. @remark LOCK_thd_data is taken due to the fact that the Vio might be disassociated concurrently. */ void THD::disconnect(bool server_shutdown) { Vio *vio = NULL; mysql_mutex_lock(&LOCK_thd_data); /* Shutdown clone vio always, to wake up clone waiting for remote. */ shutdown_clone_vio(); /* If thread is in kill immune mode (i.e. operation on new DD tables is in progress) then just save state_to_set with THD::kill_immunizer object. While exiting kill immune mode, awake() is called again with the killed state saved in THD::kill_immunizer object. active_vio is aleady associated to the thread when it is in the kill immune mode. THD::awake() closes the active_vio. */ if (kill_immunizer != nullptr) kill_immunizer->save_killed_state(THD::KILL_CONNECTION); else { killed = THD::KILL_CONNECTION; /* Since a active vio might might have not been set yet, in any case save a reference to avoid closing a inexistent one or closing the vio twice if there is a active one. */ vio = active_vio; shutdown_active_vio(); /* Disconnect even if a active vio is not associated. */ if (is_classic_protocol() && get_protocol_classic()->get_vio() != vio && get_protocol_classic()->connection_alive()) { m_protocol->shutdown(server_shutdown); } } mysql_mutex_unlock(&LOCK_thd_data); } void THD::notify_shared_lock(MDL_context_owner *ctx_in_use, bool needs_thr_lock_abort) { THD *in_use = ctx_in_use->get_thd(); if (needs_thr_lock_abort) { mysql_mutex_lock(&in_use->LOCK_thd_data); for (TABLE *thd_table = in_use->open_tables; thd_table; thd_table = thd_table->next) { /* Check for TABLE::needs_reopen() is needed since in some places we call handler::close() for table instance (and set TABLE::db_stat to 0) and do not remove such instances from the THD::open_tables for some time, during which other thread can see those instances (e.g. see partitioning code). */ if (!thd_table->needs_reopen()) mysql_lock_abort_for_thread(this, thd_table); } mysql_mutex_unlock(&in_use->LOCK_thd_data); } } /* Remember the location of thread info, the structure needed for (*THR_MALLOC)->Alloc() and the structure for the net buffer */ void THD::store_globals() { /* Assert that thread_stack is initialized: it's necessary to be able to track stack overrun. */ DBUG_ASSERT(thread_stack); current_thd = this; THR_MALLOC = &mem_root; /* is_killable is concurrently readable by a killer thread. It is protected by LOCK_thd_data, it is not needed to lock while the value is changing from false not true. If the kill thread reads true we need to ensure that the thread doesn't proceed to assign another thread to the same TLS reference. */ is_killable = true; #ifndef DBUG_OFF /* Let mysqld define the thread id (not mysys) This allows us to move THD to different threads if needed. */ set_my_thread_var_id(m_thread_id); #endif real_id = my_thread_self(); // For debugging } /* Remove the thread specific info (THD and mem_root pointer) stored during store_global call for this thread. */ void THD::restore_globals() { /* Assert that thread_stack is initialized: it's necessary to be able to track stack overrun. */ DBUG_ASSERT(thread_stack); /* Undocking the thread specific data. */ current_thd = nullptr; THR_MALLOC = nullptr; } /* Cleanup after query. SYNOPSIS THD::cleanup_after_query() DESCRIPTION This function is used to reset thread data to its default state. NOTE This function is not suitable for setting thread data to some non-default values, as there is only one replication thread, so different master threads may overwrite data of each other on slave. */ void THD::cleanup_after_query() { /* Reset rand_used so that detection of calls to rand() will save random seeds if needed by the slave. Do not reset rand_used if inside a stored function or trigger because only the call to these operations is logged. Thus only the calling statement needs to detect rand() calls made by its substatements. These substatements must not set rand_used to 0 because it would remove the detection of rand() by the calling statement. */ if (!in_sub_stmt) /* stored functions and triggers are a special case */ { /* Forget those values, for next binlogger: */ stmt_depends_on_first_successful_insert_id_in_prev_stmt = 0; auto_inc_intervals_in_cur_stmt_for_binlog.empty(); rand_used = 0; binlog_accessed_db_names = NULL; /* Clean possible unused INSERT_ID events by current statement. is_update_query() is needed to ignore SET statements: Statements that don't update anything directly and don't used stored functions. This is mostly necessary to ignore statements in binlog between SET INSERT_ID and DML statement which is intended to consume its event (there can be other SET statements between them). */ if ((rli_slave || rli_fake) && is_update_query(lex->sql_command)) auto_inc_intervals_forced.empty(); } /* In case of stored procedures, stored functions, triggers and events m_trans_fixed_log_file will not be set to NULL. The memory will be reused. */ if (!sp_runtime_ctx) m_trans_fixed_log_file = NULL; /* Forget the binlog stmt filter for the next query. There are some code paths that: - do not call THD::decide_logging_format() - do call THD::binlog_query(), making this reset necessary. */ reset_binlog_local_stmt_filter(); if (first_successful_insert_id_in_cur_stmt > 0) { /* set what LAST_INSERT_ID() will return */ first_successful_insert_id_in_prev_stmt = first_successful_insert_id_in_cur_stmt; first_successful_insert_id_in_cur_stmt = 0; } arg_of_last_insert_id_function = 0; /* Hack for cleaning up view security contexts */ List_iterator it(m_view_ctx_list); while (Security_context *ctx = it++) { ctx->logout(); } m_view_ctx_list.empty(); /* Free Items that were created during this execution */ free_items(); /* Reset where. */ where = THD::DEFAULT_WHERE; /* reset table map for multi-table update */ table_map_for_update = 0; m_binlog_invoker = false; /* reset replication info structure */ if (lex) { lex->mi.repl_ignore_server_ids.clear(); } if (rli_slave) rli_slave->cleanup_after_query(); // Set the default "cute" mode for the execution environment: check_for_truncated_fields = CHECK_FIELD_IGNORE; } /* Convert a string to another character set @param to Store new allocated string here @param to_cs New character set for allocated string @param from String to convert @param from_length Length of string to convert @param from_cs Original character set @param report_error Raise error (when true) or warning (when false) if there is problem when doing conversion @note to will be 0-terminated to make it easy to pass to system funcs @retval false ok @retval true End of memory. In this case to->str will point to 0 and to->length will be 0. */ bool THD::convert_string(LEX_STRING *to, const CHARSET_INFO *to_cs, const char *from, size_t from_length, const CHARSET_INFO *from_cs, bool report_error) { DBUG_TRACE; size_t new_length = to_cs->mbmaxlen * from_length; if (!(to->str = (char *)alloc(new_length + 1))) { to->length = 0; // Safety fix return 1; // EOM } uint errors = 0; to->length = copy_and_convert(to->str, new_length, to_cs, from, from_length, from_cs, &errors); to->str[to->length] = 0; // Safety if (errors != 0) { char printable_buff[32]; convert_to_printable(printable_buff, sizeof(printable_buff), from, from_length, from_cs, 6); if (report_error) { my_error(ER_CANNOT_CONVERT_STRING, MYF(0), printable_buff, from_cs->csname, to_cs->csname); return 1; } else { push_warning_printf(this, Sql_condition::SL_WARNING, ER_INVALID_CHARACTER_STRING, ER_THD(this, ER_CANNOT_CONVERT_STRING), printable_buff, from_cs->csname, to_cs->csname); } } return 0; } /* Update some cache variables when character set changes */ void THD::update_charset() { size_t not_used; charset_is_system_charset = !String::needs_conversion( 0, variables.character_set_client, system_charset_info, ¬_used); charset_is_collation_connection = !String::needs_conversion(0, variables.character_set_client, variables.collation_connection, ¬_used); charset_is_character_set_filesystem = !String::needs_conversion(0, variables.character_set_client, variables.character_set_filesystem, ¬_used); } int THD::send_explain_fields(Query_result *result) { List field_list; Item *item; CHARSET_INFO *cs = system_charset_info; field_list.push_back(new Item_return_int("id", 3, MYSQL_TYPE_LONGLONG)); field_list.push_back(new Item_empty_string("select_type", 19, cs)); field_list.push_back(item = new Item_empty_string("table", NAME_CHAR_LEN, cs)); item->maybe_null = 1; /* Maximum length of string that make_used_partitions_str() can produce */ item = new Item_empty_string("partitions", MAX_PARTITIONS * (1 + FN_LEN), cs); field_list.push_back(item); item->maybe_null = 1; field_list.push_back(item = new Item_empty_string("type", 10, cs)); item->maybe_null = 1; field_list.push_back(item = new Item_empty_string( "possible_keys", NAME_CHAR_LEN * MAX_KEY, cs)); item->maybe_null = 1; field_list.push_back(item = new Item_empty_string("key", NAME_CHAR_LEN, cs)); item->maybe_null = 1; field_list.push_back( item = new Item_empty_string("key_len", NAME_CHAR_LEN * MAX_KEY)); item->maybe_null = 1; field_list.push_back( item = new Item_empty_string("ref", NAME_CHAR_LEN * MAX_REF_PARTS, cs)); item->maybe_null = 1; field_list.push_back( item = new Item_return_int("rows", 10, MYSQL_TYPE_LONGLONG)); item->maybe_null = 1; field_list.push_back( item = new Item_float(NAME_STRING("filtered"), 0.1234, 2, 4)); item->maybe_null = 1; field_list.push_back(new Item_empty_string("Extra", 255, cs)); item->maybe_null = 1; return (result->send_result_set_metadata( this, field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)); } enum_vio_type THD::get_vio_type() const { DBUG_TRACE; return get_protocol()->connection_type(); } void THD::shutdown_active_vio() { DBUG_TRACE; mysql_mutex_assert_owner(&LOCK_thd_data); if (active_vio) { vio_shutdown(active_vio); active_vio = 0; m_SSL = NULL; } } void THD::shutdown_clone_vio() { DBUG_TRACE; mysql_mutex_assert_owner(&LOCK_thd_data); if (clone_vio != nullptr) { vio_shutdown(clone_vio); clone_vio = nullptr; } } /* Register an item tree tree transformation, performed by the query optimizer. */ void THD::nocheck_register_item_tree_change(Item **place, Item *new_value) { Item_change_record *change; /* Now we use one node per change, which adds some memory overhead, but still is rather fast as we use alloc_root for allocations. A list of item tree changes of an average query should be short. */ void *change_mem = mem_root->Alloc(sizeof(*change)); if (change_mem == 0) { /* OOM, thd->fatal_error() is called by the error handler of the memroot. Just return. */ return; } change = new (change_mem) Item_change_record(place, new_value); change_list.push_front(change); } void THD::replace_rollback_place(Item **new_place) { I_List_iterator it(change_list); Item_change_record *change; while ((change = it++)) { if (change->new_value == *new_place) { DBUG_PRINT("info", ("replace_rollback_place new_value %p place %p", *new_place, new_place)); change->place = new_place; break; } } } void THD::rollback_item_tree_changes() { I_List_iterator it(change_list); Item_change_record *change; DBUG_TRACE; while ((change = it++)) { DBUG_PRINT("info", ("rollback_item_tree_changes " "place %p curr_value %p old_value %p", change->place, *change->place, change->old_value)); *change->place = change->old_value; } /* We can forget about changes memory: it's allocated in runtime memroot */ change_list.empty(); } void Query_arena::add_item(Item *item) { item->next_free = m_item_list; m_item_list = item; } void Query_arena::free_items() { Item *next; DBUG_TRACE; /* This works because items are allocated with (*THR_MALLOC)->Alloc() */ for (; m_item_list; m_item_list = next) { next = m_item_list->next_free; m_item_list->delete_self(); } /* Postcondition: free_list is 0 */ } void Query_arena::set_query_arena(const Query_arena &set) { mem_root = set.mem_root; set_item_list(set.item_list()); state = set.state; } void Query_arena::swap_query_arena(const Query_arena &source, Query_arena *backup) { backup->set_query_arena(*this); set_query_arena(source); } void THD::end_statement() { DBUG_TRACE; /* Cleanup SQL processing state to reuse this statement in next query. */ lex_end(lex); //@todo Check lifetime of Query_result objects. // delete lex->result; lex->result = NULL; // Prepare for next statement /* Note that item list is freed in cleanup_after_query() */ /* Don't free mem_root, as mem_root is freed in the end of dispatch_command (once for any command). */ } Prepared_statement_map::Prepared_statement_map() : st_hash(key_memory_prepared_statement_map), names_hash(system_charset_info, key_memory_prepared_statement_map), m_last_found_statement(NULL) {} int Prepared_statement_map::insert(Prepared_statement *statement) { st_hash.emplace(statement->id, unique_ptr(statement)); if (statement->name().str) { names_hash.emplace(to_string(statement->name()), statement); } mysql_mutex_lock(&LOCK_prepared_stmt_count); /* We don't check that prepared_stmt_count is <= max_prepared_stmt_count because we would like to allow to lower the total limit of prepared statements below the current count. In that case no new statements can be added until prepared_stmt_count drops below the limit. */ if (prepared_stmt_count >= max_prepared_stmt_count) { mysql_mutex_unlock(&LOCK_prepared_stmt_count); my_error(ER_MAX_PREPARED_STMT_COUNT_REACHED, MYF(0), max_prepared_stmt_count); goto err_max; } prepared_stmt_count++; mysql_mutex_unlock(&LOCK_prepared_stmt_count); m_last_found_statement = statement; return 0; err_max: if (statement->name().str) names_hash.erase(to_string(statement->name())); st_hash.erase(statement->id); return 1; } Prepared_statement *Prepared_statement_map::find_by_name( const LEX_CSTRING &name) { return find_or_nullptr(names_hash, to_string(name)); } Prepared_statement *Prepared_statement_map::find(ulong id) { if (m_last_found_statement == NULL || id != m_last_found_statement->id) { Prepared_statement *stmt = find_or_nullptr(st_hash, id); if (stmt && stmt->name().str) return NULL; m_last_found_statement = stmt; } return m_last_found_statement; } void Prepared_statement_map::erase(Prepared_statement *statement) { if (statement == m_last_found_statement) m_last_found_statement = NULL; if (statement->name().str) names_hash.erase(to_string(statement->name())); st_hash.erase(statement->id); mysql_mutex_lock(&LOCK_prepared_stmt_count); DBUG_ASSERT(prepared_stmt_count > 0); prepared_stmt_count--; mysql_mutex_unlock(&LOCK_prepared_stmt_count); } void Prepared_statement_map::claim_memory_ownership() { for (const auto &key_and_value : st_hash) { my_claim(key_and_value.second.get()); } } void Prepared_statement_map::reset() { if (!st_hash.empty()) { #ifdef HAVE_PSI_PS_INTERFACE for (auto &key_and_value : st_hash) { Prepared_statement *stmt = key_and_value.second.get(); MYSQL_DESTROY_PS(stmt->get_PS_prepared_stmt()); } #endif mysql_mutex_lock(&LOCK_prepared_stmt_count); DBUG_ASSERT(prepared_stmt_count >= st_hash.size()); prepared_stmt_count -= st_hash.size(); mysql_mutex_unlock(&LOCK_prepared_stmt_count); } names_hash.clear(); st_hash.clear(); m_last_found_statement = NULL; } Prepared_statement_map::~Prepared_statement_map() { /* We do not want to grab the global LOCK_prepared_stmt_count mutex here. reset() should already have been called to maintain prepared_stmt_count. */ DBUG_ASSERT(st_hash.empty()); } void THD::send_kill_message() const { int err = killed; if (err && !get_stmt_da()->is_set()) { if ((err == KILL_CONNECTION) && !connection_events_loop_aborted()) err = KILL_QUERY; /* KILL is fatal because: - if a condition handler was allowed to trap and ignore a KILL, one could create routines which the DBA could not kill - INSERT/UPDATE IGNORE should fail: if KILL arrives during JOIN::optimize(), statement cannot possibly run as its caller expected => "OK" would be misleading the caller. */ my_error(err, MYF(ME_FATALERROR)); } } /**************************************************************************** Handling of open and locked tables states. This is used when we want to open/lock (and then close) some tables when we already have a set of tables open and locked. We use these methods for access to mysql.proc table to find definitions of stored routines. ****************************************************************************/ void THD::reset_n_backup_open_tables_state(Open_tables_backup *backup, uint add_state_flags) { DBUG_TRACE; backup->set_open_tables_state(this); backup->mdl_system_tables_svp = mdl_context.mdl_savepoint(); reset_open_tables_state(); state_flags |= (Open_tables_state::BACKUPS_AVAIL | add_state_flags); } void THD::restore_backup_open_tables_state(Open_tables_backup *backup) { DBUG_TRACE; mdl_context.rollback_to_savepoint(backup->mdl_system_tables_svp); /* Before we will throw away current open tables state we want to be sure that it was properly cleaned up. */ DBUG_ASSERT(open_tables == 0 && temporary_tables == 0 && lock == 0 && locked_tables_mode == LTM_NONE && get_reprepare_observer() == NULL); set_open_tables_state(backup); } void THD::begin_attachable_ro_transaction() { m_attachable_trx = new Attachable_trx(this, m_attachable_trx); } void THD::end_attachable_transaction() { Attachable_trx *prev_trx = m_attachable_trx->get_prev_attachable_trx(); delete m_attachable_trx; // Restore attachable transaction which was active before we started // the one which just has ended. NULL in most cases. m_attachable_trx = prev_trx; } void THD::begin_attachable_rw_transaction() { DBUG_ASSERT(!m_attachable_trx); m_attachable_trx = new Attachable_trx_rw(this); } /**************************************************************************** Handling of statement states in functions and triggers. This is used to ensure that the function/trigger gets a clean state to work with and does not cause any side effects of the calling statement. It also allows most stored functions and triggers to replicate even if they are used items that would normally be stored in the binary replication (like last_insert_id() etc...) The following things is done - Disable binary logging for the duration of the statement - Disable multi-result-sets for the duration of the statement - Value of last_insert_id() is saved and restored - Value set by 'SET INSERT_ID=#' is reset and restored - Value for found_rows() is reset and restored - examined_row_count is added to the total - num_truncated_fields is added to the total - new savepoint level is created and destroyed NOTES: Seed for random() is saved for the first! usage of RAND() We reset examined_row_count and num_truncated_fields and add these to the result to ensure that if we have a bug that would reset these within a function, we are not loosing any rows from the main statement. We do not reset value of last_insert_id(). ****************************************************************************/ void THD::reset_sub_statement_state(Sub_statement_state *backup, uint new_state) { /* BUG#33029, if we are replicating from a buggy master, reset auto_inc_intervals_forced to prevent substatement (triggers/functions) from using erroneous INSERT_ID value */ if (rpl_master_erroneous_autoinc(this)) { DBUG_ASSERT(backup->auto_inc_intervals_forced.nb_elements() == 0); auto_inc_intervals_forced.swap(&backup->auto_inc_intervals_forced); } backup->option_bits = variables.option_bits; backup->check_for_truncated_fields = check_for_truncated_fields; backup->in_sub_stmt = in_sub_stmt; backup->enable_slow_log = enable_slow_log; backup->current_found_rows = current_found_rows; backup->previous_found_rows = previous_found_rows; backup->examined_row_count = m_examined_row_count; backup->sent_row_count = m_sent_row_count; backup->num_truncated_fields = num_truncated_fields; backup->client_capabilities = m_protocol->get_client_capabilities(); backup->savepoints = get_transaction()->m_savepoints; backup->first_successful_insert_id_in_prev_stmt = first_successful_insert_id_in_prev_stmt; backup->first_successful_insert_id_in_cur_stmt = first_successful_insert_id_in_cur_stmt; if ((!lex->requires_prelocking() || is_update_query(lex->sql_command)) && !is_current_stmt_binlog_format_row()) { variables.option_bits &= ~OPTION_BIN_LOG; } if ((backup->option_bits & OPTION_BIN_LOG) && is_update_query(lex->sql_command) && !is_current_stmt_binlog_format_row()) mysql_bin_log.start_union_events(this, this->query_id); /* Disable result sets */ if (is_classic_protocol()) get_protocol_classic()->remove_client_capability(CLIENT_MULTI_RESULTS); in_sub_stmt |= new_state; m_examined_row_count = 0; m_sent_row_count = 0; num_truncated_fields = 0; get_transaction()->m_savepoints = 0; first_successful_insert_id_in_cur_stmt = 0; /* Reset savepoint on transaction write set */ if (is_current_stmt_binlog_row_enabled_with_write_set_extraction()) { get_transaction()->get_transaction_write_set_ctx()->reset_savepoint_list(); } } void THD::restore_sub_statement_state(Sub_statement_state *backup) { DBUG_TRACE; /* BUG#33029, if we are replicating from a buggy master, restore auto_inc_intervals_forced so that the top statement can use the INSERT_ID value set before this statement. */ if (rpl_master_erroneous_autoinc(this)) { backup->auto_inc_intervals_forced.swap(&auto_inc_intervals_forced); DBUG_ASSERT(backup->auto_inc_intervals_forced.nb_elements() == 0); } /* To save resources we want to release savepoints which were created during execution of function or trigger before leaving their savepoint level. It is enough to release first savepoint set on this level since all later savepoints will be released automatically. */ if (get_transaction()->m_savepoints) { SAVEPOINT *sv; for (sv = get_transaction()->m_savepoints; sv->prev; sv = sv->prev) { } /* ha_release_savepoint() never returns error. */ (void)ha_release_savepoint(this, sv); } check_for_truncated_fields = backup->check_for_truncated_fields; get_transaction()->m_savepoints = backup->savepoints; variables.option_bits = backup->option_bits; in_sub_stmt = backup->in_sub_stmt; enable_slow_log = backup->enable_slow_log; first_successful_insert_id_in_prev_stmt = backup->first_successful_insert_id_in_prev_stmt; first_successful_insert_id_in_cur_stmt = backup->first_successful_insert_id_in_cur_stmt; current_found_rows = backup->current_found_rows; previous_found_rows = backup->previous_found_rows; set_sent_row_count(backup->sent_row_count); if (is_classic_protocol()) get_protocol_classic()->set_client_capabilities( backup->client_capabilities); /* If we've left sub-statement mode, reset the fatal error flag. Otherwise keep the current value, to propagate it up the sub-statement stack. NOTE: is_fatal_sub_stmt_error can be set only if we've been in the sub-statement mode. */ if (!in_sub_stmt) is_fatal_sub_stmt_error = false; if ((variables.option_bits & OPTION_BIN_LOG) && is_update_query(lex->sql_command) && !is_current_stmt_binlog_format_row()) mysql_bin_log.stop_union_events(this); /* The below assert mostly serves as reminder that optimization in DML_prelocking_strategy::handle_table() relies on the fact that stored function/trigger can't change FOREIGN_KEY_CHECKS value for the top-level statement which invokes them. */ DBUG_ASSERT((variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS) == (backup->option_bits & OPTION_NO_FOREIGN_KEY_CHECKS)); /* The following is added to the old values as we are interested in the total complexity of the query */ inc_examined_row_count(backup->examined_row_count); num_truncated_fields += backup->num_truncated_fields; /* Restore savepoint on transaction write set */ if (is_current_stmt_binlog_row_enabled_with_write_set_extraction()) { get_transaction() ->get_transaction_write_set_ctx() ->restore_savepoint_list(); } } void THD::set_sent_row_count(ha_rows count) { m_sent_row_count = count; MYSQL_SET_STATEMENT_ROWS_SENT(m_statement_psi, m_sent_row_count); } void THD::inc_sent_row_count(ha_rows count) { m_sent_row_count += count; MYSQL_SET_STATEMENT_ROWS_SENT(m_statement_psi, m_sent_row_count); } void THD::inc_examined_row_count(ha_rows count) { m_examined_row_count += count; MYSQL_SET_STATEMENT_ROWS_EXAMINED(m_statement_psi, m_examined_row_count); } void THD::inc_status_created_tmp_disk_tables() { DBUG_ASSERT(!status_var_aggregated); status_var.created_tmp_disk_tables++; #ifdef HAVE_PSI_STATEMENT_INTERFACE PSI_STATEMENT_CALL(inc_statement_created_tmp_disk_tables)(m_statement_psi, 1); #endif } void THD::inc_status_created_tmp_tables() { DBUG_ASSERT(!status_var_aggregated); status_var.created_tmp_tables++; #ifdef HAVE_PSI_STATEMENT_INTERFACE PSI_STATEMENT_CALL(inc_statement_created_tmp_tables)(m_statement_psi, 1); #endif } void THD::inc_status_select_full_join() { DBUG_ASSERT(!status_var_aggregated); status_var.select_full_join_count++; #ifdef HAVE_PSI_STATEMENT_INTERFACE PSI_STATEMENT_CALL(inc_statement_select_full_join)(m_statement_psi, 1); #endif } void THD::inc_status_select_full_range_join() { DBUG_ASSERT(!status_var_aggregated); status_var.select_full_range_join_count++; #ifdef HAVE_PSI_STATEMENT_INTERFACE PSI_STATEMENT_CALL(inc_statement_select_full_range_join)(m_statement_psi, 1); #endif } void THD::inc_status_select_range() { DBUG_ASSERT(!status_var_aggregated); status_var.select_range_count++; #ifdef HAVE_PSI_STATEMENT_INTERFACE PSI_STATEMENT_CALL(inc_statement_select_range)(m_statement_psi, 1); #endif } void THD::inc_status_select_range_check() { DBUG_ASSERT(!status_var_aggregated); status_var.select_range_check_count++; #ifdef HAVE_PSI_STATEMENT_INTERFACE PSI_STATEMENT_CALL(inc_statement_select_range_check)(m_statement_psi, 1); #endif } void THD::inc_status_select_scan() { DBUG_ASSERT(!status_var_aggregated); status_var.select_scan_count++; #ifdef HAVE_PSI_STATEMENT_INTERFACE PSI_STATEMENT_CALL(inc_statement_select_scan)(m_statement_psi, 1); #endif } void THD::inc_status_sort_merge_passes() { DBUG_ASSERT(!status_var_aggregated); status_var.filesort_merge_passes++; #ifdef HAVE_PSI_STATEMENT_INTERFACE PSI_STATEMENT_CALL(inc_statement_sort_merge_passes)(m_statement_psi, 1); #endif } void THD::inc_status_sort_range() { DBUG_ASSERT(!status_var_aggregated); status_var.filesort_range_count++; #ifdef HAVE_PSI_STATEMENT_INTERFACE PSI_STATEMENT_CALL(inc_statement_sort_range)(m_statement_psi, 1); #endif } void THD::inc_status_sort_rows(ha_rows count) { DBUG_ASSERT(!status_var_aggregated); status_var.filesort_rows += count; #ifdef HAVE_PSI_STATEMENT_INTERFACE PSI_STATEMENT_CALL(inc_statement_sort_rows) (m_statement_psi, static_cast(count)); #endif } void THD::inc_status_sort_scan() { DBUG_ASSERT(!status_var_aggregated); status_var.filesort_scan_count++; #ifdef HAVE_PSI_STATEMENT_INTERFACE PSI_STATEMENT_CALL(inc_statement_sort_scan)(m_statement_psi, 1); #endif } void THD::set_status_no_index_used() { server_status |= SERVER_QUERY_NO_INDEX_USED; #ifdef HAVE_PSI_STATEMENT_INTERFACE PSI_STATEMENT_CALL(set_statement_no_index_used)(m_statement_psi); #endif } void THD::set_status_no_good_index_used() { server_status |= SERVER_QUERY_NO_GOOD_INDEX_USED; #ifdef HAVE_PSI_STATEMENT_INTERFACE PSI_STATEMENT_CALL(set_statement_no_good_index_used)(m_statement_psi); #endif } void THD::set_command(enum enum_server_command command) { m_command = command; #ifdef HAVE_PSI_THREAD_INTERFACE PSI_THREAD_CALL(set_thread_command)(m_command); #endif } void THD::debug_assert_query_locked() const { if (current_thd != this) mysql_mutex_assert_owner(&LOCK_thd_query); } void THD::set_query(const LEX_CSTRING &query_arg) { DBUG_ASSERT(this == current_thd); mysql_mutex_lock(&LOCK_thd_query); m_query_string = query_arg; mysql_mutex_unlock(&LOCK_thd_query); } /** Leave explicit LOCK TABLES or prelocked mode and restore value of transaction sentinel in MDL subsystem. */ void THD::leave_locked_tables_mode() { if (locked_tables_mode == LTM_LOCK_TABLES) { /* When leaving LOCK TABLES mode we have to change the duration of most of the metadata locks being held, except for HANDLER and GRL locks, to transactional for them to be properly released at UNLOCK TABLES. */ mdl_context.set_transaction_duration_for_all_locks(); /* Make sure we don't release the global read lock and commit blocker when leaving LTM. */ global_read_lock.set_explicit_lock_duration(this); /* Also ensure that we don't release metadata locks for open HANDLERs and user-level locks. */ if (!handler_tables_hash.empty()) mysql_ha_set_explicit_lock_duration(this); if (!ull_hash.empty()) mysql_ull_set_explicit_lock_duration(this); } locked_tables_mode = LTM_NONE; } void THD::get_definer(LEX_USER *definer) { binlog_invoker(); if (slave_thread && has_invoker()) { definer->user = m_invoker_user; definer->host = m_invoker_host; definer->plugin.str = ""; definer->plugin.length = 0; definer->auth.str = NULL; definer->auth.length = 0; } else get_default_definer(this, definer); } /** Mark transaction to rollback and mark error as fatal to a sub-statement. @param all true <=> rollback main transaction. */ void THD::mark_transaction_to_rollback(bool all) { /* There is no point in setting is_fatal_sub_stmt_error unless we are actually in_sub_stmt. */ if (in_sub_stmt) is_fatal_sub_stmt_error = true; transaction_rollback_request = all; } void THD::set_next_event_pos(const char *_filename, ulonglong _pos) { char *&filename = binlog_next_event_pos.file_name; if (filename == NULL) { /* First time, allocate maximal buffer */ filename = (char *)my_malloc(key_memory_LOG_POS_COORD, FN_REFLEN + 1, MYF(MY_WME)); if (filename == NULL) return; } assert(strlen(_filename) <= FN_REFLEN); strcpy(filename, _filename); filename[FN_REFLEN] = 0; binlog_next_event_pos.pos = _pos; } void THD::clear_next_event_pos() { if (binlog_next_event_pos.file_name != NULL) { my_free(binlog_next_event_pos.file_name); } binlog_next_event_pos.file_name = NULL; binlog_next_event_pos.pos = 0; } void THD::set_original_commit_timestamp_for_slave_thread() { /* This function may be called in four cases: - From SQL thread while executing Gtid_log_event::do_apply_event - From an mts worker thread that executes a Gtid_log_event::do_apply_event. - From an mts worker thread that is processing an old binlog that is missing Gtid events completely, from gtid_pre_statement_checks(). - From a normal client thread that is executing output from mysqlbinlog when mysqlbinlog is processing an old binlog file that is missing Gtid events completely, from gtid_pre_statement_checks() for a statement that appears after a BINLOG statement containing a Format_description_log_event originating from the master. Because of the last case, we need to add the following conditions to set original_commit_timestamp. */ if (system_thread == SYSTEM_THREAD_SLAVE_SQL || system_thread == SYSTEM_THREAD_SLAVE_WORKER) { rli_slave->original_commit_timestamp = variables.original_commit_timestamp; } } void THD::set_user_connect(USER_CONN *uc) { DBUG_TRACE; m_user_connect = uc; } void THD::increment_user_connections_counter() { DBUG_TRACE; m_user_connect->connections++; } void THD::decrement_user_connections_counter() { DBUG_TRACE; DBUG_ASSERT(m_user_connect->connections > 0); m_user_connect->connections--; } void THD::increment_con_per_hour_counter() { DBUG_TRACE; m_user_connect->conn_per_hour++; } void THD::increment_updates_counter() { DBUG_TRACE; m_user_connect->updates++; } void THD::increment_questions_counter() { DBUG_TRACE; m_user_connect->questions++; } /* Reset per-hour user resource limits when it has been more than an hour since they were last checked SYNOPSIS: time_out_user_resource_limits() NOTE: This assumes that the LOCK_user_conn mutex has been acquired, so it is safe to test and modify members of the USER_CONN structure. */ void THD::time_out_user_resource_limits() { mysql_mutex_assert_owner(&LOCK_user_conn); ulonglong check_time = start_utime; DBUG_TRACE; /* If more than a hour since last check, reset resource checking */ if (check_time - m_user_connect->reset_utime >= 3600000000LL) { m_user_connect->questions = 1; m_user_connect->updates = 0; m_user_connect->conn_per_hour = 0; m_user_connect->reset_utime = check_time; } } #ifndef DBUG_OFF void THD::Query_plan::assert_plan_is_locked_if_other() const { if (current_thd != thd) mysql_mutex_assert_owner(&thd->LOCK_query_plan); } #endif void THD::Query_plan::set_query_plan(enum_sql_command sql_cmd, LEX *lex_arg, bool ps) { DBUG_ASSERT(current_thd == thd); // No need to grab mutex for repeated (SQLCOM_END, NULL, false). if (sql_command == sql_cmd && lex == lex_arg && is_ps == ps) { return; } thd->lock_query_plan(); sql_command = sql_cmd; lex = lex_arg; is_ps = ps; thd->unlock_query_plan(); } void THD::Query_plan::set_modification_plan(Modification_plan *plan_arg) { DBUG_ASSERT(current_thd == thd); mysql_mutex_assert_owner(&thd->LOCK_query_plan); modification_plan = plan_arg; } /** Push an error message into MySQL diagnostic area with line number and position This function provides semantic action implementers with a way to push the famous "You have a syntax error near..." error message into the diagnostic area, which is normally produced only if a syntax error is discovered according to the Bison grammar. Unlike the syntax_error_at() function, the error position points to the last parsed token. @note Parse-time only function! @param format Error format message. NULL means ER(ER_SYNTAX_ERROR). */ void THD::syntax_error(const char *format, ...) { va_list args; va_start(args, format); vsyntax_error_at(m_parser_state->m_lip.get_tok_start(), format, args); va_end(args); } /** Push an error message into MySQL diagnostic area with line number and position This function provides semantic action implementers with a way to push the famous "You have a syntax error near..." error message into the diagnostic area, which is normally produced only if a syntax error is discovered according to the Bison grammar. Unlike the syntax_error_at() function, the error position points to the last parsed token. @note Parse-time only function! @param mysql_errno Error number to get a format string with ER_THD(). */ void THD::syntax_error(int mysql_errno, ...) { va_list args; va_start(args, mysql_errno); vsyntax_error_at(m_parser_state->m_lip.get_tok_start(), ER_THD_NONCONST(this, mysql_errno), args); va_end(args); } /** Push a syntax error message into MySQL diagnostic area with line and position information. This function provides semantic action implementers with a way to push the famous "You have a syntax error near..." error message into the diagnostic area, which is normally produced only if a parse error is discovered internally by the Bison generated parser. @note Parse-time only function! @param location YYSTYPE object: error position. @param format Error format message. NULL means ER(ER_SYNTAX_ERROR). */ void THD::syntax_error_at(const YYLTYPE &location, const char *format, ...) { va_list args; va_start(args, format); vsyntax_error_at(location, format, args); va_end(args); } /** Push a syntax error message into MySQL diagnostic area with line and position information. This function provides semantic action implementers with a way to push the famous "You have a syntax error near..." error message into the diagnostic area, which is normally produced only if a parse error is discovered internally by the Bison generated parser. @note Parse-time only function! @param location YYSTYPE object: error position @param mysql_errno Error number to get a format string with ER_THD() */ void THD::syntax_error_at(const YYLTYPE &location, int mysql_errno, ...) { va_list args; va_start(args, mysql_errno); vsyntax_error_at(location, ER_THD_NONCONST(this, mysql_errno), args); va_end(args); } void THD::vsyntax_error_at(const YYLTYPE &location, const char *format, va_list args) { vsyntax_error_at(location.raw.start, format, args); } /** Push a syntax error message into MySQL diagnostic area with line number and position This function provides semantic action implementers with a way to push the famous "You have a syntax error near..." error message into the error stack, which is normally produced only if a parse error is discovered internally by the Bison generated parser. @param pos_in_lexer_raw_buffer Pointer into LEX::m_buf or NULL. @param format An error message format string. @param args Arguments to the format string. */ void THD::vsyntax_error_at(const char *pos_in_lexer_raw_buffer, const char *format, va_list args) { DBUG_ASSERT( pos_in_lexer_raw_buffer == NULL || (pos_in_lexer_raw_buffer >= m_parser_state->m_lip.get_buf() && pos_in_lexer_raw_buffer <= m_parser_state->m_lip.get_end_of_query())); char buff[MYSQL_ERRMSG_SIZE]; if (check_stack_overrun(this, STACK_MIN_SIZE, (uchar *)buff)) return; const uint lineno = pos_in_lexer_raw_buffer ? m_parser_state->m_lip.get_lineno(pos_in_lexer_raw_buffer) : 1; const char *pos = pos_in_lexer_raw_buffer ? pos_in_lexer_raw_buffer : ""; ErrConvString err(pos, variables.character_set_client); (void)vsnprintf(buff, sizeof(buff), format, args); my_printf_error(ER_PARSE_ERROR, ER_THD(this, ER_PARSE_ERROR), MYF(0), buff, err.ptr(), lineno); } bool THD::send_result_metadata(List *list, uint flags) { DBUG_TRACE; List_iterator_fast it(*list); Item *item; uchar buff[MAX_FIELD_WIDTH]; String tmp((char *)buff, sizeof(buff), &my_charset_bin); if (m_protocol->start_result_metadata(list->elements, flags, variables.character_set_results)) goto err; switch (variables.resultset_metadata) { case RESULTSET_METADATA_FULL: /* Sent metadata. */ while ((item = it++)) { Send_field field; item->make_field(&field); m_protocol->start_row(); if (m_protocol->send_field_metadata(&field, item->charset_for_protocol())) goto err; if (flags & Protocol::SEND_DEFAULTS) item->send(m_protocol, &tmp); if (m_protocol->end_row()) return true; } break; case RESULTSET_METADATA_NONE: /* Skip metadata. */ break; default: /* Unknown @@resultset_metadata value. */ return 1; } return m_protocol->end_result_metadata(); err: my_error(ER_OUT_OF_RESOURCES, MYF(0)); /* purecov: inspected */ return 1; /* purecov: inspected */ } bool THD::send_result_set_row(List *row_items) { char buffer[MAX_FIELD_WIDTH]; String str_buffer(buffer, sizeof(buffer), &my_charset_bin); List_iterator_fast it(*row_items); DBUG_TRACE; for (Item *item = it++; item; item = it++) { if (item->send(m_protocol, &str_buffer) || is_error()) return true; /* Reset str_buffer to its original state, as it may have been altered in Item::send(). */ str_buffer.set(buffer, sizeof(buffer), &my_charset_bin); } return false; } void THD::send_statement_status() { DBUG_TRACE; DBUG_ASSERT(!get_stmt_da()->is_sent()); bool error = false; Diagnostics_area *da = get_stmt_da(); /* Can not be true, but do not take chances in production. */ if (da->is_sent()) return; switch (da->status()) { case Diagnostics_area::DA_ERROR: /* The query failed, send error to log and abort bootstrap. */ error = m_protocol->send_error(da->mysql_errno(), da->message_text(), da->returned_sqlstate()); break; case Diagnostics_area::DA_EOF: error = m_protocol->send_eof(server_status, da->last_statement_cond_count()); break; case Diagnostics_area::DA_OK: error = m_protocol->send_ok( server_status, da->last_statement_cond_count(), da->affected_rows(), da->last_insert_id(), da->message_text()); break; case Diagnostics_area::DA_DISABLED: break; case Diagnostics_area::DA_EMPTY: default: DBUG_ASSERT(0); error = m_protocol->send_ok(server_status, 0, 0, 0, nullptr); break; } if (!error) da->set_is_sent(true); } void THD::claim_memory_ownership() { /* Ownership of the THD object is transfered to this thread. This happens typically: - in the event scheduler, when the scheduler thread creates a work item and starts a worker thread to run it - in the main thread, when the code that accepts a new network connection creates a work item and starts a connection thread to run it. Accounting for memory statistics needs to be told that memory allocated by thread X now belongs to thread Y, so that statistics by thread/account/user/host are accurate. Inspect every piece of memory allocated in THD, and call PSI_MEMORY_CALL(memory_claim)(). */ #ifdef HAVE_PSI_MEMORY_INTERFACE main_mem_root.Claim(); my_claim(m_token_array); Protocol_classic *p = get_protocol_classic(); if (p != NULL) p->claim_memory_ownership(); session_tracker.claim_memory_ownership(); session_sysvar_res_mgr.claim_memory_ownership(); for (const auto &key_and_value : user_vars) { my_claim(key_and_value.second.get()); } #if defined(ENABLED_DEBUG_SYNC) debug_sync_claim_memory_ownership(this); #endif /* defined(ENABLED_DEBUG_SYNC) */ get_transaction()->claim_memory_ownership(); stmt_map.claim_memory_ownership(); #endif /* HAVE_PSI_MEMORY_INTERFACE */ } void THD::rpl_detach_engine_ha_data() { Relay_log_info *rli = is_binlog_applier() ? rli_fake : (slave_thread ? rli_slave : NULL); DBUG_ASSERT(!rli_fake || !rli_fake->is_engine_ha_data_detached); DBUG_ASSERT(!rli_slave || !rli_slave->is_engine_ha_data_detached); if (rli) rli->detach_engine_ha_data(this); } void THD::rpl_reattach_engine_ha_data() { Relay_log_info *rli = is_binlog_applier() ? rli_fake : (slave_thread ? rli_slave : NULL); DBUG_ASSERT(!rli_fake || !rli_fake->is_engine_ha_data_detached); DBUG_ASSERT(!rli_slave || !rli_slave->is_engine_ha_data_detached); if (rli) rli->reattach_engine_ha_data(this); } bool THD::rpl_unflag_detached_engine_ha_data() const { Relay_log_info *rli = is_binlog_applier() ? rli_fake : (slave_thread ? rli_slave : NULL); return rli ? rli->unflag_detached_engine_ha_data() : false; } /** Determine if binlogging is disabled for this session @retval 0 if the current statement binlogging is disabled (could be because of binlog closed/binlog option is set to false). @retval 1 if the current statement will be binlogged */ bool THD::is_current_stmt_binlog_disabled() const { return (!(variables.option_bits & OPTION_BIN_LOG) || !mysql_bin_log.is_open()); } bool THD::is_current_stmt_binlog_row_enabled_with_write_set_extraction() const { return ((variables.transaction_write_set_extraction != HASH_ALGORITHM_OFF) && is_current_stmt_binlog_format_row() && !is_current_stmt_binlog_disabled()); } bool THD::Query_plan::is_single_table_plan() const { assert_plan_is_locked_if_other(); return lex->m_sql_cmd->is_single_table_plan(); } const String THD::normalized_query() { m_normalized_query.mem_free(); lex->unit->print(this, &m_normalized_query, QT_NORMALIZED_FORMAT); return m_normalized_query; } bool add_item_to_list(THD *thd, Item *item) { return thd->lex->select_lex->add_item_to_list(item); } void add_order_to_list(THD *thd, ORDER *order) { thd->lex->select_lex->add_order_to_list(order); } THD::Transaction_state::Transaction_state() : m_query_tables_list(new Query_tables_list()), m_ha_data(PSI_NOT_INSTRUMENTED, m_ha_data.initial_capacity) {} THD::Transaction_state::~Transaction_state() { delete m_query_tables_list; } void THD::change_item_tree(Item **place, Item *new_value) { /* TODO: check for OOM condition here */ if (!stmt_arena->is_regular()) { DBUG_PRINT("info", ("change_item_tree place %p old_value %p new_value %p", place, *place, new_value)); if (new_value) new_value->set_runtime_created(); /* Note the change of item tree */ nocheck_register_item_tree_change(place, new_value); } *place = new_value; } bool THD::notify_hton_pre_acquire_exclusive(const MDL_key *mdl_key, bool *victimized) { return ha_notify_exclusive_mdl(this, mdl_key, HA_NOTIFY_PRE_EVENT, victimized); } void THD::notify_hton_post_release_exclusive(const MDL_key *mdl_key) { bool unused_arg; ha_notify_exclusive_mdl(this, mdl_key, HA_NOTIFY_POST_EVENT, &unused_arg); } void reattach_engine_ha_data_to_thd(THD *thd, const struct handlerton *hton) { DBUG_TRACE; if (hton->replace_native_transaction_in_thd) { /* restore the saved original engine transaction's link with thd */ void **trx_backup = &thd->get_ha_data(hton->slot)->ha_ptr_backup; hton->replace_native_transaction_in_thd(thd, *trx_backup, NULL); *trx_backup = NULL; } } /** Call parser to transform statement into a parse tree. Then, transform the parse tree further into an AST, ready for resolving. */ bool THD::sql_parser() { /* SQL parser function generated by YACC from sql_yacc.yy. In the case of success returns 0, and THD::is_error() is false. Otherwise returns 1, or THD::>is_error() is true. The second (output) parameter "root" returns the new parse tree. It is undefined (unchanged) on error. If "root" is NULL on success, then the parser has already called lex->make_sql_cmd() internally. */ extern int MYSQLparse(class THD * thd, class Parse_tree_root * *root); Parse_tree_root *root = nullptr; if (MYSQLparse(this, &root) || is_error()) { /* Restore the original LEX if it was replaced when parsing a stored procedure. We must ensure that a parsing error does not leave any side effects in the THD. */ cleanup_after_parse_error(); return true; } if (root != nullptr && lex->make_sql_cmd(root)) { return true; } return false; } bool THD::is_one_phase_commit() { /* Check if XA Commit. */ if (lex->sql_command != SQLCOM_XA_COMMIT) { return (false); } auto xa_commit_cmd = static_cast(lex->m_sql_cmd); auto xa_op = xa_commit_cmd->get_xa_opt(); return (xa_op == XA_ONE_PHASE); } bool THD::secondary_storage_engine_eligible() const { return secondary_engine_optimization() != Secondary_engine_optimization::PRIMARY_ONLY && variables.use_secondary_engine != SECONDARY_ENGINE_OFF && locked_tables_mode == LTM_NONE && !in_multi_stmt_transaction_mode() && sp_runtime_ctx == nullptr; } /** Restore session state in case of parse error. This is a clean up function that is invoked after the Bison generated parser before returning an error from THD::sql_parser(). If your semantic actions manipulate with the session state (which is a very bad practice and should not normally be employed) and need a clean-up in case of error, and you can not use %destructor rule in the grammar file itself, this function should be used to implement the clean up. */ void THD::cleanup_after_parse_error() { sp_head *sp = lex->sphead; if (sp) { sp->m_parser_data.finish_parsing_sp_body(this); // Do not delete sp_head if is invoked in the context of sp execution. if (sp_runtime_ctx == NULL) { sp_head::destroy(sp); lex->sphead = NULL; } } } bool THD::is_classic_protocol() const { return get_protocol()->type() == Protocol::PROTOCOL_BINARY || get_protocol()->type() == Protocol::PROTOCOL_TEXT; } bool THD::is_connected() { /* All system threads (e.g., the slave IO thread) are connected but not using vio. So this function always returns true for all system threads. */ if (system_thread) return true; if (is_classic_protocol()) return get_protocol()->connection_alive() && vio_is_connected(get_protocol_classic()->get_vio()); return get_protocol()->connection_alive(); } void THD::push_protocol(Protocol *protocol) { DBUG_ASSERT(m_protocol != nullptr); DBUG_ASSERT(protocol != nullptr); m_protocol->push_protocol(protocol); m_protocol = protocol; } void THD::pop_protocol() { DBUG_ASSERT(m_protocol != nullptr); m_protocol = m_protocol->pop_protocol(); DBUG_ASSERT(m_protocol != nullptr); }