Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
8767 lines (7683 sloc) 274 KB
/* Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#define MYSQL_LEX 1
#include "my_global.h"
#include "sql_priv.h"
#include "unireg.h" // REQUIRED: for other includes
#include "sql_parse.h" // sql_kill, *_precheck, *_prepare
#include "lock.h" // try_transactional_lock,
// check_transactional_lock,
// set_handler_table_locks,
// lock_global_read_lock,
// make_global_read_lock_block_commit
#include "sql_base.h" // find_temporary_table
#include "sql_cache.h" // QUERY_CACHE_FLAGS_SIZE, query_cache_*
#include "sql_show.h" // mysqld_list_*, mysqld_show_*,
// calc_sum_of_all_status
#include "mysqld.h"
#include "sql_locale.h" // my_locale_en_US
#include "log.h" // flush_error_log
#include "sql_view.h" // mysql_create_view, mysql_drop_view
#include "sql_delete.h" // mysql_delete
#include "sql_insert.h" // mysql_insert
#include "sql_update.h" // mysql_update, mysql_multi_update
#include "sql_partition.h" // struct partition_info
#include "sql_db.h" // mysql_change_db, mysql_create_db,
// mysql_rm_db, mysql_upgrade_db,
// mysql_alter_db,
// check_db_dir_existence,
// my_dbopt_cleanup
#include "sql_table.h" // mysql_create_like_table,
// mysql_create_table,
// mysql_alter_table,
// mysql_backup_table,
// mysql_restore_table
#include "sql_reload.h" // reload_acl_and_cache
#include "sql_admin.h" // mysql_assign_to_keycache
#include "sql_connect.h" // check_user,
// decrease_user_connections,
// thd_init_client_charset, check_mqh,
// reset_mqh
#include "sql_rename.h" // mysql_rename_table
#include "sql_tablespace.h" // mysql_alter_tablespace
#include "hostname.h" // hostname_cache_refresh
#include "sql_acl.h" // *_ACL, check_grant, is_acl_user,
// has_any_table_level_privileges,
// mysql_drop_user, mysql_rename_user,
// check_grant_routine,
// mysql_routine_grant,
// mysql_show_grants,
// sp_grant_privileges, ...
#include "sql_test.h" // mysql_print_status
#include "sql_select.h" // handle_select, mysql_select,
#include "sql_load.h" // mysql_load
#include "sql_servers.h" // create_servers, alter_servers,
// drop_servers, servers_reload
#include "sql_handler.h" // mysql_ha_open, mysql_ha_close,
// mysql_ha_read
#include "sql_binlog.h" // mysql_client_binlog_statement
#include "sql_do.h" // mysql_do
#include "sql_help.h" // mysqld_help
#include "rpl_constants.h" // Incident, INCIDENT_LOST_EVENTS
#include "log_event.h"
#include "rpl_slave.h"
#include "rpl_master.h"
#include "rpl_filter.h"
#include <m_ctype.h>
#include <myisam.h>
#include <my_dir.h>
#include "rpl_handler.h"
#include "sp_head.h"
#include "sp.h"
#include "sp_cache.h"
#include "events.h"
#include "sql_trigger.h"
#include "transaction.h"
#include "sql_audit.h"
#include "sql_prepare.h"
#include "debug_sync.h"
#include "probes_mysql.h"
#include "set_var.h"
#include "opt_trace.h"
#include "mysql/psi/mysql_statement.h"
#include "sql_bootstrap.h"
#include "opt_explain.h"
#include "sql_rewrite.h"
#include "global_threads.h"
#include "sql_analyse.h"
#include "table_cache.h" // table_cache_manager
#include "sql_filter.h"
#include "rpl_gtid.h" // set executed_gtid_set
#include "sql_digest.h"
#include "threadpool.h"
#include <algorithm>
using std::max;
using std::min;
#define FLAGSTR(V,F) ((V)&(F)?#F" ":"")
/**
@defgroup Runtime_Environment Runtime Environment
@{
*/
/* Used in error handling only */
#define SP_TYPE_STRING(LP) \
((LP)->sphead->m_type == SP_TYPE_FUNCTION ? "FUNCTION" : "PROCEDURE")
#define SP_COM_STRING(LP) \
((LP)->sql_command == SQLCOM_CREATE_SPFUNCTION || \
(LP)->sql_command == SQLCOM_ALTER_FUNCTION || \
(LP)->sql_command == SQLCOM_SHOW_CREATE_FUNC || \
(LP)->sql_command == SQLCOM_DROP_FUNCTION ? \
"FUNCTION" : "PROCEDURE")
static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables);
static bool check_show_access(THD *thd, TABLE_LIST *table);
static void sql_kill(THD *thd, ulong id, bool only_kill_query);
static bool lock_tables_precheck(THD *thd, TABLE_LIST *tables);
const char *any_db="*any*"; // Special symbol for check_access
const LEX_STRING command_name[]={
{ C_STRING_WITH_LEN("Sleep") },
{ C_STRING_WITH_LEN("Quit") },
{ C_STRING_WITH_LEN("Init DB") },
{ C_STRING_WITH_LEN("Query") },
{ C_STRING_WITH_LEN("Field List") },
{ C_STRING_WITH_LEN("Create DB") },
{ C_STRING_WITH_LEN("Drop DB") },
{ C_STRING_WITH_LEN("Refresh") },
{ C_STRING_WITH_LEN("Shutdown") },
{ C_STRING_WITH_LEN("Statistics") },
{ C_STRING_WITH_LEN("Processlist") },
{ C_STRING_WITH_LEN("Connect") },
{ C_STRING_WITH_LEN("Kill") },
{ C_STRING_WITH_LEN("Debug") },
{ C_STRING_WITH_LEN("Ping") },
{ C_STRING_WITH_LEN("Time") },
{ C_STRING_WITH_LEN("Delayed insert") },
{ C_STRING_WITH_LEN("Change user") },
{ C_STRING_WITH_LEN("Binlog Dump") },
{ C_STRING_WITH_LEN("Table Dump") },
{ C_STRING_WITH_LEN("Connect Out") },
{ C_STRING_WITH_LEN("Register Slave") },
{ C_STRING_WITH_LEN("Prepare") },
{ C_STRING_WITH_LEN("Execute") },
{ C_STRING_WITH_LEN("Long Data") },
{ C_STRING_WITH_LEN("Close stmt") },
{ C_STRING_WITH_LEN("Reset stmt") },
{ C_STRING_WITH_LEN("Set option") },
{ C_STRING_WITH_LEN("Fetch") },
{ C_STRING_WITH_LEN("Daemon") },
{ C_STRING_WITH_LEN("Binlog Dump GTID") },
{ C_STRING_WITH_LEN("Error") } // Last command number
};
const char *xa_state_names[]={
"NON-EXISTING", "ACTIVE", "IDLE", "PREPARED", "ROLLBACK ONLY"
};
Slow_log_throttle log_throttle_qni(&opt_log_throttle_queries_not_using_indexes,
&LOCK_log_throttle_qni,
Log_throttle::LOG_THROTTLE_WINDOW_SIZE,
slow_log_print,
"throttle: %10lu 'index "
"not used' warning(s) suppressed.");
#ifdef HAVE_REPLICATION
/**
Returns true if all tables should be ignored.
*/
inline bool all_tables_not_ok(THD *thd, TABLE_LIST *tables)
{
return rpl_filter->is_on() && tables && !thd->sp_runtime_ctx &&
!rpl_filter->tables_ok(thd->db, tables);
}
/**
Checks whether the event for the given database, db, should
be ignored or not. This is done by checking whether there are
active rules in ignore_db or in do_db containers. If there
are, then check if there is a match, if not then check the
wild_do rules.
NOTE: This means that when using this function replicate-do-db
and replicate-ignore-db take precedence over wild do
rules.
@param thd Thread handle.
@param db Database name used while evaluating the filtering
rules.
*/
inline bool db_stmt_db_ok(THD *thd, char* db)
{
DBUG_ENTER("db_stmt_db_ok");
if (!thd->slave_thread)
DBUG_RETURN(TRUE);
/*
No filters exist in ignore/do_db ? Then, just check
wild_do_table filtering. Otherwise, check the do_db
rules.
*/
bool db_ok= (rpl_filter->get_do_db()->is_empty() &&
rpl_filter->get_ignore_db()->is_empty()) ?
rpl_filter->db_ok_with_wild_table(db) :
rpl_filter->db_ok(db);
DBUG_RETURN(db_ok);
}
#endif
static bool some_non_temp_table_to_be_updated(THD *thd, TABLE_LIST *tables)
{
for (TABLE_LIST *table= tables; table; table= table->next_global)
{
DBUG_ASSERT(table->db && table->table_name);
if (table->updating && !find_temporary_table(thd, table))
return 1;
}
return 0;
}
/*
Implicitly commit a active transaction if statement requires so.
@param thd Thread handle.
@param mask Bitmask used for the SQL command match.
*/
bool stmt_causes_implicit_commit(const THD *thd, uint mask)
{
const LEX *lex= thd->lex;
bool skip= FALSE;
DBUG_ENTER("stmt_causes_implicit_commit");
if (!(sql_command_flags[lex->sql_command] & mask))
DBUG_RETURN(FALSE);
switch (lex->sql_command) {
case SQLCOM_DROP_TABLE:
skip= lex->drop_temporary;
break;
case SQLCOM_ALTER_TABLE:
case SQLCOM_CREATE_TABLE:
/* If CREATE TABLE of non-temporary table, do implicit commit */
skip= (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE);
break;
case SQLCOM_SET_OPTION:
skip= lex->autocommit ? FALSE : TRUE;
break;
default:
break;
}
DBUG_RETURN(!skip);
}
/**
Mark all commands that somehow changes a table.
This is used to check number of updates / hour.
sql_command is actually set to SQLCOM_END sometimes
so we need the +1 to include it in the array.
See COMMAND_FLAG_xxx for different type of commands
2 - query that returns meaningful ROW_COUNT() -
a number of modified rows
*/
uint sql_command_flags[SQLCOM_END+1];
uint server_command_flags[COM_END+1];
void init_update_queries(void)
{
/* Initialize the server command flags array. */
memset(server_command_flags, 0, sizeof(server_command_flags));
server_command_flags[COM_STATISTICS]= CF_SKIP_QUESTIONS;
server_command_flags[COM_PING]= CF_SKIP_QUESTIONS;
server_command_flags[COM_STMT_PREPARE]= CF_SKIP_QUESTIONS;
server_command_flags[COM_STMT_CLOSE]= CF_SKIP_QUESTIONS;
server_command_flags[COM_STMT_RESET]= CF_SKIP_QUESTIONS;
/* Initialize the sql command flags array. */
memset(sql_command_flags, 0, sizeof(sql_command_flags));
/*
In general, DDL statements do not generate row events and do not go
through a cache before being written to the binary log. However, the
CREATE TABLE...SELECT is an exception because it may generate row
events. For that reason, the SQLCOM_CREATE_TABLE which represents
a CREATE TABLE, including the CREATE TABLE...SELECT, has the
CF_CAN_GENERATE_ROW_EVENTS flag. The distinction between a regular
CREATE TABLE and the CREATE TABLE...SELECT is made in other parts of
the code, in particular in the Query_log_event's constructor.
*/
sql_command_flags[SQLCOM_CREATE_TABLE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
CF_AUTO_COMMIT_TRANS |
CF_CAN_GENERATE_ROW_EVENTS;
sql_command_flags[SQLCOM_CREATE_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_ALTER_TABLE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND |
CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_TRUNCATE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND |
CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_DROP_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_LOAD]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
CF_CAN_GENERATE_ROW_EVENTS;
sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_ALTER_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_RENAME_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_DROP_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_CREATE_VIEW]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_DROP_VIEW]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_CREATE_TRIGGER]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_DROP_TRIGGER]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_CREATE_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_ALTER_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_DROP_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_UPDATE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
CF_CAN_GENERATE_ROW_EVENTS |
CF_OPTIMIZER_TRACE |
CF_CAN_BE_EXPLAINED;
sql_command_flags[SQLCOM_UPDATE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
CF_CAN_GENERATE_ROW_EVENTS |
CF_OPTIMIZER_TRACE |
CF_CAN_BE_EXPLAINED;
// This is INSERT VALUES(...), can be VALUES(stored_func()) so we trace it
sql_command_flags[SQLCOM_INSERT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
CF_CAN_GENERATE_ROW_EVENTS |
CF_OPTIMIZER_TRACE |
CF_CAN_BE_EXPLAINED;
sql_command_flags[SQLCOM_INSERT_SELECT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
CF_CAN_GENERATE_ROW_EVENTS |
CF_OPTIMIZER_TRACE |
CF_CAN_BE_EXPLAINED;
sql_command_flags[SQLCOM_DELETE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
CF_CAN_GENERATE_ROW_EVENTS |
CF_OPTIMIZER_TRACE |
CF_CAN_BE_EXPLAINED;
sql_command_flags[SQLCOM_DELETE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
CF_CAN_GENERATE_ROW_EVENTS |
CF_OPTIMIZER_TRACE |
CF_CAN_BE_EXPLAINED;
sql_command_flags[SQLCOM_REPLACE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
CF_CAN_GENERATE_ROW_EVENTS |
CF_OPTIMIZER_TRACE |
CF_CAN_BE_EXPLAINED;
sql_command_flags[SQLCOM_REPLACE_SELECT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE |
CF_CAN_GENERATE_ROW_EVENTS |
CF_OPTIMIZER_TRACE |
CF_CAN_BE_EXPLAINED;
sql_command_flags[SQLCOM_SELECT]= CF_REEXECUTION_FRAGILE |
CF_CAN_GENERATE_ROW_EVENTS |
CF_OPTIMIZER_TRACE |
CF_CAN_BE_EXPLAINED;
// (1) so that subquery is traced when doing "SET @var = (subquery)"
/*
@todo SQLCOM_SET_OPTION should have CF_CAN_GENERATE_ROW_EVENTS
set, because it may invoke a stored function that generates row
events. /Sven
*/
sql_command_flags[SQLCOM_SET_OPTION]= CF_REEXECUTION_FRAGILE |
CF_AUTO_COMMIT_TRANS |
CF_CAN_GENERATE_ROW_EVENTS |
CF_OPTIMIZER_TRACE; // (1)
// (1) so that subquery is traced when doing "DO @var := (subquery)"
sql_command_flags[SQLCOM_DO]= CF_REEXECUTION_FRAGILE |
CF_CAN_GENERATE_ROW_EVENTS |
CF_OPTIMIZER_TRACE; // (1)
sql_command_flags[SQLCOM_SHOW_STATUS_PROC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
sql_command_flags[SQLCOM_SHOW_STATUS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
sql_command_flags[SQLCOM_SHOW_DATABASES]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
sql_command_flags[SQLCOM_SHOW_TRIGGERS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
sql_command_flags[SQLCOM_SHOW_EVENTS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
sql_command_flags[SQLCOM_SHOW_OPEN_TABLES]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
sql_command_flags[SQLCOM_SHOW_PLUGINS]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_FIELDS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
sql_command_flags[SQLCOM_SHOW_KEYS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
sql_command_flags[SQLCOM_SHOW_VARIABLES]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
sql_command_flags[SQLCOM_SHOW_CHARSETS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
sql_command_flags[SQLCOM_SHOW_COLLATIONS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
sql_command_flags[SQLCOM_SHOW_BINLOGS]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_SLAVE_HOSTS]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_BINLOG_EVENTS]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_STORAGE_ENGINES]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_PRIVILEGES]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_WARNS]= CF_STATUS_COMMAND | CF_DIAGNOSTIC_STMT;
sql_command_flags[SQLCOM_SHOW_ERRORS]= CF_STATUS_COMMAND | CF_DIAGNOSTIC_STMT;
sql_command_flags[SQLCOM_SHOW_ENGINE_STATUS]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_ENGINE_MUTEX]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_ENGINE_LOGS]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_PROCESSLIST]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_SQL_FILTERS]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_GRANTS]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_CREATE_DB]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_CREATE]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_MASTER_STAT]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_SLAVE_STAT]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_CREATE_PROC]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_CREATE_FUNC]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_CREATE_TRIGGER]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_STATUS_FUNC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE;
sql_command_flags[SQLCOM_SHOW_PROC_CODE]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_FUNC_CODE]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_CREATE_EVENT]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_PROFILES]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_PROFILE]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_BINLOG_BASE64_EVENT]= CF_STATUS_COMMAND |
CF_CAN_GENERATE_ROW_EVENTS;
sql_command_flags[SQLCOM_SHOW_TABLES]= (CF_STATUS_COMMAND |
CF_SHOW_TABLE_COMMAND |
CF_REEXECUTION_FRAGILE);
sql_command_flags[SQLCOM_SHOW_TABLE_STATUS]= (CF_STATUS_COMMAND |
CF_SHOW_TABLE_COMMAND |
CF_REEXECUTION_FRAGILE);
sql_command_flags[SQLCOM_CREATE_USER]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_RENAME_USER]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_DROP_USER]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_ALTER_USER]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_GRANT]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_REVOKE]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_REVOKE_ALL]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_OPTIMIZE]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_INSTALL_PLUGIN]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]= CF_CHANGES_DATA;
/* Does not change the contents of the diagnostics area. */
sql_command_flags[SQLCOM_GET_DIAGNOSTICS]= CF_DIAGNOSTIC_STMT;
/*
(1): without it, in "CALL some_proc((subq))", subquery would not be
traced.
*/
sql_command_flags[SQLCOM_CALL]= CF_REEXECUTION_FRAGILE |
CF_CAN_GENERATE_ROW_EVENTS |
CF_OPTIMIZER_TRACE; // (1)
sql_command_flags[SQLCOM_EXECUTE]= CF_CAN_GENERATE_ROW_EVENTS;
/*
The following admin table operations are allowed
on log tables.
*/
sql_command_flags[SQLCOM_REPAIR]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_OPTIMIZE]|= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_ANALYZE]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_CHECK]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_CREATE_USER]|= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_DROP_USER]|= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_RENAME_USER]|= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_ALTER_USER]|= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_REVOKE]|= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_REVOKE_ALL]|= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_GRANT]|= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_PRELOAD_KEYS]= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_FLUSH]= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_RESET]= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_CREATE_SERVER]= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_ALTER_SERVER]= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_DROP_SERVER]= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_CHANGE_MASTER]= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_SLAVE_START]= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_SLAVE_STOP]= CF_AUTO_COMMIT_TRANS;
/*
The following statements can deal with temporary tables,
so temporary tables should be pre-opened for those statements to
simplify privilege checking.
There are other statements that deal with temporary tables and open
them, but which are not listed here. The thing is that the order of
pre-opening temporary tables for those statements is somewhat custom.
*/
sql_command_flags[SQLCOM_CREATE_TABLE]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_DROP_TABLE]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_CREATE_INDEX]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_ALTER_TABLE]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_TRUNCATE]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_LOAD]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_DROP_INDEX]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_UPDATE]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_UPDATE_MULTI]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_INSERT_SELECT]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_DELETE]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_DELETE_MULTI]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_REPLACE_SELECT]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_SELECT]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_SET_OPTION]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_DO]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_CALL]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_CHECKSUM]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_ANALYZE]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_CHECK]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_OPTIMIZE]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_REPAIR]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_PRELOAD_KEYS]|= CF_PREOPEN_TMP_TABLES;
sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]|= CF_PREOPEN_TMP_TABLES;
/*
DDL statements that should start with closing opened handlers.
We use this flag only for statements for which open HANDLERs
have to be closed before emporary tables are pre-opened.
*/
sql_command_flags[SQLCOM_CREATE_TABLE]|= CF_HA_CLOSE;
sql_command_flags[SQLCOM_DROP_TABLE]|= CF_HA_CLOSE;
sql_command_flags[SQLCOM_ALTER_TABLE]|= CF_HA_CLOSE;
sql_command_flags[SQLCOM_TRUNCATE]|= CF_HA_CLOSE;
sql_command_flags[SQLCOM_REPAIR]|= CF_HA_CLOSE;
sql_command_flags[SQLCOM_OPTIMIZE]|= CF_HA_CLOSE;
sql_command_flags[SQLCOM_ANALYZE]|= CF_HA_CLOSE;
sql_command_flags[SQLCOM_CHECK]|= CF_HA_CLOSE;
sql_command_flags[SQLCOM_CREATE_INDEX]|= CF_HA_CLOSE;
sql_command_flags[SQLCOM_DROP_INDEX]|= CF_HA_CLOSE;
sql_command_flags[SQLCOM_PRELOAD_KEYS]|= CF_HA_CLOSE;
sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]|= CF_HA_CLOSE;
/*
Mark statements that always are disallowed in read-only
transactions. Note that according to the SQL standard,
even temporary table DDL should be disallowed.
*/
sql_command_flags[SQLCOM_CREATE_TABLE]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_ALTER_TABLE]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_DROP_TABLE]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_RENAME_TABLE]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_CREATE_INDEX]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_DROP_INDEX]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_CREATE_DB]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_DROP_DB]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_ALTER_DB]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_CREATE_VIEW]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_DROP_VIEW]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_CREATE_TRIGGER]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_DROP_TRIGGER]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_CREATE_EVENT]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_ALTER_EVENT]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_DROP_EVENT]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_CREATE_USER]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_RENAME_USER]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_ALTER_USER]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_DROP_USER]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_CREATE_SERVER]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_ALTER_SERVER]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_DROP_SERVER]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_CREATE_FUNCTION]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_CREATE_PROCEDURE]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_CREATE_SPFUNCTION]|=CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_DROP_PROCEDURE]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_DROP_FUNCTION]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_ALTER_PROCEDURE]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_ALTER_FUNCTION]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_TRUNCATE]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_ALTER_TABLESPACE]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_REPAIR]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_OPTIMIZE]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_GRANT]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_REVOKE]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_REVOKE_ALL]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_INSTALL_PLUGIN]|= CF_DISALLOW_IN_RO_TRANS;
sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]|= CF_DISALLOW_IN_RO_TRANS;
}
bool sqlcom_can_generate_row_events(enum enum_sql_command command)
{
return (sql_command_flags[command] & CF_CAN_GENERATE_ROW_EVENTS);
}
bool is_update_query(enum enum_sql_command command)
{
DBUG_ASSERT(command >= 0 && command <= SQLCOM_END);
return (sql_command_flags[command] & CF_CHANGES_DATA) != 0;
}
bool is_explainable_query(enum enum_sql_command command)
{
DBUG_ASSERT(command >= 0 && command <= SQLCOM_END);
return (sql_command_flags[command] & CF_CAN_BE_EXPLAINED) != 0;
}
/**
Check if a sql command is allowed to write to log tables.
@param command The SQL command
@return true if writing is allowed
*/
bool is_log_table_write_query(enum enum_sql_command command)
{
DBUG_ASSERT(command >= 0 && command <= SQLCOM_END);
return (sql_command_flags[command] & CF_WRITE_LOGS_COMMAND) != 0;
}
void execute_init_command(THD *thd, LEX_STRING *init_command,
mysql_rwlock_t *var_lock)
{
Vio* save_vio;
ulong save_client_capabilities;
mysql_rwlock_rdlock(var_lock);
if (!init_command->length)
{
mysql_rwlock_unlock(var_lock);
return;
}
/*
copy the value under a lock, and release the lock.
init_command has to be executed without a lock held,
as it may try to change itself
*/
size_t len= init_command->length;
char *buf= thd->strmake(init_command->str, len);
mysql_rwlock_unlock(var_lock);
#if defined(ENABLED_PROFILING)
thd->profiling.start_new_query();
thd->profiling.set_query_source(buf, len);
#endif
THD_STAGE_INFO(thd, stage_execution_of_init_command);
save_client_capabilities= thd->client_capabilities;
thd->client_capabilities|= CLIENT_MULTI_QUERIES;
/*
We don't need return result of execution to client side.
To forbid this we should set thd->net.vio to 0.
*/
save_vio= thd->net.vio;
thd->net.vio= 0;
dispatch_command(COM_QUERY, thd, buf, len);
thd->client_capabilities= save_client_capabilities;
thd->net.vio= save_vio;
#if defined(ENABLED_PROFILING)
thd->profiling.finish_current_query();
#endif
}
static char *fgets_fn(char *buffer, size_t size, fgets_input_t input, int *error)
{
MYSQL_FILE *in= static_cast<MYSQL_FILE*> (input);
char *line= mysql_file_fgets(buffer, size, in);
if (error)
*error= (line == NULL) ? ferror(in->m_file) : 0;
return line;
}
static void handle_bootstrap_impl(THD *thd)
{
MYSQL_FILE *file= bootstrap_file;
char buffer[MAX_BOOTSTRAP_QUERY_SIZE];
char *query;
int length;
int rc;
int error= 0;
DBUG_ENTER("handle_bootstrap");
#ifndef EMBEDDED_LIBRARY
pthread_detach_this_thread();
thd->thread_stack= (char*) &thd;
#endif /* EMBEDDED_LIBRARY */
thd->security_ctx->user= (char*) my_strdup("boot", MYF(MY_WME));
thd->security_ctx->priv_user[0]= thd->security_ctx->priv_host[0]=0;
/*
Make the "client" handle multiple results. This is necessary
to enable stored procedures with SELECTs and Dynamic SQL
in init-file.
*/
thd->client_capabilities|= CLIENT_MULTI_RESULTS;
thd->init_for_queries();
buffer[0]= '\0';
for ( ; ; )
{
rc= read_bootstrap_query(buffer, &length, file, fgets_fn, &error);
if (rc == READ_BOOTSTRAP_EOF)
break;
/*
Check for bootstrap file errors. SQL syntax errors will be
caught below.
*/
if (rc != READ_BOOTSTRAP_SUCCESS)
{
/*
mysql_parse() may have set a successful error status for the previous
query. We must clear the error status to report the bootstrap error.
*/
thd->get_stmt_da()->reset_diagnostics_area();
/* Get the nearest query text for reference. */
char *err_ptr= buffer + (length <= MAX_BOOTSTRAP_ERROR_LEN ?
0 : (length - MAX_BOOTSTRAP_ERROR_LEN));
switch (rc)
{
case READ_BOOTSTRAP_ERROR:
my_printf_error(ER_UNKNOWN_ERROR, "Bootstrap file error, return code (%d). "
"Nearest query: '%s'", MYF(0), error, err_ptr);
break;
case READ_BOOTSTRAP_QUERY_SIZE:
my_printf_error(ER_UNKNOWN_ERROR, "Boostrap file error. Query size "
"exceeded %d bytes near '%s'.", MYF(0),
MAX_BOOTSTRAP_LINE_SIZE, err_ptr);
break;
default:
DBUG_ASSERT(false);
break;
}
thd->protocol->end_statement();
bootstrap_error= 1;
break;
}
query= (char *) thd->memdup_w_gap(buffer, length + 1,
thd->db_length + 1 +
QUERY_CACHE_FLAGS_SIZE);
size_t db_len= 0;
memcpy(query + length + 1, (char *) &db_len, sizeof(size_t));
thd->set_query_and_id(query, length, thd->charset(), next_query_id());
DBUG_PRINT("query",("%-.4096s",thd->query()));
#if defined(ENABLED_PROFILING)
thd->profiling.start_new_query();
thd->profiling.set_query_source(thd->query(), length);
#endif
/*
We don't need to obtain LOCK_thread_count here because in bootstrap
mode we have only one thread.
*/
thd->set_time();
Parser_state parser_state;
if (parser_state.init(thd, thd->query(), length))
{
thd->protocol->end_statement();
bootstrap_error= 1;
break;
}
mysql_parse(thd, thd->query(), length, &parser_state);
bootstrap_error= thd->is_error();
thd->protocol->end_statement();
#if defined(ENABLED_PROFILING)
thd->profiling.finish_current_query();
#endif
if (bootstrap_error)
break;
free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC));
free_root(&thd->transaction.mem_root,MYF(MY_KEEP_PREALLOC));
}
DBUG_VOID_RETURN;
}
/**
Execute commands from bootstrap_file.
Used when creating the initial grant tables.
*/
pthread_handler_t handle_bootstrap(void *arg)
{
THD *thd=(THD*) arg;
mysql_thread_set_psi_id(thd->thread_id);
do_handle_bootstrap(thd);
return 0;
}
void do_handle_bootstrap(THD *thd)
{
bool thd_added= false;
/* The following must be called before DBUG_ENTER */
thd->thread_stack= (char*) &thd;
if (my_thread_init() || thd->store_globals())
{
#ifndef EMBEDDED_LIBRARY
close_connection(thd, ER_OUT_OF_RESOURCES);
#endif
thd->fatal_error();
goto end;
}
mysql_mutex_lock(&LOCK_thread_count);
thd_added= true;
add_global_thread(thd);
mysql_mutex_unlock(&LOCK_thread_count);
handle_bootstrap_impl(thd);
end:
net_end(&thd->net);
thd->release_resources();
if (thd_added)
{
remove_global_thread(thd);
}
/*
For safety we delete the thd before signalling that bootstrap is done,
since the server will be taken down immediately.
*/
delete thd;
mysql_mutex_lock(&LOCK_thread_count);
in_bootstrap= FALSE;
mysql_cond_broadcast(&COND_thread_count);
mysql_mutex_unlock(&LOCK_thread_count);
#ifndef EMBEDDED_LIBRARY
my_thread_end();
pthread_exit(0);
#endif
return;
}
/* This works because items are allocated with sql_alloc() */
void free_items(Item *item)
{
Item *next;
DBUG_ENTER("free_items");
for (; item ; item=next)
{
next=item->next;
item->delete_self();
}
DBUG_VOID_RETURN;
}
/**
This works because items are allocated with sql_alloc().
@note The function also handles null pointers (empty list).
*/
void cleanup_items(Item *item)
{
DBUG_ENTER("cleanup_items");
for (; item ; item=item->next)
item->cleanup();
DBUG_VOID_RETURN;
}
#ifndef EMBEDDED_LIBRARY
/**
Read one command from connection and execute it (query or simple command).
This function is called in loop from thread function.
For profiling to work, it must never be called recursively.
@retval
0 success
@retval
1 request of thread shutdown (see dispatch_command() description)
*/
bool do_command(THD *thd)
{
bool return_value;
char *packet= 0;
ulong packet_length;
NET *net= &thd->net;
enum enum_server_command command;
DBUG_ENTER("do_command");
/*
indicator of uninitialized lex => normal flow of errors handling
(see my_message_sql)
*/
thd->lex->current_select= 0;
/*
This thread will do a blocking read from the client which
will be interrupted when the next command is received from
the client, the connection is closed or "net_wait_timeout"
number of seconds has passed.
*/
if (!thd->skip_wait_timeout)
{
THD_TRANS *trans= &thd->transaction.all;
Ha_trx_info *ha_info= trans->ha_list;
bool is_trx_read_only= ha_check_trx_read_only(ha_info);
if (thd->in_active_multi_stmt_transaction())
{
if (thd->variables.trx_idle_timeout > 0)
{
my_net_set_read_timeout(net, thd->variables.trx_idle_timeout);
} else if ((thd->variables.trx_readonly_idle_timeout > 0) && is_trx_read_only)
{
my_net_set_read_timeout(net, thd->variables.trx_readonly_idle_timeout);
} else if ((thd->variables.trx_changes_idle_timeout > 0) && !is_trx_read_only)
{
my_net_set_read_timeout(net, thd->variables.trx_changes_idle_timeout);
} else {
my_net_set_read_timeout(net, thd->variables.net_wait_timeout);
}
} else {
my_net_set_read_timeout(net, thd->variables.net_wait_timeout);
}
}
/*
XXX: this code is here only to clear possible errors of init_connect.
Consider moving to init_connect() instead.
*/
thd->clear_error(); // Clear error message
thd->get_stmt_da()->reset_diagnostics_area();
net_new_transaction(net);
/*
Synchronization point for testing of KILL_CONNECTION.
This sync point can wait here, to simulate slow code execution
between the last test of thd->killed and blocking in read().
The goal of this test is to verify that a connection does not
hang, if it is killed at this point of execution.
(Bug#37780 - main.kill fails randomly)
Note that the sync point wait itself will be terminated by a
kill. In this case it consumes a condition broadcast, but does
not change anything else. The consumed broadcast should not
matter here, because the read/recv() below doesn't use it.
*/
DEBUG_SYNC(thd, "before_do_command_net_read");
/*
Because of networking layer callbacks in place,
this call will maintain the following instrumentation:
- IDLE events
- SOCKET events
- STATEMENT events
- STAGE events
when reading a new network packet.
In particular, a new instrumented statement is started.
See init_net_server_extension()
*/
thd->m_server_idle= true;
packet_length= my_net_read(net);
thd->m_server_idle= false;
if (packet_length == packet_error)
{
DBUG_PRINT("info",("Got error %d reading command from socket %s",
net->error,
vio_description(net->vio)));
/* Instrument this broken statement as "statement/com/error" */
thd->m_statement_psi= MYSQL_REFINE_STATEMENT(thd->m_statement_psi,
com_statement_info[COM_END].m_key);
/* Check if we can continue without closing the connection */
/* The error must be set. */
DBUG_ASSERT(thd->is_error());
thd->protocol->end_statement();
/* Mark the statement completed. */
MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da());
thd->m_statement_psi= NULL;
thd->m_digest= NULL;
if (net->error != 3)
{
return_value= TRUE; // We have to close it.
goto out;
}
net->error= 0;
return_value= FALSE;
goto out;
}
packet= (char*) net->read_pos;
/*
'packet_length' contains length of data, as it was stored in packet
header. In case of malformed header, my_net_read returns zero.
If packet_length is not zero, my_net_read ensures that the returned
number of bytes was actually read from network.
There is also an extra safety measure in my_net_read:
it sets packet[packet_length]= 0, but only for non-zero packets.
*/
if (packet_length == 0) /* safety */
{
/* Initialize with COM_SLEEP packet */
packet[0]= (uchar) COM_SLEEP;
packet_length= 1;
}
/* Do not rely on my_net_read, extra safety against programming errors. */
packet[packet_length]= '\0'; /* safety */
command= (enum enum_server_command) (uchar) packet[0];
if (command >= COM_END)
command= COM_END; // Wrong command
DBUG_PRINT("info",("Command on %s = %d (%s)",
vio_description(net->vio), command,
command_name[command].str));
/* Restore read timeout value */
my_net_set_read_timeout(net, thd->variables.net_read_timeout);
DBUG_ASSERT(packet_length);
return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1));
out:
/* The statement instrumentation must be closed in all cases. */
DBUG_ASSERT(thd->m_digest == NULL);
DBUG_ASSERT(thd->m_statement_psi == NULL);
DBUG_RETURN(return_value);
}
#endif /* EMBEDDED_LIBRARY */
/**
@brief Determine if an attempt to update a non-temporary table while the
read-only option was enabled has been made.
This is a helper function to mysql_execute_command.
@note SQLCOM_UPDATE_MULTI is an exception and delt with elsewhere.
@see mysql_execute_command
@returns Status code
@retval TRUE The statement should be denied.
@retval FALSE The statement isn't updating any relevant tables.
*/
static my_bool deny_updates_if_read_only_option(THD *thd,
TABLE_LIST *all_tables)
{
DBUG_ENTER("deny_updates_if_read_only_option");
if (!opt_readonly)
DBUG_RETURN(FALSE);
LEX *lex= thd->lex;
const my_bool user_is_super=
((ulong)(thd->security_ctx->master_access & SUPER_ACL) ==
(ulong)SUPER_ACL);
if (user_is_super)
DBUG_RETURN(FALSE);
if (!(sql_command_flags[lex->sql_command] & CF_CHANGES_DATA))
DBUG_RETURN(FALSE);
/* Multi update is an exception and is dealt with later. */
if (lex->sql_command == SQLCOM_UPDATE_MULTI)
DBUG_RETURN(FALSE);
const my_bool create_temp_tables=
(lex->sql_command == SQLCOM_CREATE_TABLE) &&
(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE);
const my_bool drop_temp_tables=
(lex->sql_command == SQLCOM_DROP_TABLE) &&
lex->drop_temporary;
const my_bool update_real_tables=
some_non_temp_table_to_be_updated(thd, all_tables) &&
!(create_temp_tables || drop_temp_tables);
const my_bool create_or_drop_databases=
(lex->sql_command == SQLCOM_CREATE_DB) ||
(lex->sql_command == SQLCOM_DROP_DB);
if (update_real_tables || create_or_drop_databases)
{
/*
An attempt was made to modify one or more non-temporary tables.
*/
DBUG_RETURN(TRUE);
}
/* Assuming that only temporary tables are modified. */
DBUG_RETURN(FALSE);
}
/**
RDS:
Avoid too many statements run concurrently, high watermark part.
If *thread_running* touch thread_running_high_watermark, stmt will be
rejected.
@param thd connection handle
@retval
FALSE query continues.
@retval
TRUE query rejected.
*/
static my_bool thread_running_control_high(THD *thd)
{
ulong tr, tr_high;
DBUG_ENTER("thread_running_control_high");
tr= num_thread_running;
tr_high= thread_running_high_watermark;
/*
The follow case can get through:
case 1: thread_running <= thread_running_high_watermark.
case 2: this query is in a active transaction.
case 3: not a COM_QUERY or COM_STMT_EXECUTE command.
case 4: non-select query when thread_running_ctl_mode isn't "SELECTS".
thread_running_ctl_mode: 0 -> SELECTS, 1 -> ALL.
case 5 6 7 8: commit, rollback, super user, slave threads
*/
if ((!tr_high || tr <= tr_high) || // case 1
thd->transaction.is_active() || // case 2
(thd->get_command() != COM_QUERY &&
thd->get_command() != COM_STMT_EXECUTE) || // case 3
(thd->lex->sql_command != SQLCOM_SELECT &&
thread_running_ctl_mode == 0) || // case 4
thd->lex->sql_command == SQLCOM_COMMIT || // case 5
thd->lex->sql_command == SQLCOM_ROLLBACK || // case 6
thd->security_ctx->master_access & SUPER_ACL || // case 7
thd->slave_thread) // case 8
DBUG_RETURN(FALSE);
DBUG_EXECUTE_IF("thread_running_change_before_doublecheck", sleep(2););
/* If the running thread is higher than high watermark, reject it. */
if ((ulong)num_thread_running > tr_high)
{
__sync_add_and_fetch(&thread_rejected, 1);
DBUG_RETURN(TRUE);
}
DBUG_RETURN(FALSE);
}
/**
Perform one connection-level (COM_XXXX) command.
@param command type of command to perform
@param thd connection handle
@param packet data for the command, packet is always null-terminated
@param packet_length length of packet + 1 (to show that data is
null-terminated) except for COM_SLEEP, where it
can be zero.
@todo
set thd->lex->sql_command to SQLCOM_END here.
@todo
The following has to be changed to an 8 byte integer
@retval
0 ok
@retval
1 request of thread shutdown, i. e. if command is
COM_QUIT/COM_SHUTDOWN
*/
bool dispatch_command(enum enum_server_command command, THD *thd,
char* packet, uint packet_length)
{
NET *net= &thd->net;
bool error= 0;
DBUG_ENTER("dispatch_command");
DBUG_PRINT("info",("packet: '%*.s'; command: %d", packet_length, packet, command));
/* SHOW PROFILE instrumentation, begin */
#if defined(ENABLED_PROFILING)
thd->profiling.start_new_query();
#endif
/* DTRACE instrumentation, begin */
MYSQL_COMMAND_START(thd->thread_id, command,
&thd->security_ctx->priv_user[0],
(char *) thd->security_ctx->host_or_ip);
/* Performance Schema Interface instrumentation, begin */
thd->m_statement_psi= MYSQL_REFINE_STATEMENT(thd->m_statement_psi,
com_statement_info[command].m_key);
thd->set_command(command);
/*
Commands which always take a long time are logged into
the slow log only if opt_log_slow_admin_statements is set.
*/
thd->enable_slow_log= TRUE;
thd->lex->sql_command= SQLCOM_END; /* to avoid confusing VIEW detectors */
thd->set_time();
thd->set_cpu_time();
if (!thd->is_valid_time())
{
/*
If the time has got past 2038 we need to shut this server down
We do this by making sure every command is a shutdown and we
have enough privileges to shut the server down
TODO: remove this when we have full 64 bit my_time_t support
*/
thd->security_ctx->master_access|= SHUTDOWN_ACL;
command= COM_SHUTDOWN;
}
thd->set_query_id(next_query_id());
inc_thread_running();
if (!(server_command_flags[command] & CF_SKIP_QUESTIONS))
statistic_increment(thd->status_var.questions, &LOCK_status);
/**
Clear the set of flags that are expected to be cleared at the
beginning of each command.
*/
thd->server_status&= ~SERVER_STATUS_CLEAR_SET;
/**
Enforce password expiration for all RPC commands, except the
following:
COM_QUERY does a more fine-grained check later.
COM_STMT_CLOSE and COM_STMT_SEND_LONG_DATA don't return anything.
COM_PING only discloses information that the server is running,
and that's available through other means.
COM_QUIT should work even for expired statements.
*/
if (unlikely(thd->security_ctx->password_expired &&
command != COM_QUERY &&
command != COM_STMT_CLOSE &&
command != COM_STMT_SEND_LONG_DATA &&
command != COM_PING &&
command != COM_QUIT))
{
my_error(ER_MUST_CHANGE_PASSWORD, MYF(0));
goto done;
}
switch (command) {
case COM_INIT_DB:
{
LEX_STRING tmp;
status_var_increment(thd->status_var.com_stat[SQLCOM_CHANGE_DB]);
thd->convert_string(&tmp, system_charset_info,
packet, packet_length, thd->charset());
if (!mysql_change_db(thd, &tmp, FALSE))
{
general_log_write(thd, command, thd->db, thd->db_length);
my_ok(thd);
}
break;
}
#ifdef HAVE_REPLICATION
case COM_REGISTER_SLAVE:
{
if (!register_slave(thd, (uchar*)packet, packet_length))
my_ok(thd);
break;
}
#endif
case COM_CHANGE_USER:
{
int auth_rc;
status_var_increment(thd->status_var.com_other);
thd->change_user();
thd->clear_error(); // if errors from rollback
/* acl_authenticate() takes the data from net->read_pos */
net->read_pos= (uchar*)packet;
USER_CONN *save_user_connect=
const_cast<USER_CONN*>(thd->get_user_connect());
char *save_db= thd->db;
uint save_db_length= thd->db_length;
Security_context save_security_ctx= *thd->security_ctx;
auth_rc= acl_authenticate(thd, packet_length);
MYSQL_AUDIT_NOTIFY_CONNECTION_CHANGE_USER(thd);
if (auth_rc)
{
my_free(thd->security_ctx->user);
*thd->security_ctx= save_security_ctx;
thd->set_user_connect(save_user_connect);
thd->reset_db(save_db, save_db_length);
my_error(ER_ACCESS_DENIED_CHANGE_USER_ERROR, MYF(0),
thd->security_ctx->user,
thd->security_ctx->host_or_ip,
(thd->password ? ER(ER_YES) : ER(ER_NO)));
thd->killed= THD::KILL_CONNECTION;
error=true;
}
else
{
#ifndef NO_EMBEDDED_ACCESS_CHECKS
/* we've authenticated new user */
if (save_user_connect)
decrease_user_connections(save_user_connect);
#endif /* NO_EMBEDDED_ACCESS_CHECKS */
mysql_mutex_lock(&thd->LOCK_thd_data);
my_free(save_db);
mysql_mutex_unlock(&thd->LOCK_thd_data);
my_free(save_security_ctx.user);
}
break;
}
case COM_STMT_EXECUTE:
{
mysqld_stmt_execute(thd, packet, packet_length);
break;
}
case COM_STMT_FETCH:
{
mysqld_stmt_fetch(thd, packet, packet_length);
break;
}
case COM_STMT_SEND_LONG_DATA:
{
mysql_stmt_get_longdata(thd, packet, packet_length);
break;
}
case COM_STMT_PREPARE:
{
mysqld_stmt_prepare(thd, packet, packet_length);
break;
}
case COM_STMT_CLOSE:
{
mysqld_stmt_close(thd, packet, packet_length);
break;
}
case COM_STMT_RESET:
{
mysqld_stmt_reset(thd, packet, packet_length);
break;
}
case COM_QUERY:
{
DBUG_ASSERT(thd->m_digest == NULL);
thd->m_digest= & thd->m_digest_state;
thd->m_digest->reset(thd->m_token_array, max_digest_length);
if (alloc_query(thd, packet, packet_length))
break; // fatal error is set
MYSQL_QUERY_START(thd->query(), thd->thread_id,
(char *) (thd->db ? thd->db : ""),
&thd->security_ctx->priv_user[0],
(char *) thd->security_ctx->host_or_ip);
char *packet_end= thd->query() + thd->query_length();
if (opt_log_raw)
general_log_write(thd, command, thd->query(), thd->query_length());
DBUG_PRINT("query",("%-.4096s",thd->query()));
#if defined(ENABLED_PROFILING)
thd->profiling.set_query_source(thd->query(), thd->query_length());
#endif
MYSQL_SET_STATEMENT_TEXT(thd->m_statement_psi, thd->query(), thd->query_length());
Parser_state parser_state;
if (parser_state.init(thd, thd->query(), thd->query_length()))
break;
mysql_parse(thd, thd->query(), thd->query_length(), &parser_state);
while (!thd->killed && (parser_state.m_lip.found_semicolon != NULL) &&
! thd->is_error())
{
/*
Multiple queries exits, execute them individually
*/
char *beginning_of_next_stmt= (char*) parser_state.m_lip.found_semicolon;
/* Finalize server status flags after executing a statement. */
thd->update_server_status();
thd->protocol->end_statement();
query_cache_end_of_result(thd);
mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_STATUS,
thd->get_stmt_da()->is_error() ?
thd->get_stmt_da()->sql_errno() : 0,
command_name[command].str);
ulong length= (ulong)(packet_end - beginning_of_next_stmt);
log_slow_statement(thd);
/* Remove garbage at start of query */
while (length > 0 && my_isspace(thd->charset(), *beginning_of_next_stmt))
{
beginning_of_next_stmt++;
length--;
}
/* PSI end */
MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da());
thd->m_statement_psi= NULL;
thd->m_digest= NULL;
/* DTRACE end */
if (MYSQL_QUERY_DONE_ENABLED())
{
MYSQL_QUERY_DONE(thd->is_error());
}
/* SHOW PROFILE end */
#if defined(ENABLED_PROFILING)
thd->profiling.finish_current_query();
#endif
/* SHOW PROFILE begin */
#if defined(ENABLED_PROFILING)
thd->profiling.start_new_query("continuing");
thd->profiling.set_query_source(beginning_of_next_stmt, length);
#endif
/* DTRACE begin */
MYSQL_QUERY_START(beginning_of_next_stmt, thd->thread_id,
(char *) (thd->db ? thd->db : ""),
&thd->security_ctx->priv_user[0],
(char *) thd->security_ctx->host_or_ip);
/* PSI begin */
thd->m_digest= & thd->m_digest_state;
thd->m_statement_psi= MYSQL_START_STATEMENT(&thd->m_statement_state,
com_statement_info[command].m_key,
thd->db, thd->db_length,
thd->charset());
THD_STAGE_INFO(thd, stage_init);
MYSQL_SET_STATEMENT_TEXT(thd->m_statement_psi, beginning_of_next_stmt, length);
thd->set_query_and_id(beginning_of_next_stmt, length,
thd->charset(), next_query_id());
/*
Count each statement from the client.
*/
statistic_increment(thd->status_var.questions, &LOCK_status);
thd->set_time(); /* Reset the query start time. */
parser_state.reset(beginning_of_next_stmt, length);
/* TODO: set thd->lex->sql_command to SQLCOM_END here */
mysql_parse(thd, beginning_of_next_stmt, length, &parser_state);
}
DBUG_PRINT("info",("query ready"));
break;
}
case COM_FIELD_LIST: // This isn't actually needed
#ifdef DONT_ALLOW_SHOW_COMMANDS
my_message(ER_NOT_ALLOWED_COMMAND, ER(ER_NOT_ALLOWED_COMMAND),
MYF(0)); /* purecov: inspected */
break;
#else
{
char *fields, *packet_end= packet + packet_length, *arg_end;
/* Locked closure of all tables */
TABLE_LIST table_list;
LEX_STRING table_name;
LEX_STRING db;
/*
SHOW statements should not add the used tables to the list of tables
used in a transaction.
*/
MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint();
status_var_increment(thd->status_var.com_stat[SQLCOM_SHOW_FIELDS]);
if (thd->copy_db_to(&db.str, &db.length))
break;
/*
We have name + wildcard in packet, separated by endzero
*/
arg_end= strend(packet);
uint arg_length= arg_end - packet;
/* Check given table name length. */
if (arg_length >= packet_length || arg_length > NAME_LEN)
{
my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0));
break;
}
thd->convert_string(&table_name, system_charset_info,
packet, arg_length, thd->charset());
enum_ident_name_check ident_check_status=
check_table_name(table_name.str, table_name.length, FALSE);
if (ident_check_status == IDENT_NAME_WRONG)
{
/* this is OK due to convert_string() null-terminating the string */
my_error(ER_WRONG_TABLE_NAME, MYF(0), table_name.str);
break;
}
else if (ident_check_status == IDENT_NAME_TOO_LONG)
{
my_error(ER_TOO_LONG_IDENT, MYF(0), table_name.str);
break;
}
packet= arg_end + 1;
mysql_reset_thd_for_next_command(thd);
lex_start(thd);
/* Must be before we init the table list. */
if (lower_case_table_names)
table_name.length= my_casedn_str(files_charset_info, table_name.str);
table_list.init_one_table(db.str, db.length, table_name.str,
table_name.length, table_name.str, TL_READ);
/*
Init TABLE_LIST members necessary when the undelrying
table is view.
*/
table_list.select_lex= &(thd->lex->select_lex);
thd->lex->
select_lex.table_list.link_in_list(&table_list,
&table_list.next_local);
thd->lex->add_to_query_tables(&table_list);
if (is_infoschema_db(table_list.db, table_list.db_length))
{
ST_SCHEMA_TABLE *schema_table= find_schema_table(thd, table_list.alias);
if (schema_table)
table_list.schema_table= schema_table;
}
uint query_length= (uint) (packet_end - packet); // Don't count end \0
if (!(fields= (char *) thd->memdup(packet, query_length + 1)))
break;
thd->set_query(fields, query_length);
general_log_print(thd, command, "%s %s", table_list.table_name, fields);
if (open_temporary_tables(thd, &table_list))
break;
if (check_table_access(thd, SELECT_ACL, &table_list,
TRUE, UINT_MAX, FALSE))
break;
/*
Turn on an optimization relevant if the underlying table
is a view: do not fill derived tables.
*/
thd->lex->sql_command= SQLCOM_SHOW_FIELDS;
// See comment in opt_trace_disable_if_no_security_context_access()
Opt_trace_start ots(thd, &table_list, thd->lex->sql_command, NULL,
NULL, 0, NULL, NULL);
mysqld_list_fields(thd,&table_list,fields);
thd->lex->unit.cleanup();
/* No need to rollback statement transaction, it's not started. */
DBUG_ASSERT(thd->transaction.stmt.is_empty());
close_thread_tables(thd);
thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
if (thd->transaction_rollback_request)
{
/*
Transaction rollback was requested since MDL deadlock was
discovered while trying to open tables. Rollback transaction
in all storage engines including binary log and release all
locks.
*/
trans_rollback_implicit(thd);
thd->mdl_context.release_transactional_locks();
}
thd->cleanup_after_query();
break;
}
#endif
case COM_QUIT:
/* We don't calculate statistics for this command */
general_log_print(thd, command, NullS);
net->error=0; // Don't give 'abort' message
thd->get_stmt_da()->disable_status(); // Don't send anything back
error=TRUE; // End server
break;
#ifndef EMBEDDED_LIBRARY
case COM_BINLOG_DUMP_GTID:
tp_dec_active_thread(thd, COM_BINLOG_DUMP_GTID);
error= com_binlog_dump_gtid(thd, packet, packet_length);
tp_inc_active_thread(thd, COM_BINLOG_DUMP_GTID);
break;
case COM_BINLOG_DUMP:
tp_dec_active_thread(thd, COM_BINLOG_DUMP);
error= com_binlog_dump(thd, packet, packet_length);
tp_inc_active_thread(thd, COM_BINLOG_DUMP);
break;
#endif
case COM_REFRESH:
{
int not_used;
if (packet_length < 1)
{
my_error(ER_MALFORMED_PACKET, MYF(0));
break;
}
/*
Initialize thd->lex since it's used in many base functions, such as
open_tables(). Otherwise, it remains unitialized and may cause crash
during execution of COM_REFRESH.
*/
lex_start(thd);
status_var_increment(thd->status_var.com_stat[SQLCOM_FLUSH]);
ulong options= (ulong) (uchar) packet[0];
if (trans_commit_implicit(thd))
break;
thd->mdl_context.release_transactional_locks();
if (check_global_access(thd,RELOAD_ACL))
break;
general_log_print(thd, command, NullS);
#ifndef DBUG_OFF
bool debug_simulate= FALSE;
DBUG_EXECUTE_IF("simulate_detached_thread_refresh", debug_simulate= TRUE;);
if (debug_simulate)
{
/*
Simulate a reload without a attached thread session.
Provides a environment similar to that of when the
server receives a SIGHUP signal and reloads caches
and flushes tables.
*/
bool res;
my_pthread_setspecific_ptr(THR_THD, NULL);
res= reload_acl_and_cache(NULL, options | REFRESH_FAST,
NULL, &not_used);
my_pthread_setspecific_ptr(THR_THD, thd);
if (res)
break;
}
else
#endif
if (reload_acl_and_cache(thd, options, (TABLE_LIST*) 0, &not_used))
break;
if (trans_commit_implicit(thd))
break;
close_thread_tables(thd);
thd->mdl_context.release_transactional_locks();
my_ok(thd);
break;
}
#ifndef EMBEDDED_LIBRARY
case COM_SHUTDOWN:
{
status_var_increment(thd->status_var.com_other);
if (check_global_access(thd,SHUTDOWN_ACL))
break; /* purecov: inspected */
/*
If the client is < 4.1.3, it is going to send us no argument; then
packet_length is 0, packet[0] is the end 0 of the packet. Note that
SHUTDOWN_DEFAULT is 0. If client is >= 4.1.3, the shutdown level is in
packet[0].
*/
enum mysql_enum_shutdown_level level;
if (packet_length == 0 || !thd->is_valid_time())
level= SHUTDOWN_DEFAULT;
else
level= (enum mysql_enum_shutdown_level) (uchar) packet[0];
if (level == SHUTDOWN_DEFAULT)
level= SHUTDOWN_WAIT_ALL_BUFFERS; // soon default will be configurable
else if (level != SHUTDOWN_WAIT_ALL_BUFFERS)
{
my_error(ER_NOT_SUPPORTED_YET, MYF(0), "this shutdown level");
break;
}
DBUG_PRINT("quit",("Got shutdown command for level %u", level));
general_log_print(thd, command, NullS);
my_eof(thd);
kill_mysql();
error=TRUE;
break;
}
#endif
case COM_STATISTICS:
{
STATUS_VAR current_global_status_var;
ulong uptime;
uint length MY_ATTRIBUTE((unused));
ulonglong queries_per_second1000;
char buff[250];
uint buff_len= sizeof(buff);
general_log_print(thd, command, NullS);
status_var_increment(thd->status_var.com_stat[SQLCOM_SHOW_STATUS]);
calc_sum_of_all_status(&current_global_status_var);
if (!(uptime= (ulong) (thd->start_time.tv_sec - server_start_time)))
queries_per_second1000= 0;
else
queries_per_second1000= thd->query_id * LL(1000) / uptime;
length= my_snprintf(buff, buff_len - 1,
"Uptime: %lu Threads: %d Questions: %lu "
"Slow queries: %llu Opens: %llu Flush tables: %lu "
"Open tables: %u Queries per second avg: %u.%03u",
uptime,
(int) get_thread_count(), (ulong) thd->query_id,
current_global_status_var.long_query_count,
current_global_status_var.opened_tables,
refresh_version,
table_cache_manager.cached_tables(),
(uint) (queries_per_second1000 / 1000),
(uint) (queries_per_second1000 % 1000));
#ifdef EMBEDDED_LIBRARY
/* Store the buffer in permanent memory */
my_ok(thd, 0, 0, buff);
#else
(void) my_net_write(net, (uchar*) buff, length);
(void) net_flush(net);
thd->get_stmt_da()->disable_status();
#endif
break;
}
case COM_PING:
status_var_increment(thd->status_var.com_other);
my_ok(thd); // Tell client we are alive
break;
case COM_PROCESS_INFO:
status_var_increment(thd->status_var.com_stat[SQLCOM_SHOW_PROCESSLIST]);
if (!thd->security_ctx->priv_user[0] &&
check_global_access(thd, PROCESS_ACL))
break;
general_log_print(thd, command, NullS);
mysqld_list_processes(thd,
thd->security_ctx->master_access & PROCESS_ACL ?
NullS : thd->security_ctx->priv_user, 0);
break;
case COM_PROCESS_KILL:
{
if (thread_id & (~0xfffffffful))
my_error(ER_DATA_OUT_OF_RANGE, MYF(0), "thread_id", "mysql_kill()");
else if (packet_length < 4)
my_error(ER_MALFORMED_PACKET, MYF(0));
else
{
status_var_increment(thd->status_var.com_stat[SQLCOM_KILL]);
ulong id=(ulong) uint4korr(packet);
sql_kill(thd,id,false);
}
break;
}
case COM_SET_OPTION:
{
if (packet_length < 2)
{
my_error(ER_MALFORMED_PACKET, MYF(0));
break;
}
status_var_increment(thd->status_var.com_stat[SQLCOM_SET_OPTION]);
uint opt_command= uint2korr(packet);
switch (opt_command) {
case (int) MYSQL_OPTION_MULTI_STATEMENTS_ON:
thd->client_capabilities|= CLIENT_MULTI_STATEMENTS;
my_eof(thd);
break;
case (int) MYSQL_OPTION_MULTI_STATEMENTS_OFF:
thd->client_capabilities&= ~CLIENT_MULTI_STATEMENTS;
my_eof(thd);
break;
default:
my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0));
break;
}
break;
}
case COM_DEBUG:
status_var_increment(thd->status_var.com_other);
if (check_global_access(thd, SUPER_ACL))
break; /* purecov: inspected */
mysql_print_status();
general_log_print(thd, command, NullS);
my_eof(thd);
break;
case COM_SLEEP:
case COM_CONNECT: // Impossible here
case COM_TIME: // Impossible from client
case COM_DELAYED_INSERT:
case COM_END:
default:
my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0));
break;
}
done:
DBUG_ASSERT(thd->derived_tables == NULL &&
(thd->open_tables == NULL ||
(thd->locked_tables_mode == LTM_LOCK_TABLES)));
/* Finalize server status flags after executing a command. */
thd->update_server_status();
if (thd->killed)
thd->send_kill_message();
thd->protocol->end_statement();
query_cache_end_of_result(thd);
if (!thd->is_error() && !thd->killed_errno())
mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_RESULT, 0, 0);
mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_STATUS,
thd->get_stmt_da()->is_error() ?
thd->get_stmt_da()->sql_errno() : 0,
command_name[command].str);
log_slow_statement(thd);
THD_STAGE_INFO(thd, stage_cleaning_up);
thd->reset_query();
thd->set_command(COM_SLEEP);
/* Performance Schema Interface instrumentation, end */
MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da());
thd->m_statement_psi= NULL;
thd->m_digest= NULL;
dec_thread_running();
thd->packet.shrink(thd->variables.net_buffer_length); // Reclaim some memory
free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC));
thd->inc_cpu_time();
/* DTRACE instrumentation, end */
if (MYSQL_QUERY_DONE_ENABLED() || MYSQL_COMMAND_DONE_ENABLED())
{
int res MY_ATTRIBUTE((unused));
res= (int) thd->is_error();
if (command == COM_QUERY)
{
MYSQL_QUERY_DONE(res);
}
MYSQL_COMMAND_DONE(res);
}
/* SHOW PROFILE instrumentation, end */
#if defined(ENABLED_PROFILING)
thd->profiling.finish_current_query();
#endif
DBUG_RETURN(error);
}
/**
Check whether we need to write the current statement (or its rewritten
version if it exists) to the slow query log.
As a side-effect, a digest of suppressed statements may be written.
@param thd thread handle
@retval
true statement needs to be logged
@retval
false statement does not need to be logged
*/
bool log_slow_applicable(THD *thd)
{
DBUG_ENTER("log_slow_applicable");
/*
The following should never be true with our current code base,
but better to keep this here so we don't accidently try to log a
statement in a trigger or stored function
*/
if (unlikely(thd->in_sub_stmt))
DBUG_RETURN(false); // Don't set time for sub stmt
/*
Do not log administrative statements unless the appropriate option is
set.
*/
if (thd->enable_slow_log)
{
bool warn_no_index= ((thd->server_status &
(SERVER_QUERY_NO_INDEX_USED |
SERVER_QUERY_NO_GOOD_INDEX_USED)) &&
opt_log_queries_not_using_indexes &&
!(sql_command_flags[thd->lex->sql_command] &
CF_STATUS_COMMAND));
bool log_this_query= ((thd->server_status & SERVER_QUERY_WAS_SLOW) ||
warn_no_index) &&
(thd->get_examined_row_count() >=
thd->variables.min_examined_row_limit);
bool suppress_logging= log_throttle_qni.log(thd, warn_no_index);
if (!suppress_logging && log_this_query)
DBUG_RETURN(true);
}
DBUG_RETURN(false);
}
/**
Unconditionally the current statement (or its rewritten version if it
exists) to the slow query log.
@param thd thread handle
*/
void log_slow_do(THD *thd)
{
DBUG_ENTER("log_slow_do");
THD_STAGE_INFO(thd, stage_logging_slow_query);
thd->status_var.long_query_count++;
if (thd->rewritten_query.length())
slow_log_print(thd,
thd->rewritten_query.c_ptr_safe(),
thd->rewritten_query.length());
else
slow_log_print(thd, thd->query(), thd->query_length());
DBUG_VOID_RETURN;
}
/**
Check whether we need to write the current statement to the slow query
log. If so, do so. This is a wrapper for the two functions above;
most callers should use this wrapper. Only use the above functions
directly if you have expensive rewriting that you only need to do if
the query actually needs to be logged (e.g. SP variables / NAME_CONST
substitution when executing a PROCEDURE).
A digest of suppressed statements may be logged instead of the current
statement.
@param thd thread handle
*/
void log_slow_statement(THD *thd)
{
DBUG_ENTER("log_slow_statement");
if (log_slow_applicable(thd))
log_slow_do(thd);
DBUG_VOID_RETURN;
}
/**
Create a TABLE_LIST object for an INFORMATION_SCHEMA table.
This function is used in the parser to convert a SHOW or DESCRIBE
table_name command to a SELECT from INFORMATION_SCHEMA.
It prepares a SELECT_LEX and a TABLE_LIST object to represent the
given command as a SELECT parse tree.
@param thd thread handle
@param lex current lex
@param table_ident table alias if it's used
@param schema_table_idx the type of the INFORMATION_SCHEMA table to be
created
@note
Due to the way this function works with memory and LEX it cannot
be used outside the parser (parse tree transformations outside
the parser break PS and SP).
@retval
0 success
@retval
1 out of memory or SHOW commands are not allowed
in this version of the server.
*/
int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident,
enum enum_schema_tables schema_table_idx)
{
SELECT_LEX *schema_select_lex= NULL;
DBUG_ENTER("prepare_schema_table");
switch (schema_table_idx) {
case SCH_SCHEMATA:
#if defined(DONT_ALLOW_SHOW_COMMANDS)
my_message(ER_NOT_ALLOWED_COMMAND,
ER(ER_NOT_ALLOWED_COMMAND), MYF(0)); /* purecov: inspected */
DBUG_RETURN(1);
#else
break;
#endif
case SCH_TABLE_NAMES:
case SCH_TABLES:
case SCH_VIEWS:
case SCH_TRIGGERS:
case SCH_EVENTS:
#ifdef DONT_ALLOW_SHOW_COMMANDS
my_message(ER_NOT_ALLOWED_COMMAND,
ER(ER_NOT_ALLOWED_COMMAND), MYF(0)); /* purecov: inspected */
DBUG_RETURN(1);
#else
{
LEX_STRING db;
size_t dummy;
if (lex->select_lex.db == NULL &&
lex->copy_db_to(&lex->select_lex.db, &dummy))
{
DBUG_RETURN(1);
}
schema_select_lex= new SELECT_LEX();
db.str= schema_select_lex->db= lex->select_lex.db;
schema_select_lex->table_list.first= NULL;
db.length= strlen(db.str);
if (check_and_convert_db_name(&db, FALSE) != IDENT_NAME_OK)
DBUG_RETURN(1);
break;
}
#endif
case SCH_COLUMNS:
case SCH_STATISTICS:
{
#ifdef DONT_ALLOW_SHOW_COMMANDS
my_message(ER_NOT_ALLOWED_COMMAND,
ER(ER_NOT_ALLOWED_COMMAND), MYF(0)); /* purecov: inspected */
DBUG_RETURN(1);
#else
DBUG_ASSERT(table_ident);
TABLE_LIST **query_tables_last= lex->query_tables_last;
schema_select_lex= new SELECT_LEX();
/* 'parent_lex' is used in init_query() so it must be before it. */
schema_select_lex->parent_lex= lex;
schema_select_lex->init_query();
if (!schema_select_lex->add_table_to_list(thd, table_ident, 0, 0, TL_READ,
MDL_SHARED_READ))
DBUG_RETURN(1);
lex->query_tables_last= query_tables_last;
break;
}
#endif
case SCH_PROFILES:
/*
Mark this current profiling record to be discarded. We don't
wish to have SHOW commands show up in profiling.
*/
#if defined(ENABLED_PROFILING)
thd->profiling.discard_current_query();
#endif
break;
case SCH_OPTIMIZER_TRACE:
case SCH_OPEN_TABLES:
case SCH_VARIABLES:
case SCH_STATUS:
case SCH_PROCEDURES:
case SCH_CHARSETS:
case SCH_ENGINES:
case SCH_COLLATIONS:
case SCH_COLLATION_CHARACTER_SET_APPLICABILITY:
case SCH_USER_PRIVILEGES:
case SCH_SCHEMA_PRIVILEGES:
case SCH_TABLE_PRIVILEGES:
case SCH_COLUMN_PRIVILEGES:
case SCH_TABLE_CONSTRAINTS:
case SCH_KEY_COLUMN_USAGE:
case SCH_SQL_FILTER_INFO:
default:
break;
}
SELECT_LEX *select_lex= lex->current_select;
if (make_schema_select(thd, select_lex, schema_table_idx))
{
DBUG_RETURN(1);
}
TABLE_LIST *table_list= select_lex->table_list.first;
table_list->schema_select_lex= schema_select_lex;
table_list->schema_table_reformed= 1;
DBUG_RETURN(0);
}
/**
Read query from packet and store in thd->query.
Used in COM_QUERY and COM_STMT_PREPARE.
Sets the following THD variables:
- query
- query_length
@retval
FALSE ok
@retval
TRUE error; In this case thd->fatal_error is set
*/
bool alloc_query(THD *thd, const char *packet, uint packet_length)
{
char *query;
/* Remove garbage at start and end of query */
while (packet_length > 0 && my_isspace(thd->charset(), packet[0]))
{
packet++;
packet_length--;
}
const char *pos= packet + packet_length; // Point at end null
while (packet_length > 0 &&
(pos[-1] == ';' || my_isspace(thd->charset() ,pos[-1])))
{
pos--;
packet_length--;
}
/* We must allocate some extra memory for query cache
The query buffer layout is:
buffer :==
<statement> The input statement(s)
'\0' Terminating null char (1 byte)
<length> Length of following current database name (size_t)
<db_name> Name of current database
<flags> Flags struct
*/
if (! (query= (char*) thd->memdup_w_gap(packet,
packet_length,
1 + sizeof(size_t) + thd->db_length +
QUERY_CACHE_FLAGS_SIZE)))
return TRUE;
query[packet_length]= '\0';
/*
Space to hold the name of the current database is allocated. We
also store this length, in case current database is changed during
execution. We might need to reallocate the 'query' buffer
*/
char *len_pos = (query + packet_length + 1);
memcpy(len_pos, (char *) &thd->db_length, sizeof(size_t));
thd->set_query(query, packet_length);
thd->rewritten_query.free(); // free here lest PS break
/* Reclaim some memory */
thd->packet.shrink(thd->variables.net_buffer_length);
thd->convert_buffer.shrink(thd->variables.net_buffer_length);
return FALSE;
}
static void reset_one_shot_variables(THD *thd)
{
thd->variables.character_set_client=
global_system_variables.character_set_client;
thd->variables.collation_connection=
global_system_variables.collation_connection;
thd->variables.collation_database=
global_system_variables.collation_database;
thd->variables.collation_server=
global_system_variables.collation_server;
thd->update_charset();
thd->variables.time_zone=
global_system_variables.time_zone;
thd->variables.lc_time_names= &my_locale_en_US;
thd->one_shot_set= 0;
}
static
bool sp_process_definer(THD *thd)
{
DBUG_ENTER("sp_process_definer");
LEX *lex= thd->lex;
/*
If the definer is not specified, this means that CREATE-statement missed
DEFINER-clause. DEFINER-clause can be missed in two cases:
- The user submitted a statement w/o the clause. This is a normal
case, we should assign CURRENT_USER as definer.
- Our slave received an updated from the master, that does not
replicate definer for stored rountines. We should also assign
CURRENT_USER as definer here, but also we should mark this routine
as NON-SUID. This is essential for the sake of backward
compatibility.
The problem is the slave thread is running under "special" user (@),
that actually does not exist. In the older versions we do not fail
execution of a stored routine if its definer does not exist and
continue the execution under the authorization of the invoker
(BUG#13198). And now if we try to switch to slave-current-user (@),
we will fail.
Actually, this leads to the inconsistent state of master and
slave (different definers, different SUID behaviour), but it seems,
this is the best we can do.
*/
if (!lex->definer)
{
Prepared_stmt_arena_holder ps_arena_holder(thd);
lex->definer= create_default_definer(thd);
/* Error has been already reported. */
if (lex->definer == NULL)
DBUG_RETURN(TRUE);
if (thd->slave_thread && lex->sphead)
lex->sphead->m_chistics->suid= SP_IS_NOT_SUID;
}
else
{
/*
If the specified definer differs from the current user, we
should check that the current user has SUPER privilege (in order
to create a stored routine under another user one must have
SUPER privilege).
*/
if ((strcmp(lex->definer->user.str, thd->security_ctx->priv_user) ||
my_strcasecmp(system_charset_info, lex->definer->host.str,
thd->security_ctx->priv_host)) &&
check_global_access(thd, SUPER_ACL))
{
my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "SUPER");
DBUG_RETURN(TRUE);
}
}
/* Check that the specified definer exists. Emit a warning if not. */
#ifndef NO_EMBEDDED_ACCESS_CHECKS
if (!is_acl_user(lex->definer->host.str, lex->definer->user.str))
{
push_warning_printf(thd,
Sql_condition::WARN_LEVEL_NOTE,
ER_NO_SUCH_USER,
ER(ER_NO_SUCH_USER),
lex->definer->user.str,
lex->definer->host.str);
}
#endif /* NO_EMBEDDED_ACCESS_CHECKS */
DBUG_RETURN(FALSE);
}
/**
Auxiliary call that opens and locks tables for LOCK TABLES statement
and initializes the list of locked tables.
@param thd Thread context.
@param tables List of tables to be locked.
@return FALSE in case of success, TRUE in case of error.
*/
static bool lock_tables_open_and_lock_tables(THD *thd, TABLE_LIST *tables)
{
Lock_tables_prelocking_strategy lock_tables_prelocking_strategy;
uint counter;
TABLE_LIST *table;
thd->in_lock_tables= 1;
if (open_tables(thd, &tables, &counter, 0, &lock_tables_prelocking_strategy))
goto err;
/*
We allow to change temporary tables even if they were locked for read
by LOCK TABLES. To avoid a discrepancy between lock acquired at LOCK
TABLES time and by the statement which is later executed under LOCK TABLES
we ensure that for temporary tables we always request a write lock (such
discrepancy can cause problems for the storage engine).
We don't set TABLE_LIST::lock_type in this case as this might result in
extra warnings from THD::decide_logging_format() even though binary logging
is totally irrelevant for LOCK TABLES.
*/
for (table= tables; table; table= table->next_global)
if (!table->placeholder() && table->table->s->tmp_table)
table->table->reginfo.lock_type= TL_WRITE;
if (lock_tables(thd, tables, counter, 0) ||
thd->locked_tables_list.init_locked_tables(thd))
goto err;
thd->in_lock_tables= 0;
return FALSE;
err:
thd->in_lock_tables= 0;
trans_rollback_stmt(thd);
/*
Need to end the current transaction, so the storage engine (InnoDB)
can free its locks if LOCK TABLES locked some tables before finding
that it can't lock a table in its list
*/
trans_rollback(thd);
/* Close tables and release metadata locks. */
close_thread_tables(thd);
DBUG_ASSERT(!thd->locked_tables_mode);
thd->mdl_context.release_transactional_locks();
return TRUE;
}
/**
Check if need to wait on pk, and waitting.
@param thd Thread handler
@param all_tables global table list of query
@param lex LEX for SELECT statement.
*/
void check_queue_on_pk(THD *thd, TABLE_LIST *all_tables, LEX *lex)
{
if (thd->execute_item)
return;
unsigned long long primary_id= 0;
if (ic_reduce_hint_enable)
{
primary_id= thd->lex->ic_reduce_id;
}
if (primary_id == 0)
return;
ic_hash_item_t *new_item, *item;
new_item= new ic_hash_item_t;
new_item->key_len= snprintf(new_item->key, sizeof(new_item->key), "%s\1%s\1%llu",
all_tables->db, all_tables->table_name, primary_id);
pthread_mutex_lock(&ic_gather_hash_lock);
item= (ic_hash_item_t*) my_hash_search(&ic_gather_hash, (const uchar*)new_item->key,
new_item->key_len);
if (!item)
{
item= new_item;
my_hash_insert(&ic_gather_hash, (const uchar*)new_item);
}
else
{
delete new_item;
}
pthread_mutex_lock(&item->queue_lock);
pthread_mutex_unlock(&ic_gather_hash_lock);
item->left_thread_num++;
pthread_mutex_unlock(&item->queue_lock);
DEBUG_SYNC(thd, "after_unlock_queue_lock_in_check");
thd->execute_item= item;
pthread_mutex_lock(&item->execute_lock);
}
/**
Execute command saved in thd and lex->sql_command.
@param thd Thread handle
@todo
- Invalidate the table in the query cache if something changed
after unlocking when changes become visible.
@todo: this is workaround. right way will be move invalidating in
the unlock procedure.
- TODO: use check_change_password()
@retval
FALSE OK
@retval
TRUE Error
*/
int
mysql_execute_command(THD *thd)
{
int res= FALSE;
int up_result= 0;
LEX *lex= thd->lex;
/* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
SELECT_LEX *select_lex= &lex->select_lex;
/* first table of first SELECT_LEX */
TABLE_LIST *first_table= select_lex->table_list.first;
/* list of all tables in query */
TABLE_LIST *all_tables;
/* most outer SELECT_LEX_UNIT of query */
SELECT_LEX_UNIT *unit= &lex->unit;
#ifdef HAVE_REPLICATION
/* have table map for update for multi-update statement (BUG#37051) */
bool have_table_map_for_update= FALSE;
#endif
DBUG_ENTER("mysql_execute_command");
DBUG_ASSERT(!lex->describe || is_explainable_query(lex->sql_command));
if (unlikely(lex->is_broken()))
{
// Force a Reprepare, to get a fresh LEX
Reprepare_observer *reprepare_observer= thd->get_reprepare_observer();
if (reprepare_observer &&
reprepare_observer->report_error(thd))
{
DBUG_ASSERT(thd->is_error());
DBUG_RETURN(1);
}
}
#ifdef WITH_PARTITION_STORAGE_ENGINE
thd->work_part_info= 0;
#endif
thd->trx_end_by_hint= FALSE;
DBUG_ASSERT(thd->transaction.stmt.is_empty() || thd->in_sub_stmt);
/*
Each statement or replication event which might produce deadlock
should handle transaction rollback on its own. So by the start of
the next statement transaction rollback request should be fulfilled
already.
*/
DBUG_ASSERT(! thd->transaction_rollback_request || thd->in_sub_stmt);
/*
In many cases first table of main SELECT_LEX have special meaning =>
check that it is first table in global list and relink it first in
queries_tables list if it is necessary (we need such relinking only
for queries with subqueries in select list, in this case tables of
subqueries will go to global list first)
all_tables will differ from first_table only if most upper SELECT_LEX
do not contain tables.
Because of above in place where should be at least one table in most
outer SELECT_LEX we have following check:
DBUG_ASSERT(first_table == all_tables);
DBUG_ASSERT(first_table == all_tables && first_table != 0);
*/
lex->first_lists_tables_same();
/* should be assigned after making first tables same */
all_tables= lex->query_tables;
/* set context for commands which do not use setup_tables */
select_lex->
context.resolve_in_table_list_only(select_lex->
table_list.first);
/*
Reset warning count for each query that uses tables
A better approach would be to reset this for any commands
that is not a SHOW command or a select that only access local
variables, but for now this is probably good enough.
*/
if ((sql_command_flags[lex->sql_command] & CF_DIAGNOSTIC_STMT) != 0)
thd->get_stmt_da()->set_warning_info_read_only(TRUE);
else
{
thd->get_stmt_da()->set_warning_info_read_only(FALSE);
if (all_tables)
thd->get_stmt_da()->opt_clear_warning_info(thd->query_id);
}
#ifdef HAVE_REPLICATION
if (unlikely(thd->slave_thread))
{
// Database filters.
if (lex->sql_command != SQLCOM_BEGIN &&
lex->sql_command != SQLCOM_COMMIT &&
lex->sql_command != SQLCOM_SAVEPOINT &&
lex->sql_command != SQLCOM_ROLLBACK &&
lex->sql_command != SQLCOM_ROLLBACK_TO_SAVEPOINT &&
!rpl_filter->db_ok(thd->db))
DBUG_RETURN(0);
if (lex->sql_command == SQLCOM_DROP_TRIGGER)
{
/*
When dropping a trigger, we need to load its table name
before checking slave filter rules.
*/
add_table_for_trigger(thd, thd->lex->spname, 1, &all_tables);
if (!all_tables)
{
/*
If table name cannot be loaded,
it means the trigger does not exists possibly because
CREATE TRIGGER was previously skipped for this trigger
according to slave filtering rules.
Returning success without producing any errors in this case.
*/
DBUG_RETURN(0);
}
// force searching in slave.cc:tables_ok()
all_tables->updating= 1;
}
/*
For fix of BUG#37051, the master stores the table map for update
in the Query_log_event, and the value is assigned to
thd->variables.table_map_for_update before executing the update
query.
If thd->variables.table_map_for_update is set, then we are
replicating from a new master, we can use this value to apply
filter rules without opening all the tables. However If
thd->variables.table_map_for_update is not set, then we are
replicating from an old master, so we just skip this and
continue with the old method. And of course, the bug would still
exist for old masters.
*/
if (lex->sql_command == SQLCOM_UPDATE_MULTI &&
thd->table_map_for_update)
{
have_table_map_for_update= TRUE;
table_map table_map_for_update= thd->table_map_for_update;
uint nr= 0;
TABLE_LIST *table;
for (table=all_tables; table; table=table->next_global, nr++)
{
if (table_map_for_update & ((table_map)1 << nr))
table->updating= TRUE;
else
table->updating= FALSE;
}
if (all_tables_not_ok(thd, all_tables))
{
/* we warn the slave SQL thread */
my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0));
if (thd->one_shot_set)
reset_one_shot_variables(thd);
DBUG_RETURN(0);
}
for (table=all_tables; table; table=table->next_global)
table->updating= TRUE;
}
/*
Check if statment should be skipped because of slave filtering
rules
Exceptions are:
- UPDATE MULTI: For this statement, we want to check the filtering
rules later in the code
- SET: we always execute it (Not that many SET commands exists in
the binary log anyway -- only 4.1 masters write SET statements,
in 5.0 there are no SET statements in the binary log)
- DROP TEMPORARY TABLE IF EXISTS: we always execute it (otherwise we
have stale files on slave caused by exclusion of one tmp table).
*/
if (!(lex->sql_command == SQLCOM_UPDATE_MULTI) &&
!(lex->sql_command == SQLCOM_SET_OPTION) &&
!(lex->sql_command == SQLCOM_DROP_TABLE &&
lex->drop_temporary && lex->drop_if_exists) &&
all_tables_not_ok(thd, all_tables))
{
/* we warn the slave SQL thread */
my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0));
if (thd->one_shot_set)
{
/*
It's ok to check thd->one_shot_set here:
The charsets in a MySQL 5.0 slave can change by both a binlogged
SET ONE_SHOT statement and the event-internal charset setting,
and these two ways to change charsets do not seems to work
together.
At least there seems to be problems in the rli cache for
charsets if we are using ONE_SHOT. Note that this is normally no
problem because either the >= 5.0 slave reads a 4.1 binlog (with
ONE_SHOT) *or* or 5.0 binlog (without ONE_SHOT) but never both."
*/
reset_one_shot_variables(thd);
}
DBUG_RETURN(0);
}
/*
Execute deferred events first
*/
if (slave_execute_deferred_events(thd))
DBUG_RETURN(-1);
}
else
{
#endif /* HAVE_REPLICATION */
/*
When option readonly is set deny operations which change non-temporary
tables. Except for the replication thread and the 'super' users.
*/
if (deny_updates_if_read_only_option(thd, all_tables))
{
my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
DBUG_RETURN(-1);
}
#ifdef HAVE_REPLICATION
} /* endif unlikely slave */
#endif
Opt_trace_start ots(thd, all_tables, lex->sql_command, &lex->var_list,
thd->query(), thd->query_length(), NULL,
thd->variables.character_set_client);
Opt_trace_object trace_command(&thd->opt_trace);
Opt_trace_array trace_command_steps(&thd->opt_trace, "steps");
DBUG_ASSERT(thd->transaction.stmt.cannot_safely_rollback() == FALSE);
if (need_traffic_control(thd, lex->sql_command))
{
thd->killed= THD::KILL_QUERY;
goto error;
}
status_var_increment(thd->status_var.com_stat[lex->sql_command]);
switch (gtid_pre_statement_checks(thd))
{
case GTID_STATEMENT_EXECUTE:
break;
case GTID_STATEMENT_CANCEL:
DBUG_RETURN(-1);
case GTID_STATEMENT_SKIP:
my_ok(thd);
DBUG_RETURN(0);
}
/*
End a active transaction so that this command will have it's
own transaction and will also sync the binary log. If a DDL is
not run in it's own transaction it may simply never appear on
the slave in case the outside transaction rolls back.
*/
if (stmt_causes_implicit_commit(thd, CF_IMPLICIT_COMMIT_BEGIN))
{
/*
Note that this should never happen inside of stored functions
or triggers as all such statements prohibited there.
*/
DBUG_ASSERT(! thd->in_sub_stmt);
/* Statement transaction still should not be started. */
DBUG_ASSERT(thd->transaction.stmt.is_empty());
/*
Implicit commit is not allowed with an active XA transaction.
In this case we should not release metadata locks as the XA transaction
will not be rolled back. Therefore we simply return here.
*/
if (trans_check_state(thd))
DBUG_RETURN(-1);
/* Commit the normal transaction if one is active. */
if (trans_commit_implicit(thd))
goto error;
/* Release metadata locks acquired in this transaction. */
thd->mdl_context.release_transactional_locks();
}
#ifndef DBUG_OFF
if (lex->sql_command != SQLCOM_SET_OPTION)
DEBUG_SYNC(thd,"before_execute_sql_command");
#endif
/* RDS: thread_running high watermark check. */
if (thread_running_control_high(thd))
{
my_error(ER_RDS_SERVER_THREAD_RUNNING_TOO_HIGH, MYF(0));
goto error;
}
/*
Check if we are in a read-only transaction and we're trying to
execute a statement which should always be disallowed in such cases.
Note that this check is done after any implicit commits.
*/
if (thd->tx_read_only &&
(sql_command_flags[lex->sql_command] & CF_DISALLOW_IN_RO_TRANS))
{
my_error(ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION, MYF(0));
goto error;
}
/*
Close tables open by HANDLERs before executing DDL statement
which is going to affect those tables.
This should happen before temporary tables are pre-opened as
otherwise we will get errors about attempt to re-open tables
if table to be changed is open through HANDLER.
Note that even although this is done before any privilege
checks there is no security problem here as closing open
HANDLER doesn't require any privileges anyway.
*/
if (sql_command_flags[lex->sql_command] & CF_HA_CLOSE)
mysql_ha_rm_tables(thd, all_tables);
/*
Pre-open temporary tables to simplify privilege checking
for statements which need this.
*/
if (sql_command_flags[lex->sql_command] & CF_PREOPEN_TMP_TABLES)
{
if (open_temporary_tables(thd, all_tables))
goto error;
}
switch (lex->sql_command) {
case SQLCOM_SHOW_STATUS:
{
system_status_var old_status_var= thd->status_var;
thd->initial_status_var= &old_status_var;
if (!(res= select_precheck(thd, lex, all_tables, first_table)))
res= execute_sqlcom_select(thd, all_tables);
/* Don't log SHOW STATUS commands to slow query log */
thd->server_status&= ~(SERVER_QUERY_NO_INDEX_USED |
SERVER_QUERY_NO_GOOD_INDEX_USED);
/*
restore status variables, as we don't want 'show status' to cause
changes
*/
mysql_mutex_lock(&LOCK_status);
add_diff_to_status(&global_status_var, &thd->status_var,
&old_status_var);
thd->status_var= old_status_var;
mysql_mutex_unlock(&LOCK_status);
break;
}
case SQLCOM_SHOW_EVENTS:
#ifndef HAVE_EVENT_SCHEDULER
my_error(ER_NOT_SUPPORTED_YET, MYF(0), "embedded server");
break;
#endif
case SQLCOM_SHOW_STATUS_PROC:
case SQLCOM_SHOW_STATUS_FUNC:
case SQLCOM_SHOW_DATABASES:
case SQLCOM_SHOW_TABLES:
case SQLCOM_SHOW_TRIGGERS:
case SQLCOM_SHOW_TABLE_STATUS:
case SQLCOM_SHOW_OPEN_TABLES:
case SQLCOM_SHOW_PLUGINS:
case SQLCOM_SHOW_FIELDS:
case SQLCOM_SHOW_KEYS:
case SQLCOM_SHOW_VARIABLES:
case SQLCOM_SHOW_CHARSETS:
case SQLCOM_SHOW_COLLATIONS:
case SQLCOM_SHOW_STORAGE_ENGINES:
case SQLCOM_SHOW_PROFILE:
case SQLCOM_SELECT:
{
thd->status_var.last_query_cost= 0.0;
thd->status_var.last_query_partial_plans= 0;
if ((res= select_precheck(thd, lex, all_tables, first_table)))
break;
res= execute_sqlcom_select(thd, all_tables);
break;
}
case SQLCOM_PREPARE:
{
mysql_sql_stmt_prepare(thd);
break;
}
case SQLCOM_EXECUTE:
{
mysql_sql_stmt_execute(thd);
break;
}
case SQLCOM_DEALLOCATE_PREPARE:
{
mysql_sql_stmt_close(thd);
break;
}
case SQLCOM_DO:
if (check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE)
|| open_and_lock_tables(thd, all_tables, TRUE, 0))
goto error;
res= mysql_do(thd, *lex->insert_list);
break;
case SQLCOM_EMPTY_QUERY:
my_ok(thd);
break;
case SQLCOM_HELP:
res= mysqld_help(thd,lex->help_arg);
break;
#ifndef EMBEDDED_LIBRARY
case SQLCOM_PURGE:
{
if (check_global_access(thd, SUPER_ACL))
goto error;
/* PURGE MASTER LOGS TO 'file' */
res = purge_master_logs(thd, lex->to_log);
break;
}
case SQLCOM_PURGE_BEFORE:
{
Item *it;
if (check_global_access(thd, SUPER_ACL))
goto error;
/* PURGE MASTER LOGS BEFORE 'data' */
it= (Item *)lex->value_list.head();
if ((!it->fixed && it->fix_fields(lex->thd, &it)) ||
it->check_cols(1))
{
my_error(ER_WRONG_ARGUMENTS, MYF(0), "PURGE LOGS BEFORE");
goto error;
}
it= new Item_func_unix_timestamp(it);
/*
it is OK only emulate fix_fieds, because we need only
value of constant
*/
it->quick_fix_field();
res = purge_master_logs_before_date(thd, (ulong)it->val_int());
break;
}
#endif
case SQLCOM_SHOW_WARNS:
{
res= mysqld_show_warnings(thd, (ulong)
((1L << (uint) Sql_condition::WARN_LEVEL_NOTE) |
(1L << (uint) Sql_condition::WARN_LEVEL_WARN) |
(1L << (uint) Sql_condition::WARN_LEVEL_ERROR)
));
break;
}
case SQLCOM_SHOW_ERRORS:
{
res= mysqld_show_warnings(thd, (ulong)
(1L << (uint) Sql_condition::WARN_LEVEL_ERROR));
break;
}
case SQLCOM_SHOW_PROFILES:
{
#if defined(ENABLED_PROFILING)
thd->profiling.discard_current_query();
res= thd->profiling.show_profiles();
if (res)
goto error;
#else
my_error(ER_FEATURE_DISABLED, MYF(0), "SHOW PROFILES", "enable-profiling");
goto error;
#endif
break;
}
#ifdef HAVE_REPLICATION
case SQLCOM_SHOW_SLAVE_HOSTS:
{
if (check_global_access(thd, REPL_SLAVE_ACL))
goto error;
res= show_slave_hosts(thd);
break;
}
case SQLCOM_SHOW_RELAYLOG_EVENTS:
{
if (check_global_access(thd, REPL_SLAVE_ACL))
goto error;
res = mysql_show_relaylog_events(thd);
break;
}
case SQLCOM_SHOW_BINLOG_EVENTS:
{
if (check_global_access(thd, REPL_SLAVE_ACL))
goto error;
res = mysql_show_binlog_events(thd);
break;
}
#endif
case SQLCOM_ASSIGN_TO_KEYCACHE:
{
DBUG_ASSERT(first_table == all_tables && first_table != 0);
if (check_access(thd, INDEX_ACL, first_table->db,
&first_table->grant.privilege,
&first_table->grant.m_internal,
0, 0))
goto error;
res= mysql_assign_to_keycache(thd, first_table, &lex->ident);
break;
}
case SQLCOM_PRELOAD_KEYS:
{
DBUG_ASSERT(first_table == all_tables && first_table != 0);
if (check_access(thd, INDEX_ACL, first_table->db,
&first_table->grant.privilege,
&first_table->grant.m_internal,
0, 0))
goto error;
res = mysql_preload_keys(thd, first_table);
break;
}
#ifdef HAVE_REPLICATION
case SQLCOM_CHANGE_MASTER:
{
if (check_global_access(thd, SUPER_ACL))
goto error;
mysql_mutex_lock(&LOCK_active_mi);
if (active_mi != NULL)
res= change_master(thd, active_mi);
else
my_message(ER_SLAVE_CONFIGURATION, ER(ER_SLAVE_CONFIGURATION),
MYF(0));
mysql_mutex_unlock(&LOCK_active_mi);
break;
}
case SQLCOM_SHOW_SLAVE_STAT:
{
/* Accept one of two privileges */
if (check_global_access(thd, SUPER_ACL | REPL_CLIENT_ACL))
goto error;
mysql_mutex_lock(&LOCK_active_mi);
res= show_slave_status(thd, active_mi);
mysql_mutex_unlock(&LOCK_active_mi);
break;
}
case SQLCOM_SHOW_MASTER_STAT:
{
/* Accept one of two privileges */
if (check_global_access(thd, SUPER_ACL | REPL_CLIENT_ACL))
goto error;
res = show_master_status(thd);
break;
}
#endif /* HAVE_REPLICATION */
case SQLCOM_SHOW_ENGINE_STATUS:
{
if (check_global_access(thd, PROCESS_ACL))
goto error;
res = ha_show_status(thd, lex->create_info.db_type, HA_ENGINE_STATUS);
break;
}
case SQLCOM_SHOW_ENGINE_MUTEX:
{
if (check_global_access(thd, PROCESS_ACL))
goto error;
res = ha_show_status(thd, lex->create_info.db_type, HA_ENGINE_MUTEX);
break;
}
case SQLCOM_CREATE_TABLE:
{
DBUG_ASSERT(first_table == all_tables && first_table != 0);
bool link_to_local;
TABLE_LIST *create_table= first_table;
TABLE_LIST *select_tables= lex->create_last_non_select_table->next_global;
if (lex->seq_create_info)
{
if (prepare_create_sequence(thd, lex, create_table))
goto error;
}
/*
Code below (especially in mysql_create_table() and select_create
methods) may modify HA_CREATE_INFO structure in LEX, so we have to
use a copy of this structure to make execution prepared statement-
safe. A shallow copy is enough as this code won't modify any memory
referenced from this structure.
*/
HA_CREATE_INFO create_info(lex->create_info);
/*
We need to copy alter_info for the same reasons of re-execution
safety, only in case of Alter_info we have to do (almost) a deep
copy.
*/
Alter_info alter_info(lex->alter_info, thd->mem_root);
if (thd->is_fatal_error)
{
/* If out of memory when creating a copy of alter_info. */
res= 1;
goto end_with_restore_list;
}
if ((res= create_table_precheck(thd, select_tables, create_table)))
goto end_with_restore_list;
/* Might have been updated in create_table_precheck */
create_info.alias= create_table->alias;
/* Fix names if symlinked or relocated tables */
if (append_file_to_dir(thd, &create_info.data_file_name,
create_table->table_name) ||
append_file_to_dir(thd, &create_info.index_file_name,
create_table->table_name))
goto end_with_restore_list;
/*
If no engine type was given, work out the default now
rather than at parse-time.
*/
if (!(create_info.used_fields & HA_CREATE_USED_ENGINE))
create_info.db_type= create_info.options & HA_LEX_CREATE_TMP_TABLE ?
ha_default_temp_handlerton(thd) : ha_default_handlerton(thd);
/*
If we are using SET CHARSET without DEFAULT, add an implicit
DEFAULT to not confuse old users. (This may change).
*/
if ((create_info.used_fields &
(HA_CREATE_USED_DEFAULT_CHARSET | HA_CREATE_USED_CHARSET)) ==
HA_CREATE_USED_CHARSET)
{
create_info.used_fields&= ~HA_CREATE_USED_CHARSET;
create_info.used_fields|= HA_CREATE_USED_DEFAULT_CHARSET;
create_info.default_table_charset= create_info.table_charset;
create_info.table_charset= 0;
}
#ifdef WITH_PARTITION_STORAGE_ENGINE
{
partition_info *part_info= thd->lex->part_info;
if (part_info && !(part_info= thd->lex->part_info->get_clone(true)))
{
res= -1;
goto end_with_restore_list;
}
thd->work_part_info= part_info;
}
#endif
if (select_lex->item_list.elements) // With select
{
select_result *result;
/*
CREATE TABLE...IGNORE/REPLACE SELECT... can be unsafe, unless
ORDER BY PRIMARY KEY clause is used in SELECT statement. We therefore
use row based logging if mixed or row based logging is available.
TODO: Check if the order of the output of the select statement is
deterministic. Waiting for BUG#42415
*/
if(lex->ignore)
lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_CREATE_IGNORE_SELECT);
if(lex->duplicates == DUP_REPLACE)
lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_CREATE_REPLACE_SELECT);
/*
If:
a) we inside an SP and there was NAME_CONST substitution,
b) binlogging is on (STMT mode),
c) we log the SP as separate statements
raise a warning, as it may cause problems
(see 'NAME_CONST issues' in 'Binary Logging of Stored Programs')
*/
if (thd->query_name_consts &&
mysql_bin_log.is_open() &&
thd->variables.binlog_format == BINLOG_FORMAT_STMT &&
!mysql_bin_log.is_query_in_union(thd, thd->query_id))
{
List_iterator_fast<Item> it(select_lex->item_list);
Item *item;
uint splocal_refs= 0;
/* Count SP local vars in the top-level SELECT list */
while ((item= it++))
{
if (item->is_splocal())
splocal_refs++;
}
/*
If it differs from number of NAME_CONST substitution applied,
we may have a SOME_FUNC(NAME_CONST()) in the SELECT list,
that may cause a problem with binary log (see BUG#35383),
raise a warning.
*/
if (splocal_refs != thd->query_name_consts)
push_warning(thd,
Sql_condition::WARN_LEVEL_WARN,
ER_UNKNOWN_ERROR,
"Invoked routine ran a statement that may cause problems with "
"binary log, see 'NAME_CONST issues' in 'Binary Logging of Stored Programs' "
"section of the manual.");
}
select_lex->options|= SELECT_NO_UNLOCK;
unit->set_limit(select_lex);
/*
Disable non-empty MERGE tables with CREATE...SELECT. Too
complicated. See Bug #26379. Empty MERGE tables are read-only
and don't allow CREATE...SELECT anyway.
*/
if (create_info.used_fields & HA_CREATE_USED_UNION)
{
my_error(ER_WRONG_OBJECT, MYF(0), create_table->db,
create_table->table_name, "BASE TABLE");
res= 1;
goto end_with_restore_list;
}
if (!(res= open_normal_and_derived_tables(thd, all_tables, 0)))
{
/* The table already exists */
if (create_table->table || create_table->view)
{
if (create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS)
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
ER_TABLE_EXISTS_ERROR,
ER(ER_TABLE_EXISTS_ERROR),
create_info.alias);
my_ok(thd);
}
else
{
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), create_info.alias);
res= 1;
}
goto end_with_restore_list;
}
/*
Remove target table from main select and name resolution
context. This can't be done earlier as it will break view merging in
statements like "CREATE TABLE IF NOT EXISTS existing_view SELECT".
*/
lex->unlink_first_table(&link_to_local);
/* Updating any other table is prohibited in CTS statement */
for (TABLE_LIST *table= lex->query_tables; table;
table= table->next_global)
if (table->lock_type >= TL_WRITE_ALLOW_WRITE)
{
lex->link_first_table_back(create_table, link_to_local);
res= 1;
my_error(ER_CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT, MYF(0),
table->table_name, create_info.alias);
goto end_with_restore_list;
}
/*
select_create is currently not re-execution friendly and
needs to be created for every execution of a PS/SP.
*/
if ((result= new select_create(create_table,
&create_info,
&alter_info,
select_lex->item_list,
lex->duplicates,
lex->ignore,
select_tables)))
{
/*
CREATE from SELECT give its SELECT_LEX for SELECT,
and item_list belong to SELECT
*/
res= handle_select(thd, result, 0);
delete result;
}
lex->link_first_table_back(create_table, link_to_local);
}
}
else
{
/* regular create */
if (create_info.options & HA_LEX_CREATE_TABLE_LIKE)
{
/* CREATE TABLE ... LIKE ... */
res= mysql_create_like_table(thd, create_table, select_tables,
&create_info);
}
else
{
/* Regular CREATE TABLE */
res= mysql_create_table(thd, create_table,
&create_info, &alter_info);
if (!res && lex->seq_create_info
&& lex->native_create_sequence)
{
res= sequence_insert(thd, lex, create_table);
if (res)
break;
}
}
if (!res)
my_ok(thd);
}
end_with_restore_list:
break;
}
case SQLCOM_CREATE_INDEX:
/* Fall through */
case SQLCOM_DROP_INDEX:
/*
CREATE INDEX and DROP INDEX are implemented by calling ALTER
TABLE with proper arguments.
In the future ALTER TABLE will notice that the request is to
only add indexes and create these one by one for the existing
table without having to do a full rebuild.
*/
{
/* Prepare stack copies to be re-execution safe */
HA_CREATE_INFO create_info;
Alter_info alter_info(lex->alter_info, thd->mem_root);
if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */
goto error;
DBUG_ASSERT(first_table == all_tables && first_table != 0);
if (check_one_table_access(thd, INDEX_ACL, all_tables))
goto error; /* purecov: inspected */
/*
Currently CREATE INDEX or DROP INDEX cause a full table rebuild
and thus classify as slow administrative statements just like
ALTER TABLE.
*/
thd->enable_slow_log= opt_log_slow_admin_statements;
memset(&create_info, 0, sizeof(create_info));
create_info.db_type= 0;
create_info.row_type= ROW_TYPE_NOT_USED;
create_info.default_table_charset= thd->variables.collation_database;
res= mysql_alter_table(thd, first_table->db, first_table->table_name,
&create_info, first_table, &alter_info,
0, (ORDER*) 0, 0);
break;
}
#ifdef HAVE_REPLICATION
case SQLCOM_SLAVE_START:
{
mysql_mutex_lock(&LOCK_active_mi);
if (active_mi != NULL)
res= start_slave(thd, active_mi, 1 /* net report*/);
else
my_message(ER_SLAVE_CONFIGURATION, ER(ER_SLAVE_CONFIGURATION),
MYF(0));
mysql_mutex_unlock(&LOCK_active_mi);
break;
}
case SQLCOM_SLAVE_STOP:
/*
If the client thread has locked tables, a deadlock is possible.
Assume that
- the client thread does LOCK TABLE t READ.
- then the master updates t.
- then the SQL slave thread wants to update t,
so it waits for the client thread because t is locked by it.
- then the client thread does SLAVE STOP.
SLAVE STOP waits for the SQL slave thread to terminate its
update t, which waits for the client thread because t is locked by it.
To prevent that, refuse SLAVE STOP if the
client thread has locked tables
*/
if (thd->locked_tables_mode ||
thd->in_active_multi_stmt_transaction() || thd->global_read_lock.is_acquired())
{
my_message(ER_LOCK_OR_ACTIVE_TRANSACTION,
ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0));
goto error;
}
{
mysql_mutex_lock(&LOCK_active_mi);
if (active_mi != NULL)
res= stop_slave(thd, active_mi, 1 /* net report*/);
else
my_message(ER_SLAVE_CONFIGURATION, ER(ER_SLAVE_CONFIGURATION),
MYF(0));
mysql_mutex_unlock(&LOCK_active_mi);
break;
}
#endif /* HAVE_REPLICATION */
case SQLCOM_RENAME_TABLE:
{
DBUG_ASSERT(first_table == all_tables && first_table != 0);
TABLE_LIST *table;
for (table= first_table; table; table= table->next_local->next_local)
{
if (check_access(thd, ALTER_ACL | DROP_ACL, table->db,
&table->grant.privilege,
&table->grant.m_internal,
0, 0) ||
check_access(thd, INSERT_ACL | CREATE_ACL, table->next_local->db,
&table->next_local->grant.privilege,
&table->next_local->grant.m_internal,
0, 0))
goto error;
TABLE_LIST old_list, new_list;
/*
we do not need initialize old_list and new_list because we will
come table[0] and table->next[0] there
*/
old_list= table[0];
new_list= table->next_local[0];
if (check_grant(thd, ALTER_ACL | DROP_ACL, &old_list, FALSE, 1, FALSE) ||
(!test_all_bits(table->next_local->grant.privilege,
INSERT_ACL | CREATE_ACL) &&
check_grant(thd, INSERT_ACL | CREATE_ACL, &new_list, FALSE, 1,
FALSE)))
goto error;
}
if (mysql_rename_tables(thd, first_table, 0))
goto error;
break;
}
#ifndef EMBEDDED_LIBRARY
case SQLCOM_SHOW_BINLOGS:
#ifdef DONT_ALLOW_SHOW_COMMANDS
my_message(ER_NOT_ALLOWED_COMMAND, ER(ER_NOT_ALLOWED_COMMAND),
MYF(0)); /* purecov: inspected */
goto error;
#else
{
if (check_global_access(thd, SUPER_ACL | REPL_CLIENT_ACL))
goto error;
res = show_binlogs(thd);
break;
}
#endif
#endif /* EMBEDDED_LIBRARY */
case SQLCOM_SHOW_CREATE:
DBUG_ASSERT(first_table == all_tables && first_table != 0);
#ifdef DONT_ALLOW_SHOW_COMMANDS
my_message(ER_NOT_ALLOWED_COMMAND, ER(ER_NOT_ALLOWED_COMMAND),
MYF(0)); /* purecov: inspected */
goto error;
#else
{
/*
Access check:
SHOW CREATE TABLE require any privileges on the table level (ie
effecting all columns in the table).
SHOW CREATE VIEW require the SHOW_VIEW and SELECT ACLs on the table
level.
NOTE: SHOW_VIEW ACL is checked when the view is created.
*/
DBUG_PRINT("debug", ("lex->only_view: %d, table: %s.%s",
lex->only_view,
first_table->db, first_table->table_name));
if (lex->only_view)
{
if (check_table_access(thd, SELECT_ACL, first_table, FALSE, 1, FALSE))
{
DBUG_PRINT("debug", ("check_table_access failed"));
my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
"SHOW", thd->security_ctx->priv_user,
thd->security_ctx->host_or_ip, first_table->alias);
goto error;
}
DBUG_PRINT("debug", ("check_table_access succeeded"));
/* Ignore temporary tables if this is "SHOW CREATE VIEW" */
first_table->open_type= OT_BASE_ONLY;
}
else
{
/*
Temporary tables should be opened for SHOW CREATE TABLE, but not
for SHOW CREATE VIEW.
*/
if (open_temporary_tables(thd, all_tables))
goto error;
/*
The fact that check_some_access() returned FALSE does not mean that
access is granted. We need to check if first_table->grant.privilege
contains any table-specific privilege.
*/
DBUG_PRINT("debug", ("first_table->grant.privilege: %lx",
first_table->grant.privilege));
if (check_some_access(thd, SHOW_CREATE_TABLE_ACLS, first_table) ||
(first_table->grant.privilege & SHOW_CREATE_TABLE_ACLS) == 0)
{
my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
"SHOW", thd->security_ctx->priv_user,
thd->security_ctx->host_or_ip, first_table->alias);
goto error;
}
}
/* Access is granted. Execute the command. */
res= mysqld_show_create(thd, first_table);
break;
}
#endif
case SQLCOM_CHECKSUM:
{
DBUG_ASSERT(first_table == all_tables && first_table != 0);
if (check_table_access(thd, SELECT_ACL, all_tables,
FALSE, UINT_MAX, FALSE))
goto error; /* purecov: inspected */
res = mysql_checksum_table(thd, first_table, &lex->check_opt);
break;
}
case SQLCOM_UPDATE:
{
ha_rows found= 0, updated= 0;
DBUG_ASSERT(first_table == all_tables && first_table != 0);
if (update_precheck(thd, all_tables))
break;
/*
UPDATE IGNORE can be unsafe. We therefore use row based
logging if mixed or row based logging is available.
TODO: Check if the order of the output of the select statement is
deterministic. Waiting for BUG#42415
*/
if (lex->ignore)
lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_UPDATE_IGNORE);
DBUG_ASSERT(select_lex->offset_limit == 0);
unit->set_limit(select_lex);
MYSQL_UPDATE_START(thd->query());
check_queue_on_pk(thd, all_tables, lex);
res= (up_result= mysql_update(thd, all_tables,
select_lex->item_list,
lex->value_list,
select_lex->where,
select_lex->order_list.elements,
select_lex->order_list.first,
unit->select_limit_cnt,
lex->duplicates, lex->ignore,
&found, &updated));
if (!res && lex->ci_on_success)
{
trans_commit_stmt(thd);
trans_commit(thd);
thd->trx_end_by_hint= TRUE;
}
else if (res && lex->rb_on_fail)
{
trans_rollback_stmt(thd);
trans_rollback(thd);
thd->trx_end_by_hint= TRUE;
}
MYSQL_UPDATE_DONE(res, found, updated);
/* mysql_update return 2 if we need to switch to multi-update */
if (up_result != 2)
break;
/* Fall through */
}
case SQLCOM_UPDATE_MULTI:
{
DBUG_ASSERT(first_table == all_tables && first_table != 0);
/* if we switched from normal update, rights are checked */
if (up_result != 2)
{
if ((res= multi_update_precheck(thd, all_tables)))
break;
}
else
res= 0;
res= mysql_multi_update_prepare(thd);
#ifdef HAVE_REPLICATION
/* Check slave filtering rules */
if (unlikely(thd->slave_thread && !have_table_map_for_update))
{
if (all_tables_not_ok(thd, all_tables))
{
if (res!= 0)
{
res= 0; /* don't care of prev failure */
thd->clear_error(); /* filters are of highest prior */
}
/* we warn the slave SQL thread */
my_error(ER_SLAVE_IGNORED_TABLE, MYF(0));
break;
}
if (res)
break;
}
else
{
#endif /* HAVE_REPLICATION */
if (res)
break;
if (opt_readonly &&
!(thd->security_ctx->master_access & SUPER_ACL) &&
some_non_temp_table_to_be_updated(thd, all_tables))
{
my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
break;
}
#ifdef HAVE_REPLICATION
} /* unlikely */
#endif
{
multi_update *result_obj;
MYSQL_MULTI_UPDATE_START(thd->query());
res= mysql_multi_update(thd, all_tables,
&select_lex->item_list,
&lex->value_list,
select_lex->where,
select_lex->options,
lex->duplicates,
lex->ignore,
unit,
select_lex,
&result_obj);
if (result_obj)
{
MYSQL_MULTI_UPDATE_DONE(res, result_obj->num_found(),
result_obj->num_updated());
res= FALSE; /* Ignore errors here */
delete result_obj;
}
else
{
MYSQL_MULTI_UPDATE_DONE(1, 0, 0);
}
}
break;
}
case SQLCOM_REPLACE:
#ifndef DBUG_OFF
if (mysql_bin_log.is_open())
{
/*
Generate an incident log event before writing the real event
to the binary log. We put this event is before the statement
since that makes it simpler to check that the statement was
not executed on the slave (since incidents usually stop the
slave).
Observe that any row events that are generated will be
generated before.
This is only for testing purposes and will not be present in a
release build.
*/
Incident incident= INCIDENT_NONE;
DBUG_PRINT("debug", ("Just before generate_incident()"));
DBUG_EXECUTE_IF("incident_database_resync_on_replace",
incident= INCIDENT_LOST_EVENTS;);
if (incident)
{
Incident_log_event ev(thd, incident);
if (mysql_bin_log.write_incident(&ev, true/*need_lock_log=true*/))
{
res= 1;
break;
}
}
DBUG_PRINT("debug", ("Just after generate_incident()"));
}
#endif
case SQLCOM_INSERT:
{
DBUG_ASSERT(first_table == all_tables && first_table != 0);
/*
Since INSERT DELAYED doesn't support temporary tables, we could
not pre-open temporary tables for SQLCOM_INSERT / SQLCOM_REPLACE.
Open them here instead.
*/
if (first_table->lock_type != TL_WRITE_DELAYED)
{
if ((res= open_temporary_tables(thd, all_tables)))
break;
}
if ((res= insert_precheck(thd, all_tables)))
break;
MYSQL_INSERT_START(thd->query());
check_queue_on_pk(thd, all_tables, lex);
res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values,
lex->update_list, lex->value_list,
lex->duplicates, lex->ignore);
if (!res && lex->ci_on_success)
{
trans_commit_stmt(thd);
trans_commit(thd);
thd->trx_end_by_hint= TRUE;
}
else if (res && lex->rb_on_fail)
{
trans_rollback_stmt(thd);
trans_rollback(thd);
thd->trx_end_by_hint= TRUE;
}
MYSQL_INSERT_DONE(res, (ulong) thd->get_row_count_func());
/*
If we have inserted into a VIEW, and the base table has
AUTO_INCREMENT column, but this column is not accessible through
a view, then we should restore LAST_INSERT_ID to the value it
had before the statement.
*/
if (first_table->view && !first_table->contain_auto_increment)
thd->first_successful_insert_id_in_cur_stmt=
thd->first_successful_insert_id_in_prev_stmt;
DBUG_EXECUTE_IF("after_mysql_insert",
{
const char act[]=
"now "
"wait_for signal.continue";
DBUG_ASSERT(opt_debug_sync_timeout > 0);
DBUG_ASSERT(!debug_sync_set_action(current_thd,
STRING_WITH_LEN(act)));
};);
break;
}
case SQLCOM_REPLACE_SELECT:
case SQLCOM_INSERT_SELECT:
{
select_insert *sel_result;
DBUG_ASSERT(first_table == all_tables && first_table != 0);
if ((res= insert_precheck(thd, all_tables)))
break;
/*
INSERT...SELECT...ON DUPLICATE KEY UPDATE/REPLACE SELECT/
INSERT...IGNORE...SELECT can be unsafe, unless ORDER BY PRIMARY KEY
clause is used in SELECT statement. We therefore use row based
logging if mixed or row based logging is available.
TODO: Check if the order of the output of the select statement is
deterministic. Waiting for BUG#42415
*/
if (lex->sql_command == SQLCOM_INSERT_SELECT &&
lex->duplicates == DUP_UPDATE)
lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_INSERT_SELECT_UPDATE);
if (lex->sql_command == SQLCOM_INSERT_SELECT && lex->ignore)
lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_INSERT_IGNORE_SELECT);
if (lex->sql_command == SQLCOM_REPLACE_SELECT)
lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_REPLACE_SELECT);
/* Fix lock for first table */
if (first_table->lock_type == TL_WRITE_DELAYED)
first_table->lock_type= TL_WRITE;
/* Don't unlock tables until command is written to binary log */
select_lex->options|= SELECT_NO_UNLOCK;
unit->set_limit(select_lex);
if (!(res= open_normal_and_derived_tables(thd, all_tables, 0)))
{
MYSQL_INSERT_SELECT_START(thd->query());
/* Skip first table, which is the table we are inserting in */
TABLE_LIST *second_table= first_table->next_local;
select_lex->table_list.first= second_table;
select_lex->context.table_list=
select_lex->context.first_name_resolution_table= second_table;
res= mysql_insert_select_prepare(thd);
if (!res && (sel_result= new select_insert(first_table,
first_table->table,
&lex->field_list,
&lex->field_list,
&lex->update_list,
&lex->value_list,
lex->duplicates,
lex->ignore)))
{
if (lex->describe)
res= explain_multi_table_modification(thd, sel_result);
else
{
res= handle_select(thd, sel_result, OPTION_SETUP_TABLES_DONE);
/*
Invalidate the table in the query cache if something changed
after unlocking when changes become visible.
TODO: this is workaround. right way will be move invalidating in
the unlock procedure.
*/
if (!res && first_table->lock_type == TL_WRITE_CONCURRENT_INSERT &&
thd->lock)
{
/* INSERT ... SELECT should invalidate only the very first table */
TABLE_LIST *save_table= first_table->next_local;
first_table->next_local= 0;
query_cache_invalidate3(thd, first_table, 1);
first_table->next_local= save_table;
}
}
delete sel_result;
}
/* revert changes for SP */
MYSQL_INSERT_SELECT_DONE(res, (ulong) thd->get_row_count_func());
select_lex->table_list.first= first_table;
}
/*
If we have inserted into a VIEW, and the base table has
AUTO_INCREMENT column, but this column is not accessible through
a view, then we should restore LAST_INSERT_ID to the value it
had before the statement.
*/
if (first_table->view && !first_table->contain_auto_increment)
thd->first_successful_insert_id_in_cur_stmt=
thd->first_successful_insert_id_in_prev_stmt;
break;
}
case SQLCOM_DELETE:
{
DBUG_ASSERT(first_table == all_tables && first_table != 0);
if ((res= delete_precheck(thd, all_tables)))
break;
DBUG_ASSERT(select_lex->offset_limit == 0);
unit->set_limit(select_lex);
MYSQL_DELETE_START(thd->query());
res = mysql_delete(thd, all_tables, select_lex->where,
&select_lex->order_list,
unit->select_limit_cnt, select_lex->options);
MYSQL_DELETE_DONE(res, (ulong) thd->get_row_count_func());
break;
}
case SQLCOM_DELETE_MULTI:
{
DBUG_ASSERT(first_table == all_tables && first_table != 0);
TABLE_LIST *aux_tables= thd->lex->auxiliary_table_list.first;
uint del_table_count;
multi_delete *del_result;
if ((res= multi_delete_precheck(thd, all_tables)))
break;
/* condition will be TRUE on SP re-excuting */
if (select_lex->item_list.elements != 0)
select_lex->item_list.empty();
if (add_item_to_list(thd, new Item_null()))
goto error;
THD_STAGE_INFO(thd, stage_init);
if ((res= open_normal_and_derived_tables(thd, all_tables, 0)))
break;
MYSQL_MULTI_DELETE_START(thd->query());
if ((res= mysql_multi_delete_prepare(thd, &del_table_count)))
{
MYSQL_MULTI_DELETE_DONE(1, 0);
goto error;
}
if (!thd->is_fatal_error &&
(del_result= new multi_delete(aux_tables, del_table_count)))
{
if (lex->describe)
res= explain_multi_table_modification(thd, del_result);
else
{
res= mysql_select(thd,
select_lex->get_table_list(),
select_lex->with_wild,
select_lex->item_list,
select_lex->where,
(SQL_I_List<ORDER> *)NULL, (SQL_I_List<ORDER> *)NULL,
(Item *)NULL,
(select_lex->options | thd->variables.option_bits |
SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK |
OPTION_SETUP_TABLES_DONE) & ~OPTION_BUFFER_RESULT,
del_result, unit, select_lex);
res|= thd->is_error();
if (res)
del_result->abort_result_set();
}
MYSQL_MULTI_DELETE_DONE(res, del_result->num_deleted());
delete del_result;
}
else
{
res= TRUE; // Error
MYSQL_MULTI_DELETE_DONE(1, 0);
}
break;
}
case SQLCOM_DROP_TABLE:
{
DBUG_ASSERT(first_table == all_tables && first_table != 0);
/* Only allow super user to do FORCE DROP operation. */
if (lex->force_drop_table
&& (check_global_access(thd, SUPER_ACL)))
goto error;
if (!lex->drop_temporary)
{
if (check_table_access(thd, DROP_ACL, all_tables, FALSE, UINT_MAX, FALSE))
goto error; /* purecov: inspected */
}
/* DDL and binlog write order are protected by metadata locks. */
res= mysql_rm_table(thd, first_table, lex->drop_if_exists,
lex->drop_temporary);
}
break;
case SQLCOM_SHOW_PROCESSLIST:
if (!thd->security_ctx->priv_user[0] &&
check_global_access(thd,PROCESS_ACL))
break;
mysqld_list_processes(thd,
(thd->security_ctx->master_access & PROCESS_ACL ?
NullS :
thd->security_ctx->priv_user),
lex->verbose);
break;
case SQLCOM_SET_EXECUTED_GTID_SET:
if (!add_executed_gtid_set(thd, lex->add_executed_gtid_set_string))
my_ok(thd);
break;
case SQLCOM_SHOW_PRIVILEGES:
res= mysqld_show_privileges(thd);
break;
case SQLCOM_SHOW_ENGINE_LOGS:
#ifdef DONT_ALLOW_SHOW_COMMANDS
my_message(ER_NOT_ALLOWED_COMMAND, ER(ER_NOT_ALLOWED_COMMAND),
MYF(0)); /* purecov: inspected */
goto error;
#else
{
if (check_access(thd, FILE_ACL, any_db, NULL, NULL, 0, 0))
goto error;
res= ha_show_status(thd, lex->create_info.db_type, HA_ENGINE_LOGS);
break;
}
#endif
case SQLCOM_CHANGE_DB:
{
LEX_STRING db_str= { (char *) select_lex->db, strlen(select_lex->db) };
if (!mysql_change_db(thd, &db_str, FALSE))
my_ok(thd);
break;
}
case SQLCOM_LOAD:
{
DBUG_ASSERT(first_table == all_tables && first_table != 0);
uint privilege= (lex->duplicates == DUP_REPLACE ?
INSERT_ACL | DELETE_ACL : INSERT_ACL) |
(lex->local_file ? 0 : FILE_ACL);
if (lex->local_file)
{
if (!(thd->client_capabilities & CLIENT_LOCAL_FILES) ||
!opt_local_infile)
{
my_message(ER_NOT_ALLOWED_COMMAND, ER(ER_NOT_ALLOWED_COMMAND), MYF(0));
goto error;
}
}
if (check_one_table_access(thd, privilege, all_tables))
goto error;
res= mysql_load(thd, lex->exchange, first_table, lex->field_list,
lex->update_list, lex->value_list, lex->duplicates,
lex->ignore, (bool) lex->local_file);
break;
}
case SQLCOM_SET_OPTION:
{
List<set_var_base> *lex_var_list= &lex->var_list;
if ((check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE)
|| open_and_lock_tables(thd, all_tables, TRUE, 0)))
goto error;
if (!(res= sql_set_variables(thd, lex_var_list)))
{
/*
If the previous command was a SET ONE_SHOT, we don't want to forget
about the ONE_SHOT property of that SET. So we use a |= instead of = .
*/
thd->one_shot_set|= lex->one_shot_set;
my_ok(thd);
}
else
{
/*
We encountered some sort of error, but no message was sent.
Send something semi-generic here since we don't know which
assignment in the list caused the error.
*/
if (!thd->is_error())
my_error(ER_WRONG_ARGUMENTS,MYF(0),"SET");
goto error;
}
break;
}
case SQLCOM_UNLOCK_TABLES:
/*
It is critical for mysqldump --single-transaction --master-data that
UNLOCK TABLES does not implicitely commit a connection which has only
done FLUSH TABLES WITH READ LOCK + BEGIN. If this assumption becomes
false, mysqldump will not work.
*/