用于EagleEye3.0 规则集漏报和误报测试的示例项目,项目收集于github和gitee
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2805 lines
92 KiB

/*
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 <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <algorithm>
#include <utility>
#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<char *>(m_db.str), new_db.str, new_db.length + 1);
else {
my_free(const_cast<char *>(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<int>(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<char *>(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<Security_context> 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, &not_used);
charset_is_collation_connection =
!String::needs_conversion(0, variables.character_set_client,
variables.collation_connection, &not_used);
charset_is_character_set_filesystem =
!String::needs_conversion(0, variables.character_set_client,
variables.character_set_filesystem, &not_used);
}
int THD::send_explain_fields(Query_result *result) {
List<Item> 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<Item_change_record> 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<Item_change_record> 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<Prepared_statement>(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<ulong>(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<Item> *list, uint flags) {
DBUG_TRACE;
List_iterator_fast<Item> 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<Item> *row_items) {
char buffer[MAX_FIELD_WIDTH];
String str_buffer(buffer, sizeof(buffer), &my_charset_bin);
List_iterator_fast<Item> 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<Sql_cmd_xa_commit *>(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);
}