diff options
Diffstat (limited to 'sql/sql_parse.cc')
-rw-r--r-- | sql/sql_parse.cc | 2131 |
1 files changed, 1510 insertions, 621 deletions
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 82dbe809c7d..a27cbdf5962 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -15,16 +15,15 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ #define MYSQL_LEX 1 -#include "my_global.h" +#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_tablesx +#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 @@ -44,7 +43,6 @@ #include "sql_table.h" // mysql_create_like_table, // mysql_create_table, // mysql_alter_table, - // mysql_recreate_table, // mysql_backup_table, // mysql_restore_table #include "sql_reload.h" // reload_acl_and_cache @@ -82,6 +80,9 @@ #include <myisam.h> #include <my_dir.h> #include "rpl_handler.h" +#include "rpl_mi.h" + +#include "sql_digest.h" #include "sp_head.h" #include "sp.h" @@ -95,6 +96,7 @@ #include "probes_mysql.h" #include "set_var.h" #include "log_slow.h" +#include "sql_bootstrap.h" #define FLAGSTR(V,F) ((V)&(F)?#F" ":"") @@ -118,8 +120,9 @@ "FUNCTION" : "PROCEDURE") static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables); -static void sql_kill(THD *thd, ulong id, killed_state state); +static void sql_kill(THD *thd, longlong id, killed_state state, killed_type type); static void sql_kill_user(THD *thd, LEX_USER *user, killed_state state); +static bool lock_tables_precheck(THD *thd, TABLE_LIST *tables); static bool execute_show_status(THD *, TABLE_LIST *); static bool execute_rename_table(THD *, TABLE_LIST *, TABLE_LIST *); @@ -169,6 +172,7 @@ const char *xa_state_names[]={ */ inline bool all_tables_not_ok(THD *thd, TABLE_LIST *tables) { + Rpl_filter *rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter; return rpl_filter->is_on() && tables && !thd->spcont && !rpl_filter->tables_ok(thd->db, tables); } @@ -188,13 +192,15 @@ static bool some_non_temp_table_to_be_updated(THD *thd, TABLE_LIST *tables) /* - Implicitly commit a active transaction if statement requires so. + Check whether the statement implicitly commits an active transaction. @param thd Thread handle. @param mask Bitmask used for the SQL command match. + @return 0 No implicit commit + @return 1 Do a commit */ -static bool stmt_causes_implicit_commit(THD *thd, uint mask) +bool stmt_causes_implicit_commit(THD *thd, uint mask) { LEX *lex= thd->lex; bool skip= FALSE; @@ -205,12 +211,22 @@ static bool stmt_causes_implicit_commit(THD *thd, uint mask) switch (lex->sql_command) { case SQLCOM_DROP_TABLE: - skip= lex->drop_temporary; + skip= (lex->drop_temporary || + (thd->variables.option_bits & OPTION_GTID_BEGIN)); break; case SQLCOM_ALTER_TABLE: + /* If ALTER TABLE of non-temporary table, do implicit commit */ + skip= (lex->create_info.tmp_table()); + break; case SQLCOM_CREATE_TABLE: - /* If CREATE TABLE of non-temporary table, do implicit commit */ - skip= (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE); + /* + If CREATE TABLE of non-temporary table and the table is not part + if a BEGIN GTID ... COMMIT group, do a implicit commit. + This ensures that CREATE ... SELECT will in the same GTID group on the + master and slave. + */ + skip= (lex->create_info.tmp_table() || + (thd->variables.option_bits & OPTION_GTID_BEGIN)); break; case SQLCOM_SET_OPTION: skip= lex->autocommit ? FALSE : TRUE; @@ -266,14 +282,16 @@ void init_update_queries(void) sql_command_flags[SQLCOM_CREATE_TABLE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS | CF_CAN_GENERATE_ROW_EVENTS; - sql_command_flags[SQLCOM_CREATE_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS; sql_command_flags[SQLCOM_ALTER_TABLE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND | - CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS ; + CF_AUTO_COMMIT_TRANS | CF_REPORT_PROGRESS | + CF_INSERTS_DATA; 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 | CF_REPORT_PROGRESS; + CF_CAN_GENERATE_ROW_EVENTS | CF_REPORT_PROGRESS | + CF_INSERTS_DATA; 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; @@ -290,26 +308,61 @@ void init_update_queries(void) 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_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED | + CF_UPDATES_DATA; sql_command_flags[SQLCOM_UPDATE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED | + CF_UPDATES_DATA; sql_command_flags[SQLCOM_INSERT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED | + CF_INSERTS_DATA; sql_command_flags[SQLCOM_INSERT_SELECT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED | + CF_INSERTS_DATA; sql_command_flags[SQLCOM_DELETE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + 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_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_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED | + CF_INSERTS_DATA;; sql_command_flags[SQLCOM_REPLACE_SELECT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; + CF_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE | + CF_CAN_BE_EXPLAINED | + CF_INSERTS_DATA; sql_command_flags[SQLCOM_SELECT]= CF_REEXECUTION_FRAGILE | - CF_CAN_GENERATE_ROW_EVENTS; - sql_command_flags[SQLCOM_SET_OPTION]= CF_REEXECUTION_FRAGILE | CF_AUTO_COMMIT_TRANS; + 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_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; @@ -335,6 +388,7 @@ void init_update_queries(void) 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_EXPLAIN]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_PROCESSLIST]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_GRANTS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CREATE_DB]= CF_STATUS_COMMAND; @@ -350,7 +404,7 @@ void init_update_queries(void) 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; + sql_command_flags[SQLCOM_BINLOG_BASE64_EVENT]= CF_STATUS_COMMAND | CF_CAN_GENERATE_ROW_EVENTS; sql_command_flags[SQLCOM_SHOW_CLIENT_STATS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_USER_STATS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_TABLE_STATS]= CF_STATUS_COMMAND; @@ -362,18 +416,21 @@ void init_update_queries(void) 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_CREATE_ROLE]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_GRANT]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_GRANT_ROLE]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_REVOKE]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_REVOKE_ROLE]= CF_CHANGES_DATA; sql_command_flags[SQLCOM_OPTIMIZE]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_CREATE_FUNCTION]= 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; + sql_command_flags[SQLCOM_INSTALL_PLUGIN]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; /* The following is used to preserver CF_ROW_COUNT during the @@ -381,11 +438,32 @@ void init_update_queries(void) last called (or executed) statement is preserved. See mysql_execute_command() for how CF_ROW_COUNT is used. */ + /* + (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_CAN_GENERATE_ROW_EVENTS | + CF_OPTIMIZER_TRACE; // (1) sql_command_flags[SQLCOM_EXECUTE]= CF_CAN_GENERATE_ROW_EVENTS; /* + We don't want to change to statement based replication for these commands + */ + sql_command_flags[SQLCOM_ROLLBACK]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT; + /* We don't want to replicate ALTER TABLE for temp tables in row format */ + sql_command_flags[SQLCOM_ALTER_TABLE]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT; + /* We don't want to replicate TRUNCATE for temp tables in row format */ + sql_command_flags[SQLCOM_TRUNCATE]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT; + /* We don't want to replicate DROP for temp tables in row format */ + sql_command_flags[SQLCOM_DROP_TABLE]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT; + /* We don't want to replicate CREATE/DROP INDEX for temp tables in row format */ + sql_command_flags[SQLCOM_CREATE_INDEX]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT; + sql_command_flags[SQLCOM_DROP_INDEX]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT; + /* One can change replication mode with SET */ + sql_command_flags[SQLCOM_SET_OPTION]|= CF_FORCE_ORIGINAL_BINLOG_FORMAT; + + /* The following admin table operations are allowed on log tables. */ @@ -398,18 +476,120 @@ void init_update_queries(void) 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_REVOKE_ALL]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_ROLE]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_ROLE]|= 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_REVOKE_ROLE]|= 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_GRANT_ROLE]|= 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; + + /* + 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. + + Note that SQLCOM_RENAME_TABLE should not be in this list! + */ + 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_HA_OPEN]|= 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 temporary 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_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(const THD *thd) @@ -462,7 +642,7 @@ void execute_init_command(THD *thd, LEX_STRING *init_command, thd->profiling.set_query_source(buf, len); #endif - thd_proc_info(thd, "Execution of init_command"); + THD_STAGE_INFO(thd, stage_execution_of_init_command); save_client_capabilities= thd->client_capabilities; thd->client_capabilities|= CLIENT_MULTI_QUERIES; /* @@ -481,21 +661,29 @@ void execute_init_command(THD *thd, LEX_STRING *init_command, } +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 *buff, *res; - - DBUG_ENTER("handle_bootstrap"); + DBUG_ENTER("handle_bootstrap_impl"); #ifndef EMBEDDED_LIBRARY pthread_detach_this_thread(); thd->thread_stack= (char*) &thd; #endif /* EMBEDDED_LIBRARY */ - thd_proc_info(thd, 0); thd->security_ctx->user= (char*) my_strdup("boot", MYF(MY_WME)); - thd->security_ctx->priv_user[0]= thd->security_ctx->priv_host[0]=0; + thd->security_ctx->priv_user[0]= thd->security_ctx->priv_host[0]= + thd->security_ctx->priv_role[0]= 0; /* Make the "client" handle multiple results. This is necessary to enable stored procedures with SELECTs and Dynamic SQL @@ -503,50 +691,58 @@ static void handle_bootstrap_impl(THD *thd) */ thd->client_capabilities|= CLIENT_MULTI_RESULTS; - buff= (char*) thd->net.buff; thd->init_for_queries(); - while (mysql_file_fgets(buff, thd->net.max_packet, file)) + + for ( ; ; ) { + char buffer[MAX_BOOTSTRAP_QUERY_SIZE] = ""; + int rc, length; char *query; - /* strlen() can't be deleted because mysql_file_fgets() doesn't return length */ - ulong length= (ulong) strlen(buff); - while (buff[length-1] != '\n' && !mysql_file_feof(file)) + int error= 0; + + 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) { /* - We got only a part of the current string. Will try to increase - net buffer then read the rest of the current string. + mysql_parse() may have set a successful error status for the previous + query. We must clear the error status to report the bootstrap error. */ - /* purecov: begin tested */ - if (net_realloc(&(thd->net), 2 * thd->net.max_packet)) + 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) { - thd->protocol->end_statement(); - bootstrap_error= 1; + 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; - } - buff= (char*) thd->net.buff; - res= mysql_file_fgets(buff + length, thd->net.max_packet - length, file); - if (!res && !mysql_file_feof(file)) - { - thd->protocol->end_statement(); - bootstrap_error= 1; + + 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; - } - length+= (ulong) strlen(buff + length); - /* purecov: end */ - } - if (bootstrap_error) - break; /* purecov: inspected */ - while (length && (my_isspace(thd->charset(), buff[length-1]) || - buff[length-1] == ';')) - length--; - buff[length]=0; + default: + DBUG_ASSERT(false); + break; + } - /* Skip lines starting with delimiter */ - if (strncmp(buff, STRING_WITH_LEN("delimiter")) == 0) - continue; + thd->protocol->end_statement(); + bootstrap_error= 1; + break; + } - query= (char *) thd->memdup_w_gap(buff, length + 1, + query= (char *) thd->memdup_w_gap(buffer, length + 1, thd->db_length + 1 + QUERY_CACHE_DB_LENGTH_SIZE + QUERY_CACHE_FLAGS_SIZE); @@ -581,6 +777,7 @@ static void handle_bootstrap_impl(THD *thd) #if defined(ENABLED_PROFILING) thd->profiling.finish_current_query(); #endif + delete_explain_query(thd->lex); if (bootstrap_error) break; @@ -625,14 +822,13 @@ void do_handle_bootstrap(THD *thd) handle_bootstrap_impl(thd); end: - net_end(&thd->net); - thd->cleanup(); delete thd; #ifndef EMBEDDED_LIBRARY - mysql_mutex_lock(&LOCK_thread_count); - thread_count--; + thread_safe_decrement32(&thread_count, &thread_count_lock); in_bootstrap= FALSE; + + mysql_mutex_lock(&LOCK_thread_count); mysql_cond_broadcast(&COND_thread_count); mysql_mutex_unlock(&LOCK_thread_count); my_thread_end(); @@ -713,7 +909,7 @@ bool do_command(THD *thd) Consider moving to init_connect() instead. */ thd->clear_error(); // Clear error message - thd->stmt_da->reset_diagnostics_area(); + thd->get_stmt_da()->reset_diagnostics_area(); net_new_transaction(net); @@ -736,18 +932,31 @@ bool do_command(THD *thd) */ DEBUG_SYNC(thd, "before_do_command_net_read"); - if ((packet_length= my_net_read(net)) == packet_error) + packet_length= my_net_read_packet(net, 1); + + 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. @@ -790,9 +999,14 @@ bool do_command(THD *thd) my_net_set_read_timeout(net, thd->variables.net_read_timeout); DBUG_ASSERT(packet_length); + DBUG_ASSERT(!thd->apc_target.is_enabled()); return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1)); + DBUG_ASSERT(!thd->apc_target.is_enabled()); 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 */ @@ -893,7 +1107,12 @@ bool dispatch_command(enum enum_server_command command, THD *thd, { DBUG_PRINT("crash_dispatch_command_before", ("now")); DBUG_ABORT(); }); - thd->command=command; + /* 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. @@ -901,14 +1120,21 @@ bool dispatch_command(enum enum_server_command command, THD *thd, thd->enable_slow_log= TRUE; thd->query_plan_flags= QPLAN_INIT; thd->lex->sql_command= SQLCOM_END; /* to avoid confusing VIEW detectors */ + thd->reset_kill_query(); DEBUG_SYNC(thd,"dispatch_command_before_set_time"); thd->set_time(); - if (server_command_flags[command] & CF_SKIP_QUERY_ID) - thd->set_query_id(get_query_id()); - else + if (!(server_command_flags[command] & CF_SKIP_QUERY_ID)) thd->set_query_id(next_query_id()); + else + { + /* + ping, get statistics or similar stateless command. + No reason to increase query id here. + */ + thd->set_query_id(get_query_id()); + } inc_thread_running(); if (!(server_command_flags[command] & CF_SKIP_QUESTIONS)) @@ -944,6 +1170,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, #ifdef HAVE_REPLICATION case COM_REGISTER_SLAVE: { + status_var_increment(thd->status_var.com_register_slave); if (!register_slave(thd, (uchar*)packet, packet_length)) my_ok(thd); break; @@ -951,7 +1178,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, #endif case COM_CHANGE_USER: { - bool rc; + int auth_rc; status_var_increment(thd->status_var.com_other); thd->change_user(); @@ -982,13 +1209,13 @@ bool dispatch_command(enum enum_server_command command, THD *thd, if (thd->failed_com_change_user >= 3) { my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); - rc= 1; + auth_rc= 1; } else - rc= acl_authenticate(thd, 0, packet_length); + auth_rc= acl_authenticate(thd, packet_length); - MYSQL_AUDIT_NOTIFY_CONNECTION_CHANGE_USER(thd); - if (rc) + mysql_audit_notify_connection_change_user(thd); + if (auth_rc) { /* Free user if allocated by acl_authenticate */ my_free(thd->security_ctx->user); @@ -1048,6 +1275,10 @@ bool dispatch_command(enum enum_server_command command, THD *thd, } 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, @@ -1060,6 +1291,9 @@ bool dispatch_command(enum enum_server_command command, THD *thd, #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; @@ -1084,12 +1318,15 @@ bool dispatch_command(enum enum_server_command command, THD *thd, query_cache_end_of_result(thd); mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_STATUS, - thd->stmt_da->is_error() ? thd->stmt_da->sql_errno() - : 0, command_name[command].str); + 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); + DBUG_ASSERT(!thd->apc_target.is_enabled()); /* Remove garbage at start of query */ while (length > 0 && my_isspace(thd->charset(), *beginning_of_next_stmt)) @@ -1098,6 +1335,12 @@ bool dispatch_command(enum enum_server_command command, THD *thd, 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()); @@ -1109,11 +1352,23 @@ bool dispatch_command(enum enum_server_command command, THD *thd, 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()); /* @@ -1176,7 +1431,10 @@ bool dispatch_command(enum enum_server_command command, THD *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); + db.length= my_casedn_str(files_charset_info, db.str); + } table_list.init_one_table(db.str, db.length, table_name.str, table_name.length, table_name.str, TL_READ); /* @@ -1202,6 +1460,9 @@ bool dispatch_command(enum enum_server_command command, THD *thd, 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; @@ -1238,7 +1499,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, /* We don't calculate statistics for this command */ general_log_print(thd, command, NullS); net->error=0; // Don't give 'abort' message - thd->stmt_da->disable_status(); // Don't send anything back + thd->get_stmt_da()->disable_status(); // Don't send anything back error=TRUE; // End server break; #ifndef EMBEDDED_LIBRARY @@ -1258,10 +1519,10 @@ bool dispatch_command(enum enum_server_command command, THD *thd, /* TODO: The following has to be changed to an 8 byte integer */ pos = uint4korr(packet); flags = uint2korr(packet + 4); - thd->server_id=0; /* avoid suicide */ + thd->variables.server_id=0; /* avoid suicide */ if ((slave_server_id= uint4korr(packet+6))) // mysqlbinlog.server_id==0 kill_zombie_dump_threads(slave_server_id); - thd->server_id = slave_server_id; + thd->variables.server_id = slave_server_id; const char *name= packet + 10; size_t nlen= strlen(name); @@ -1286,7 +1547,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, lex_start(thd); status_var_increment(thd->status_var.com_stat[SQLCOM_FLUSH]); - ulong options= (ulong) (uchar) packet[0]; + ulonglong options= (ulonglong) (uchar) packet[0]; if (trans_commit_implicit(thd)) break; thd->mdl_context.release_transactional_locks(); @@ -1305,17 +1566,21 @@ bool dispatch_command(enum enum_server_command command, THD *thd, and flushes tables. */ bool res; - my_pthread_setspecific_ptr(THR_THD, NULL); + set_current_thd(0); res= reload_acl_and_cache(NULL, options | REFRESH_FAST, NULL, ¬_used); - my_pthread_setspecific_ptr(THR_THD, thd); + set_current_thd(thd); if (res) break; } else #endif - if (reload_acl_and_cache(thd, options, (TABLE_LIST*) 0, ¬_used)) - break; + { + thd->lex->relay_log_connection_name.str= (char*) ""; + thd->lex->relay_log_connection_name.length= 0; + if (reload_acl_and_cache(thd, options, (TABLE_LIST*) 0, ¬_used)) + break; + } if (trans_commit_implicit(thd)) break; close_thread_tables(thd); @@ -1370,7 +1635,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, if (!(uptime= (ulong) (thd->start_time - server_start_time))) queries_per_second1000= 0; else - queries_per_second1000= thd->query_id * LL(1000) / uptime; + queries_per_second1000= thd->query_id * 1000 / uptime; length= my_snprintf(buff, buff_len - 1, "Uptime: %lu Threads: %d Questions: %lu " @@ -1380,8 +1645,8 @@ bool dispatch_command(enum enum_server_command command, THD *thd, (int) thread_count, (ulong) thd->query_id, current_global_status_var->long_query_count, current_global_status_var->opened_tables, - refresh_version, - cached_open_tables(), + tdc_refresh_version(), + tc_records(), (uint) (queries_per_second1000 / 1000), (uint) (queries_per_second1000 % 1000)); #ifdef EMBEDDED_LIBRARY @@ -1390,7 +1655,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, #else (void) my_net_write(net, (uchar*) buff, length); (void) net_flush(net); - thd->stmt_da->disable_status(); + thd->get_stmt_da()->disable_status(); #endif break; } @@ -1412,7 +1677,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, { status_var_increment(thd->status_var.com_stat[SQLCOM_KILL]); ulong id=(ulong) uint4korr(packet); - sql_kill(thd,id, KILL_CONNECTION_HARD); + sql_kill(thd, id, KILL_CONNECTION_HARD, KILL_TYPE_ID); break; } case COM_SET_OPTION: @@ -1466,19 +1731,26 @@ bool dispatch_command(enum enum_server_command command, THD *thd, mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_RESULT, 0, 0); mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_STATUS, - thd->stmt_da->is_error() ? thd->stmt_da->sql_errno() : 0, + thd->get_stmt_da()->is_error() ? + thd->get_stmt_da()->sql_errno() : 0, command_name[command].str); thd->update_all_stats(); log_slow_statement(thd); - thd_proc_info(thd, "cleaning up"); + THD_STAGE_INFO(thd, stage_cleaning_up); thd->reset_query(); - thd->command=COM_SLEEP; + thd->set_examined_row_count(0); // For processlist + 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; + thd->set_time(); dec_thread_running(); - thd_proc_info(thd, 0); thd->packet.shrink(thd->variables.net_buffer_length); // Reclaim some memory free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC)); @@ -1503,30 +1775,38 @@ bool dispatch_command(enum enum_server_command command, THD *thd, } +/* + @note + This function must call delete_explain_query(). +*/ void log_slow_statement(THD *thd) { DBUG_ENTER("log_slow_statement"); + /* 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_VOID_RETURN; // Don't set time for sub stmt + goto end; // Don't set time for sub stmt + /* Follow the slow log filter configuration. */ if (!thd->enable_slow_log || (thd->variables.log_slow_filter && !(thd->variables.log_slow_filter & thd->query_plan_flags))) - DBUG_VOID_RETURN; + { + goto end; + } if (((thd->server_status & SERVER_QUERY_WAS_SLOW) || ((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))) && - thd->examined_row_count >= thd->variables.min_examined_row_limit) + thd->get_examined_row_count() >= thd->variables.min_examined_row_limit) { thd->status_var.long_query_count++; /* @@ -1535,13 +1815,15 @@ void log_slow_statement(THD *thd) */ if (thd->variables.log_slow_rate_limit > 1 && (global_query_id % thd->variables.log_slow_rate_limit) != 0) - DBUG_VOID_RETURN; + goto end; - thd_proc_info(thd, "logging slow query"); + THD_STAGE_INFO(thd, stage_logging_slow_query); slow_log_print(thd, thd->query(), thd->query_length(), thd->utime_after_query); - thd_proc_info(thd, 0); } + +end: + delete_explain_query(thd->lex); DBUG_VOID_RETURN; } @@ -1638,8 +1920,8 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident, 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 @@ -1748,25 +2030,7 @@ bool alloc_query(THD *thd, const char *packet, uint packet_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"); @@ -1803,7 +2067,7 @@ bool sp_process_definer(THD *thd) Query_arena original_arena; Query_arena *ps_arena= thd->activate_stmt_arena_if_needed(&original_arena); - lex->definer= create_default_definer(thd); + lex->definer= create_default_definer(thd, false); if (ps_arena) thd->restore_active_arena(ps_arena, &original_arena); @@ -1817,20 +2081,24 @@ bool sp_process_definer(THD *thd) } else { + LEX_USER *d= lex->definer= get_current_user(thd, lex->definer); + if (!d) + DBUG_RETURN(TRUE); + /* - If the specified definer differs from the current user, we + If the specified definer differs from the current user or role, 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, true)) - { - my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "SUPER"); + bool curuser= !strcmp(d->user.str, thd->security_ctx->priv_user); + bool currole= !curuser && !strcmp(d->user.str, thd->security_ctx->priv_role); + bool curuserhost= curuser && d->host.str && + !my_strcasecmp(system_charset_info, d->host.str, + thd->security_ctx->priv_host); + if (!curuserhost && !currole && + check_global_access(thd, SUPER_ACL, false)) DBUG_RETURN(TRUE); - } } /* Check that the specified definer exists. Emit a warning if not. */ @@ -1839,7 +2107,7 @@ bool sp_process_definer(THD *thd) if (!is_acl_user(lex->definer->host.str, lex->definer->user.str)) { push_warning_printf(thd, - MYSQL_ERROR::WARN_LEVEL_NOTE, + Sql_condition::WARN_LEVEL_NOTE, ER_NO_SUCH_USER, ER(ER_NO_SUCH_USER), lex->definer->user.str, @@ -1933,7 +2201,7 @@ err: int mysql_execute_command(THD *thd) { - int res= FALSE; + int res= 0; int up_result= 0; LEX *lex= thd->lex; /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ @@ -1947,6 +2215,8 @@ mysql_execute_command(THD *thd) #ifdef HAVE_REPLICATION /* have table map for update for multi-update statement (BUG#37051) */ bool have_table_map_for_update= FALSE; + /* */ + Rpl_filter *rpl_filter; #endif DBUG_ENTER("mysql_execute_command"); @@ -1992,12 +2262,12 @@ mysql_execute_command(THD *thd) variables, but for now this is probably good enough. */ if ((sql_command_flags[lex->sql_command] & CF_DIAGNOSTIC_STMT) != 0) - thd->warning_info->set_read_only(TRUE); + thd->get_stmt_da()->set_warning_info_read_only(TRUE); else { - thd->warning_info->set_read_only(FALSE); + thd->get_stmt_da()->set_warning_info_read_only(FALSE); if (all_tables) - thd->warning_info->opt_clear_warning_info(thd->query_id); + thd->get_stmt_da()->opt_clear_warning_info(thd->query_id); } #ifdef HAVE_REPLICATION @@ -2020,11 +2290,15 @@ mysql_execute_command(THD *thd) according to slave filtering rules. Returning success without producing any errors in this case. */ - DBUG_RETURN(0); + if (!thd->lex->check_exists) + DBUG_RETURN(0); + /* + DROP TRIGGER IF NOT EXISTS will return without an error later + after possibly writing the query to a binlog + */ } - - // force searching in slave.cc:tables_ok() - all_tables->updating= 1; + else // force searching in slave.cc:tables_ok() + all_tables->updating= 1; } /* @@ -2060,9 +2334,6 @@ mysql_execute_command(THD *thd) { /* 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); } } @@ -2082,28 +2353,11 @@ mysql_execute_command(THD *thd) 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) && + lex->drop_temporary && lex->check_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); } /* @@ -2129,11 +2383,27 @@ mysql_execute_command(THD *thd) #endif status_var_increment(thd->status_var.com_stat[lex->sql_command]); - thd->progress.report_to_client= test(sql_command_flags[lex->sql_command] & - CF_REPORT_PROGRESS); + thd->progress.report_to_client= MY_TEST(sql_command_flags[lex->sql_command] & + CF_REPORT_PROGRESS); DBUG_ASSERT(thd->transaction.stmt.modified_non_trans_table == FALSE); + /* store old value of binlog format */ + enum_binlog_format orig_binlog_format,orig_current_stmt_binlog_format; + + thd->get_binlog_format(&orig_binlog_format, + &orig_current_stmt_binlog_format); + + /* + Force statement logging for DDL commands to allow us to update + privilege, system or statistic tables directly without the updates + getting logged. + */ + if (!(sql_command_flags[lex->sql_command] & + (CF_CAN_GENERATE_ROW_EVENTS | CF_FORCE_ORIGINAL_BINLOG_FORMAT | + CF_STATUS_COMMAND))) + thd->set_binlog_format_stmt(); + /* 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 @@ -2149,18 +2419,59 @@ mysql_execute_command(THD *thd) DBUG_ASSERT(! thd->in_sub_stmt); /* Statement transaction still should not be started. */ DBUG_ASSERT(thd->transaction.stmt.is_empty()); - /* 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(); + if (!(thd->variables.option_bits & OPTION_GTID_BEGIN)) + { + /* 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 + /* + 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_EVENTS: @@ -2168,18 +2479,40 @@ mysql_execute_command(THD *thd) my_error(ER_NOT_SUPPORTED_YET, MYF(0), "embedded server"); break; #endif - case SQLCOM_SHOW_STATUS_PROC: - case SQLCOM_SHOW_STATUS_FUNC: - if ((res= check_table_access(thd, SELECT_ACL, all_tables, FALSE, - UINT_MAX, FALSE))) - goto error; - res= execute_sqlcom_select(thd, all_tables); - break; case SQLCOM_SHOW_STATUS: { execute_show_status(thd, all_tables); break; } + case SQLCOM_SHOW_EXPLAIN: + { + if (!thd->security_ctx->priv_user[0] && + check_global_access(thd,PROCESS_ACL)) + break; + + /* + The select should use only one table, it's the SHOW EXPLAIN pseudo-table + */ + if (lex->sroutines.records || lex->query_tables->next_global) + { + my_message(ER_SET_CONSTANTS_ONLY, ER(ER_SET_CONSTANTS_ONLY), + MYF(0)); + goto error; + } + + Item **it= lex->value_list.head_ref(); + if (!(*it)->basic_const_item() || + (!(*it)->fixed && (*it)->fix_fields(lex->thd, it)) || + (*it)->check_cols(1)) + { + my_message(ER_SET_CONSTANTS_ONLY, ER(ER_SET_CONSTANTS_ONLY), + MYF(0)); + goto error; + } + } + /* fall through */ + case SQLCOM_SHOW_STATUS_PROC: + case SQLCOM_SHOW_STATUS_FUNC: case SQLCOM_SHOW_DATABASES: case SQLCOM_SHOW_TABLES: case SQLCOM_SHOW_TRIGGERS: @@ -2284,16 +2617,16 @@ case SQLCOM_PREPARE: case SQLCOM_SHOW_WARNS: { res= mysqld_show_warnings(thd, (ulong) - ((1L << (uint) MYSQL_ERROR::WARN_LEVEL_NOTE) | - (1L << (uint) MYSQL_ERROR::WARN_LEVEL_WARN) | - (1L << (uint) MYSQL_ERROR::WARN_LEVEL_ERROR) + ((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) MYSQL_ERROR::WARN_LEVEL_ERROR)); + (1L << (uint) Sql_condition::WARN_LEVEL_ERROR)); break; } case SQLCOM_SHOW_PROFILES: @@ -2353,10 +2686,62 @@ case SQLCOM_PREPARE: #ifdef HAVE_REPLICATION case SQLCOM_CHANGE_MASTER: { + LEX_MASTER_INFO *lex_mi= &thd->lex->mi; + Master_info *mi; + bool new_master= 0; + bool master_info_added; + if (check_global_access(thd, SUPER_ACL)) goto error; + /* + In this code it's ok to use LOCK_active_mi as we are adding new things + into master_info_index + */ mysql_mutex_lock(&LOCK_active_mi); - res = change_master(thd,active_mi); + if (!master_info_index) + { + mysql_mutex_unlock(&LOCK_active_mi); + my_error(ER_SERVER_SHUTDOWN, MYF(0)); + goto error; + } + + mi= master_info_index->get_master_info(&lex_mi->connection_name, + Sql_condition::WARN_LEVEL_NOTE); + + if (mi == NULL) + { + /* New replication created */ + mi= new Master_info(&lex_mi->connection_name, relay_log_recovery); + if (!mi || mi->error()) + { + delete mi; + res= 1; + mysql_mutex_unlock(&LOCK_active_mi); + break; + } + new_master= 1; + } + + res= change_master(thd, mi, &master_info_added); + if (res && new_master) + { + /* + If the new master was added by change_master(), remove it as it didn't + work (this will free mi as well). + + If new master was not added, we still need to free mi. + */ + if (master_info_added) + master_info_index->remove_master_info(mi); + else + delete mi; + } + else + { + mi->rpl_filter= get_or_create_rpl_filter(lex_mi->connection_name.str, + lex_mi->connection_name.length); + } + mysql_mutex_unlock(&LOCK_active_mi); break; } @@ -2365,18 +2750,24 @@ case SQLCOM_PREPARE: /* Accept one of two privileges */ if (check_global_access(thd, SUPER_ACL | REPL_CLIENT_ACL)) goto error; - mysql_mutex_lock(&LOCK_active_mi); - if (active_mi != NULL) + + if (lex->verbose) { - res = show_master_info(thd, active_mi); + mysql_mutex_lock(&LOCK_active_mi); + res= show_all_master_info(thd); + mysql_mutex_unlock(&LOCK_active_mi); } else { - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_NO_MASTER_INFO, ER(WARN_NO_MASTER_INFO)); - my_ok(thd); + LEX_MASTER_INFO *lex_mi= &thd->lex->mi; + Master_info *mi; + if ((mi= get_master_info(&lex_mi->connection_name, + Sql_condition::WARN_LEVEL_ERROR))) + { + res= show_master_info(thd, mi, 0); + mi->release(); + } } - mysql_mutex_unlock(&LOCK_active_mi); break; } case SQLCOM_SHOW_MASTER_STAT: @@ -2432,20 +2823,20 @@ case SQLCOM_PREPARE: goto end_with_restore_list; } + /* Check privileges */ 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; -#ifdef HAVE_READLINK - /* Fix names if symlinked tables */ + /* 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; -#endif + /* If no engine type was given, work out the default now rather than at parse-time. @@ -2466,6 +2857,18 @@ case SQLCOM_PREPARE: create_info.table_charset= 0; } + /* + If we are a slave, we should add OR REPLACE if we don't have + IF EXISTS. This will help a slave to recover from + CREATE TABLE OR EXISTS failures by dropping the table and + retrying the create. + */ + create_info.org_options= create_info.options; + if (thd->slave_thread && + slave_ddl_exec_mode_options == SLAVE_EXEC_MODE_IDEMPOTENT && + !(lex->create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS)) + create_info.options|= HA_LEX_CREATE_REPLACE; + #ifdef WITH_PARTITION_STORAGE_ENGINE { partition_info *part_info= thd->lex->part_info; @@ -2478,9 +2881,6 @@ case SQLCOM_PREPARE: } #endif - /* Close any open handlers for the table. */ - mysql_ha_rm_tables(thd, create_table); - if (select_lex->item_list.elements) // With select { select_result *result; @@ -2528,7 +2928,7 @@ case SQLCOM_PREPARE: */ if (splocal_refs != thd->query_name_consts) push_warning(thd, - MYSQL_ERROR::WARN_LEVEL_WARN, + 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' " @@ -2551,34 +2951,35 @@ case SQLCOM_PREPARE: goto end_with_restore_list; } + /* Copy temporarily the statement flags to thd for lock_table_names() */ + uint save_thd_create_info_options= thd->lex->create_info.options; + thd->lex->create_info.options|= create_info.options; res= open_and_lock_tables(thd, lex->query_tables, TRUE, 0); + thd->lex->create_info.options= save_thd_create_info_options; if (res) { /* Got error or warning. Set res to 1 if error */ if (!(res= thd->is_error())) my_ok(thd); // CREATE ... IF NOT EXISTS + goto end_with_restore_list; } - else + + /* Ensure we don't try to create something from which we select from */ + if ((create_info.options & HA_LEX_CREATE_REPLACE) && + !create_info.tmp_table()) { - /* The table already exists */ - if (create_table->table) + TABLE_LIST *duplicate; + if ((duplicate= unique_table(thd, lex->query_tables, + lex->query_tables->next_global, + CHECK_DUP_FOR_CREATE))) { - if (create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS) - { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_TABLE_EXISTS_ERROR, - ER(ER_TABLE_EXISTS_ERROR), - create_info.alias); - my_ok(thd); - } - else - { - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), create_info.alias); - res= 1; - } + update_non_unique_table_error(lex->query_tables, "CREATE", + duplicate); + res= TRUE; 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 @@ -2586,9 +2987,8 @@ case SQLCOM_PREPARE: */ lex->unlink_first_table(&link_to_local); - /* So that CREATE TEMPORARY TABLE gets to binlog at commit/rollback */ - if (create_info.options & HA_LEX_CREATE_TMP_TABLE) - thd->variables.option_bits|= OPTION_KEEP_LOG; + /* Store reference to table in case of LOCK TABLES */ + create_info.table= create_table->table; /* select_create is currently not re-execution friendly and @@ -2606,18 +3006,18 @@ case SQLCOM_PREPARE: CREATE from SELECT give its SELECT_LEX for SELECT, and item_list belong to SELECT */ - res= handle_select(thd, lex, result, 0); + if (!(res= handle_select(thd, lex, result, 0))) + { + if (create_info.tmp_table()) + thd->variables.option_bits|= OPTION_KEEP_LOG; + } delete result; } - lex->link_first_table_back(create_table, link_to_local); } } else { - /* So that CREATE TEMPORARY TABLE gets to binlog at commit/rollback */ - if (create_info.options & HA_LEX_CREATE_TMP_TABLE) - thd->variables.option_bits|= OPTION_KEEP_LOG; /* regular create */ if (create_info.options & HA_LEX_CREATE_TABLE_LIKE) { @@ -2632,14 +3032,18 @@ case SQLCOM_PREPARE: &create_info, &alter_info); } if (!res) + { + /* So that CREATE TEMPORARY TABLE gets to binlog at commit/rollback */ + if (create_info.tmp_table()) + thd->variables.option_bits|= OPTION_KEEP_LOG; 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 @@ -2675,46 +3079,111 @@ end_with_restore_list: res= mysql_alter_table(thd, first_table->db, first_table->table_name, &create_info, first_table, &alter_info, - 0, (ORDER*) 0, 0, 0); + 0, (ORDER*) 0, 0); break; } #ifdef HAVE_REPLICATION case SQLCOM_SLAVE_START: { - mysql_mutex_lock(&LOCK_active_mi); - start_slave(thd,active_mi,1 /* net report*/); - mysql_mutex_unlock(&LOCK_active_mi); + LEX_MASTER_INFO* lex_mi= &thd->lex->mi; + Master_info *mi; + int load_error; + + load_error= rpl_load_gtid_slave_state(thd); + + /* + We don't need to ensure that only one user is using master_info + as start_slave is protected against simultaneous usage + */ + if ((mi= get_master_info(&lex_mi->connection_name, + Sql_condition::WARN_LEVEL_ERROR))) + { + if (load_error) + { + /* + We cannot start a slave using GTID if we cannot load the + GTID position from the mysql.gtid_slave_pos table. But we + can allow non-GTID replication (useful eg. during upgrade). + */ + if (mi->using_gtid != Master_info::USE_GTID_NO) + { + mi->release(); + break; + } + else + thd->clear_error(); + } + if (!start_slave(thd, mi, 1 /* net report*/)) + my_ok(thd); + mi->release(); + } 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. + { + LEX_MASTER_INFO *lex_mi; + Master_info *mi; + /* + 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()) + 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; + } + + lex_mi= &thd->lex->mi; + if ((mi= get_master_info(&lex_mi->connection_name, + Sql_condition::WARN_LEVEL_ERROR))) + { + if (stop_slave(thd, mi, 1/* net report*/)) + res= 1; + mi->release(); + if (rpl_parallel_resize_pool_if_no_slaves()) + res= 1; + if (!res) + my_ok(thd); + } + break; + } + case SQLCOM_SLAVE_ALL_START: { - my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, - ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); - goto error; + mysql_mutex_lock(&LOCK_active_mi); + if (master_info_index && !master_info_index->start_all_slaves(thd)) + my_ok(thd); + mysql_mutex_unlock(&LOCK_active_mi); + break; } + case SQLCOM_SLAVE_ALL_STOP: { + 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); - stop_slave(thd,active_mi,1/* net report*/); + if (master_info_index && !master_info_index->stop_all_slaves(thd)) + my_ok(thd); mysql_mutex_unlock(&LOCK_active_mi); break; } #endif /* HAVE_REPLICATION */ - case SQLCOM_RENAME_TABLE: { if (execute_rename_table(thd, first_table, all_tables)) @@ -2775,6 +3244,13 @@ end_with_restore_list: 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. @@ -2953,6 +3429,18 @@ end_with_restore_list: 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; @@ -2994,6 +3482,7 @@ end_with_restore_list: case SQLCOM_INSERT_SELECT: { select_result *sel_result; + bool explain= MY_TEST(lex->describe); DBUG_ASSERT(first_table == all_tables && first_table != 0); if ((res= insert_precheck(thd, all_tables))) break; @@ -3052,7 +3541,10 @@ end_with_restore_list: lex->duplicates, lex->ignore))) { - res= handle_select(thd, lex, sel_result, OPTION_SETUP_TABLES_DONE); + if (explain) + res= mysql_explain_union(thd, &thd->lex->unit, sel_result); + else + res= handle_select(thd, lex, sel_result, OPTION_SETUP_TABLES_DONE); /* Invalidate the table in the query cache if something changed after unlocking when changes become visible. @@ -3068,8 +3560,22 @@ end_with_restore_list: query_cache_invalidate3(thd, first_table, 1); first_table->next_local= save_table; } + if (explain) + { + /* + sel_result needs to be cleaned up properly. + INSERT... SELECT statement will call either send_eof() or + abort_result_set(). EXPLAIN doesn't call either, so we need + to cleanup manually. + */ + sel_result->abort_result_set(); + } delete sel_result; } + + if (!res && explain) + res= thd->lex->explain->send_explain(thd); + /* revert changes for SP */ MYSQL_INSERT_SELECT_DONE(res, (ulong) thd->get_row_count_func()); select_lex->table_list.first= first_table; @@ -3088,6 +3594,7 @@ end_with_restore_list: } case SQLCOM_DELETE: { + select_result *sel_result=lex->result; DBUG_ASSERT(first_table == all_tables && first_table != 0); if ((res= delete_precheck(thd, all_tables))) break; @@ -3095,9 +3602,13 @@ end_with_restore_list: 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); + if (!(sel_result= lex->result) && !(sel_result= new select_send())) + return 1; + res = mysql_delete(thd, all_tables, + select_lex->where, &select_lex->order_list, + unit->select_limit_cnt, select_lex->options, + sel_result); + delete sel_result; MYSQL_DELETE_DONE(res, (ulong) thd->get_row_count_func()); break; } @@ -3105,7 +3616,8 @@ end_with_restore_list: { DBUG_ASSERT(first_table == all_tables && first_table != 0); TABLE_LIST *aux_tables= thd->lex->auxiliary_table_list.first; - multi_delete *del_result; + bool explain= MY_TEST(lex->describe); + multi_delete *result; if ((res= multi_delete_precheck(thd, all_tables))) break; @@ -3116,7 +3628,7 @@ end_with_restore_list: if (add_item_to_list(thd, new Item_null())) goto error; - thd_proc_info(thd, "init"); + THD_STAGE_INFO(thd, stage_init); if ((res= open_and_lock_tables(thd, all_tables, TRUE, 0))) break; @@ -3127,25 +3639,34 @@ end_with_restore_list: goto error; } - if (!thd->is_fatal_error && - (del_result= new multi_delete(aux_tables, lex->table_count))) - { - res= mysql_select(thd, &select_lex->ref_pointer_array, - select_lex->get_table_list(), - select_lex->with_wild, - select_lex->item_list, - select_lex->where, - 0, (ORDER *)NULL, (ORDER *)NULL, (Item *)NULL, - (ORDER *)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(); - MYSQL_MULTI_DELETE_DONE(res, del_result->num_deleted()); - if (res) - del_result->abort_result_set(); - delete del_result; + if (!thd->is_fatal_error) + { + result= new multi_delete(aux_tables, lex->table_count); + if (result) + { + res= mysql_select(thd, &select_lex->ref_pointer_array, + select_lex->get_table_list(), + select_lex->with_wild, + select_lex->item_list, + select_lex->where, + 0, (ORDER *)NULL, (ORDER *)NULL, (Item *)NULL, + (ORDER *)NULL, + (select_lex->options | thd->variables.option_bits | + SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK | + OPTION_SETUP_TABLES_DONE) & ~OPTION_BUFFER_RESULT, + result, unit, select_lex); + res|= thd->is_error(); + + MYSQL_MULTI_DELETE_DONE(res, result->num_deleted()); + if (res) + result->abort_result_set(); /* for both DELETE and EXPLAIN DELETE */ + else + { + if (explain) + res= thd->lex->explain->send_explain(thd); + } + delete result; + } } else { @@ -3167,8 +3688,18 @@ end_with_restore_list: /* So that DROP TEMPORARY TABLE gets to binlog at commit/rollback */ thd->variables.option_bits|= OPTION_KEEP_LOG; } + /* + If we are a slave, we should add IF EXISTS if the query executed + on the master without an error. This will help a slave to + recover from multi-table DROP TABLE that was aborted in the + middle. + */ + if (thd->slave_thread && !thd->slave_expected_error && + slave_ddl_exec_mode_options == SLAVE_EXEC_MODE_IDEMPOTENT) + lex->check_exists= 1; + /* DDL and binlog write order are protected by metadata locks. */ - res= mysql_rm_table(thd, first_table, lex->drop_if_exists, + res= mysql_rm_table(thd, first_table, lex->check_exists, lex->drop_temporary); } break; @@ -3249,11 +3780,6 @@ end_with_restore_list: 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 @@ -3299,8 +3825,20 @@ end_with_restore_list: thd->mdl_context.release_transactional_locks(); if (res) goto error; - if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables, - FALSE, UINT_MAX, FALSE)) + + /* + Here we have to pre-open temporary tables for LOCK TABLES. + + CF_PREOPEN_TMP_TABLES is not set for this SQL statement simply + because LOCK TABLES calls close_thread_tables() as a first thing + (it's called from unlock_locked_tables() above). So even if + CF_PREOPEN_TMP_TABLES was set and the tables would be pre-opened + in a usual way, they would have been closed. + */ + if (open_temporary_tables(thd, all_tables)) + goto error; + + if (lock_tables_precheck(thd, all_tables)) goto error; thd->variables.option_bits|= OPTION_TABLE_LOCK; @@ -3328,9 +3866,7 @@ end_with_restore_list: prepared statement- safe. */ HA_CREATE_INFO create_info(lex->create_info); - char *alias; - if (!(alias=thd->strmake(lex->name.str, lex->name.length)) || - check_db_name(&lex->name)) + if (check_db_name(&lex->name)) { my_error(ER_WRONG_DB_NAME, MYF(0), lex->name.str); break; @@ -3343,18 +3879,23 @@ end_with_restore_list: above was not called. So we have to check rules again here. */ #ifdef HAVE_REPLICATION - if (thd->slave_thread && - (!rpl_filter->db_ok(lex->name.str) || - !rpl_filter->db_ok_with_wild_table(lex->name.str))) + if (thd->slave_thread) { - my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); - break; + rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter; + if (!rpl_filter->db_ok(lex->name.str) || + !rpl_filter->db_ok_with_wild_table(lex->name.str)) + { + my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); + break; + } + if (slave_ddl_exec_mode_options == SLAVE_EXEC_MODE_IDEMPOTENT && + !(lex->create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS)) + create_info.options|= HA_LEX_CREATE_IF_NOT_EXISTS; } #endif if (check_access(thd, CREATE_ACL, lex->name.str, NULL, NULL, 1, 0)) break; - res= mysql_create_db(thd,(lower_case_table_names == 2 ? alias : - lex->name.str), &create_info, 0); + res= mysql_create_db(thd, lex->name.str, &create_info, 0); break; } case SQLCOM_DROP_DB: @@ -3372,30 +3913,39 @@ end_with_restore_list: above was not called. So we have to check rules again here. */ #ifdef HAVE_REPLICATION - if (thd->slave_thread && - (!rpl_filter->db_ok(lex->name.str) || - !rpl_filter->db_ok_with_wild_table(lex->name.str))) + if (thd->slave_thread) { - my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); - break; + rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter; + if (!rpl_filter->db_ok(lex->name.str) || + !rpl_filter->db_ok_with_wild_table(lex->name.str)) + { + my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); + break; + } + if (!thd->slave_expected_error && + slave_ddl_exec_mode_options == SLAVE_EXEC_MODE_IDEMPOTENT) + lex->check_exists= 1; } #endif if (check_access(thd, DROP_ACL, lex->name.str, NULL, NULL, 1, 0)) break; - res= mysql_rm_db(thd, lex->name.str, lex->drop_if_exists, 0); + res= mysql_rm_db(thd, lex->name.str, lex->check_exists, 0); break; } case SQLCOM_ALTER_DB_UPGRADE: { LEX_STRING *db= & lex->name; #ifdef HAVE_REPLICATION - if (thd->slave_thread && - (!rpl_filter->db_ok(db->str) || - !rpl_filter->db_ok_with_wild_table(db->str))) + if (thd->slave_thread) { - res= 1; - my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); - break; + rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter; + if (!rpl_filter->db_ok(db->str) || + !rpl_filter->db_ok_with_wild_table(db->str)) + { + res= 1; + my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); + break; + } } #endif if (check_db_name(db)) @@ -3432,12 +3982,15 @@ end_with_restore_list: above was not called. So we have to check rules again here. */ #ifdef HAVE_REPLICATION - if (thd->slave_thread && - (!rpl_filter->db_ok(db->str) || - !rpl_filter->db_ok_with_wild_table(db->str))) + if (thd->slave_thread) { - my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); - break; + rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter; + if (!rpl_filter->db_ok(db->str) || + !rpl_filter->db_ok_with_wild_table(db->str)) + { + my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0)); + break; + } } #endif if (check_access(thd, ALTER_ACL, db->str, NULL, NULL, 1, 0)) @@ -3447,14 +4000,20 @@ end_with_restore_list: } case SQLCOM_SHOW_CREATE_DB: { + char db_name_buff[NAME_LEN+1]; + LEX_STRING db_name; DBUG_EXECUTE_IF("4x_server_emul", my_error(ER_UNKNOWN_ERROR, MYF(0)); goto error;); - if (check_db_name(&lex->name)) + + db_name.str= db_name_buff; + db_name.length= lex->name.length; + strmov(db_name.str, lex->name.str); + if (check_db_name(&db_name)) { - my_error(ER_WRONG_DB_NAME, MYF(0), lex->name.str); + my_error(ER_WRONG_DB_NAME, MYF(0), db_name.str); break; } - res= mysqld_show_create_db(thd, lex->name.str, &lex->create_info); + res= mysqld_show_create_db(thd, &db_name, &lex->name, &lex->create_info); break; } case SQLCOM_CREATE_EVENT: @@ -3510,7 +4069,7 @@ end_with_restore_list: case SQLCOM_DROP_EVENT: if (!(res= Events::drop_event(thd, lex->spname->m_db, lex->spname->m_name, - lex->drop_if_exists))) + lex->check_exists))) my_ok(thd); break; #else @@ -3532,22 +4091,26 @@ end_with_restore_list: } #ifndef NO_EMBEDDED_ACCESS_CHECKS case SQLCOM_CREATE_USER: + case SQLCOM_CREATE_ROLE: { if (check_access(thd, INSERT_ACL, "mysql", NULL, NULL, 1, 1) && check_global_access(thd,CREATE_USER_ACL)) break; /* Conditionally writes to binlog */ - if (!(res= mysql_create_user(thd, lex->users_list))) + if (!(res= mysql_create_user(thd, lex->users_list, + lex->sql_command == SQLCOM_CREATE_ROLE))) my_ok(thd); break; } case SQLCOM_DROP_USER: + case SQLCOM_DROP_ROLE: { if (check_access(thd, DELETE_ACL, "mysql", NULL, NULL, 1, 1) && check_global_access(thd,CREATE_USER_ACL)) break; /* Conditionally writes to binlog */ - if (!(res= mysql_drop_user(thd, lex->users_list))) + if (!(res= mysql_drop_user(thd, lex->users_list, + lex->sql_command == SQLCOM_DROP_ROLE))) my_ok(thd); break; } @@ -3567,9 +4130,6 @@ end_with_restore_list: check_global_access(thd,CREATE_USER_ACL)) break; - /* Replicate current user as grantor */ - thd->binlog_invoker(); - /* Conditionally writes to binlog */ if (!(res = mysql_revoke_all(thd, lex->users_list))) my_ok(thd); @@ -3587,42 +4147,58 @@ end_with_restore_list: goto error; /* Replicate current user as grantor */ - thd->binlog_invoker(); + thd->binlog_invoker(false); if (thd->security_ctx->user) // If not replication { - LEX_USER *user, *tmp_user; + LEX_USER *user; bool first_user= TRUE; List_iterator <LEX_USER> user_list(lex->users_list); - while ((tmp_user= user_list++)) + while ((user= user_list++)) { - if (!(user= get_current_user(thd, tmp_user))) - goto error; if (specialflag & SPECIAL_NO_RESOLVE && hostname_requires_resolving(user->host.str)) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_HOSTNAME_WONT_WORK, ER(ER_WARN_HOSTNAME_WONT_WORK)); - // Are we trying to change a password of another user - DBUG_ASSERT(user->host.str != 0); /* GRANT/REVOKE PROXY has the target user as a first entry in the list. */ if (lex->type == TYPE_ENUM_PROXY && first_user) { + if (!(user= get_current_user(thd, user)) || !user->host.str) + goto error; + first_user= FALSE; if (acl_check_proxy_grant_access (thd, user->host.str, user->user.str, lex->grant & GRANT_ACL)) goto error; } - else if (is_acl_user(user->host.str, user->user.str) && - user->password.str && - check_change_password (thd, user->host.str, user->user.str, - user->password.str, - user->password.length)) - goto error; + else if (user->password.str) + { + // Are we trying to change a password of another user? + const char *hostname= user->host.str, *username=user->user.str; + bool userok; + if (username == current_user.str) + { + username= thd->security_ctx->priv_user; + hostname= thd->security_ctx->priv_host; + userok= true; + } + else + { + if (!hostname) + hostname= host_not_specified.str; + userok= is_acl_user(hostname, username); + } + + if (userok && check_change_password (thd, hostname, username, + user->password.str, + user->password.length)) + goto error; + } } } if (first_table) @@ -3666,9 +4242,9 @@ end_with_restore_list: else { /* Conditionally writes to binlog */ - res = mysql_grant(thd, select_lex->db, lex->users_list, lex->grant, - lex->sql_command == SQLCOM_REVOKE, - lex->type == TYPE_ENUM_PROXY); + res= mysql_grant(thd, select_lex->db, lex->users_list, lex->grant, + lex->sql_command == SQLCOM_REVOKE, + lex->type == TYPE_ENUM_PROXY); } if (!res) { @@ -3687,6 +4263,14 @@ end_with_restore_list: } break; } + case SQLCOM_REVOKE_ROLE: + case SQLCOM_GRANT_ROLE: + { + if (!(res= mysql_grant_role(thd, lex->users_list, + lex->sql_command != SQLCOM_GRANT_ROLE))) + my_ok(thd); + break; + } #endif /*!NO_EMBEDDED_ACCESS_CHECKS*/ case SQLCOM_RESET: /* @@ -3701,18 +4285,33 @@ end_with_restore_list: if (check_global_access(thd,RELOAD_ACL)) goto error; - if (first_table && lex->type & REFRESH_READ_LOCK) + if (first_table && lex->type & (REFRESH_READ_LOCK|REFRESH_FOR_EXPORT)) { /* Check table-level privileges. */ if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; + if (flush_tables_with_read_lock(thd, all_tables)) goto error; + my_ok(thd); break; } +#ifdef HAVE_REPLICATION + if (lex->type & REFRESH_READ_LOCK) + { + /* + We need to pause any parallel replication slave workers during FLUSH + TABLES WITH READ LOCK. Otherwise we might cause a deadlock, as + worker threads eun run in arbitrary order but need to commit in a + specific given order. + */ + if (rpl_pause_for_ftwrl(thd)) + goto error; + } +#endif /* reload_acl_and_cache() will tell us if we are allowed to write to the binlog or not. @@ -3738,11 +4337,17 @@ end_with_restore_list: reload_acl_and_cache binlog interactions failed */ res= 1; - } + } if (!res) my_ok(thd); } + else + res= 1; // reload_acl_and_cache failed +#ifdef HAVE_REPLICATION + if (lex->type & REFRESH_READ_LOCK) + rpl_unpause_after_ftwrl(thd); +#endif break; } @@ -3755,7 +4360,7 @@ end_with_restore_list: break; } - if (lex->kill_type == KILL_TYPE_ID) + if (lex->kill_type == KILL_TYPE_ID || lex->kill_type == KILL_TYPE_QUERY) { Item *it= (Item *)lex->value_list.head(); if ((!it->fixed && it->fix_fields(lex->thd, &it)) || it->check_cols(1)) @@ -3764,25 +4369,32 @@ end_with_restore_list: MYF(0)); goto error; } - sql_kill(thd, (ulong) it->val_int(), lex->kill_signal); + sql_kill(thd, it->val_int(), lex->kill_signal, lex->kill_type); } else sql_kill_user(thd, get_current_user(thd, lex->users_list.head()), lex->kill_signal); break; } + case SQLCOM_SHUTDOWN: +#ifndef EMBEDDED_LIBRARY + if (check_global_access(thd,SHUTDOWN_ACL)) + goto error; + kill_mysql(); + my_ok(thd); +#else + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "embedded server"); +#endif + break; + #ifndef NO_EMBEDDED_ACCESS_CHECKS case SQLCOM_SHOW_GRANTS: { - LEX_USER *grant_user= get_current_user(thd, lex->grant_user); + LEX_USER *grant_user= lex->grant_user; if (!grant_user) goto error; - if ((thd->security_ctx->priv_user && - !strcmp(thd->security_ctx->priv_user, grant_user->user.str)) || - !check_access(thd, SELECT_ACL, "mysql", NULL, NULL, 1, 0)) - { - res = mysql_show_grants(thd, grant_user); - } + + res = mysql_show_grants(thd, grant_user); break; } #endif @@ -3790,6 +4402,9 @@ end_with_restore_list: DBUG_ASSERT(first_table == all_tables && first_table != 0); if (check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; + /* Close temporary tables which were pre-opened for privilege checking. */ + close_thread_tables(thd); + all_tables->table= NULL; res= mysql_ha_open(thd, first_table, 0); break; case SQLCOM_HA_CLOSE: @@ -3810,6 +4425,7 @@ end_with_restore_list: break; case SQLCOM_BEGIN: + DBUG_PRINT("info", ("Executing SQLCOM_BEGIN thd: %p", thd)); if (trans_begin(thd, lex->start_transaction_opt)) goto error; my_ok(thd); @@ -3831,12 +4447,13 @@ end_with_restore_list: if (tx_chain) { if (trans_begin(thd)) - goto error; + goto error; } else { - /* Reset the isolation level if no chaining transaction. */ + /* Reset the isolation level and access mode if no chaining transaction.*/ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; } /* Disconnect the current client connection. */ if (tx_release) @@ -3857,6 +4474,7 @@ end_with_restore_list: bool tx_release= (lex->tx_release == TVL_YES || (thd->variables.completion_type == 2 && lex->tx_release != TVL_NO)); + if (trans_rollback(thd)) goto error; thd->mdl_context.release_transactional_locks(); @@ -3868,8 +4486,9 @@ end_with_restore_list: } else { - /* Reset the isolation level if no chaining transaction. */ + /* Reset the isolation level and access mode if no chaining transaction.*/ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; } /* Disconnect the current client connection. */ if (tx_release) @@ -3911,6 +4530,10 @@ end_with_restore_list: goto create_sp_error; } + if (check_access(thd, CREATE_PROC_ACL, lex->sphead->m_db.str, + NULL, NULL, 0, 0)) + goto create_sp_error; + /* Check that a database directory with this name exists. Design note: This won't work on virtual databases @@ -3922,10 +4545,6 @@ end_with_restore_list: goto create_sp_error; } - if (check_access(thd, CREATE_PROC_ACL, lex->sphead->m_db.str, - NULL, NULL, 0, 0)) - goto create_sp_error; - name= lex->sphead->name(&namelen); #ifdef HAVE_DLOPEN if (lex->sphead->m_type == TYPE_ENUM_FUNCTION) @@ -3995,7 +4614,7 @@ end_with_restore_list: { if (sp_grant_privileges(thd, lex->sphead->m_db.str, name, lex->sql_command == SQLCOM_CREATE_PROCEDURE)) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_PROC_AUTO_GRANT_FAIL, ER(ER_PROC_AUTO_GRANT_FAIL)); thd->clear_error(); } @@ -4051,6 +4670,10 @@ create_sp_error: open_and_lock_tables(thd, all_tables, TRUE, 0)) goto error; + if (check_routine_access(thd, EXECUTE_ACL, lex->spname->m_db.str, + lex->spname->m_name.str, TRUE, FALSE)) + goto error; + /* By this moment all needed SPs should be in cache so no need to look into DB. @@ -4101,11 +4724,6 @@ create_sp_error: SERVER_MORE_RESULTS_EXISTS); thd->server_status|= SERVER_MORE_RESULTS_EXISTS; - if (check_routine_access(thd, EXECUTE_ACL, - sp->m_db.str, sp->m_name.str, TRUE, FALSE)) - { - goto error; - } select_limit= thd->variables.select_limit; thd->variables.select_limit= HA_POS_ERROR; @@ -4199,9 +4817,9 @@ create_sp_error: if (lex->spname->m_db.str == NULL) { - if (lex->drop_if_exists) + if (lex->check_exists) { - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST), "FUNCTION (UDF)", lex->spname->m_name.str); res= FALSE; @@ -4254,7 +4872,7 @@ create_sp_error: sp_revoke_privileges(thd, db, name, lex->sql_command == SQLCOM_DROP_PROCEDURE)) { - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_PROC_AUTO_REVOKE_FAIL, ER(ER_PROC_AUTO_REVOKE_FAIL)); /* If this happens, an error should have been reported. */ @@ -4268,10 +4886,10 @@ create_sp_error: my_ok(thd); break; case SP_KEY_NOT_FOUND: - if (lex->drop_if_exists) + if (lex->check_exists) { res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST), SP_COM_STRING(lex), lex->spname->m_qname.str); if (!res) @@ -4385,9 +5003,10 @@ create_sp_error: thd->mdl_context.release_transactional_locks(); /* We've just done a commit, reset transaction - isolation level to the session default. + isolation level and access mode to the session default. */ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; my_ok(thd); break; case SQLCOM_XA_ROLLBACK: @@ -4396,9 +5015,10 @@ create_sp_error: thd->mdl_context.release_transactional_locks(); /* We've just done a rollback, reset transaction - isolation level to the session default. + isolation level and access mode to the session default. */ thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + thd->tx_read_only= thd->variables.tx_read_only; my_ok(thd); break; case SQLCOM_XA_RECOVER: @@ -4478,7 +5098,7 @@ create_sp_error: if ((err_code= drop_server(thd, &lex->server_options))) { - if (! lex->drop_if_exists && err_code == ER_FOREIGN_SERVER_DOESNT_EXIST) + if (! lex->check_exists && err_code == ER_FOREIGN_SERVER_DOESNT_EXIST) { DBUG_PRINT("info", ("problem dropping server %s", lex->server_options.server_name)); @@ -4504,32 +5124,21 @@ create_sp_error: /* fall through */ case SQLCOM_SIGNAL: case SQLCOM_RESIGNAL: - DBUG_ASSERT(lex->m_stmt != NULL); - res= lex->m_stmt->execute(thd); + case SQLCOM_GET_DIAGNOSTICS: + DBUG_ASSERT(lex->m_sql_cmd != NULL); + res= lex->m_sql_cmd->execute(thd); break; default: + #ifndef EMBEDDED_LIBRARY DBUG_ASSERT(0); /* Impossible */ #endif my_ok(thd); break; } - thd_proc_info(thd, "query end"); + THD_STAGE_INFO(thd, stage_query_end); thd->update_stats(); - /* - Binlog-related cleanup: - Reset system variables temporarily modified by SET ONE SHOT. - - Exception: If this is a SET, do nothing. This is to allow - mysqlbinlog to print many SET commands (in this case we want the - charset temp setting to live until the real query). This is also - needed so that SET CHARACTER_SET_CLIENT... does not cancel itself - immediately. - */ - if (thd->one_shot_set && lex->sql_command != SQLCOM_SET_OPTION) - reset_one_shot_variables(thd); - goto finish; error: @@ -4542,6 +5151,10 @@ finish: lex->unit.cleanup(); + /* close/reopen tables that were marked to need reopen under LOCK TABLES */ + if (! thd->lex->requires_prelocking()) + thd->locked_tables_list.reopen_tables(thd, true); + if (! thd->in_sub_stmt) { if (thd->killed != NOT_KILLED) @@ -4550,23 +5163,19 @@ finish: if (thd->killed_errno()) { /* If we already sent 'ok', we can ignore any kill query statements */ - if (! thd->stmt_da->is_set()) + if (! thd->get_stmt_da()->is_set()) thd->send_kill_message(); } - if (thd->killed < KILL_CONNECTION) - { - thd->reset_killed(); - thd->mysys_var->abort= 0; - } + thd->reset_kill_query(); } if (thd->is_error() || (thd->variables.option_bits & OPTION_MASTER_SQL_ERROR)) trans_rollback_stmt(thd); else { /* If commit fails, we should be able to reset the OK status. */ - thd->stmt_da->can_overwrite_status= TRUE; + thd->get_stmt_da()->set_overwrite_status(true); trans_commit_stmt(thd); - thd->stmt_da->can_overwrite_status= FALSE; + thd->get_stmt_da()->set_overwrite_status(false); } #ifdef WITH_ARIA_STORAGE_ENGINE ha_maria::implicit_commit(thd, FALSE); @@ -4575,12 +5184,16 @@ finish: /* Free tables */ close_thread_tables(thd); - thd_proc_info(thd, 0); #ifndef DBUG_OFF if (lex->sql_command != SQLCOM_SET_OPTION && ! thd->in_sub_stmt) DEBUG_SYNC(thd, "execute_command_after_close_tables"); #endif + if (!(sql_command_flags[lex->sql_command] & + (CF_CAN_GENERATE_ROW_EVENTS | CF_FORCE_ORIGINAL_BINLOG_FORMAT | + CF_STATUS_COMMAND))) + thd->set_binlog_format(orig_binlog_format, + orig_current_stmt_binlog_format); if (! thd->in_sub_stmt && thd->transaction_rollback_request) { @@ -4596,12 +5209,15 @@ finish: { /* No transaction control allowed in sub-statements. */ DBUG_ASSERT(! thd->in_sub_stmt); - /* If commit fails, we should be able to reset the OK status. */ - thd->stmt_da->can_overwrite_status= TRUE; - /* Commit the normal transaction if one is active. */ - trans_commit_implicit(thd); - thd->stmt_da->can_overwrite_status= FALSE; - thd->mdl_context.release_transactional_locks(); + if (!(thd->variables.option_bits & OPTION_GTID_BEGIN)) + { + /* If commit fails, we should be able to reset the OK status. */ + thd->get_stmt_da()->set_overwrite_status(true); + /* Commit the normal transaction if one is active. */ + trans_commit_implicit(thd); + thd->get_stmt_da()->set_overwrite_status(false); + thd->mdl_context.release_transactional_locks(); + } } else if (! thd->in_sub_stmt && ! thd->in_multi_stmt_transaction_mode()) { @@ -4651,24 +5267,37 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) if (!(result= new select_send())) return 1; /* purecov: inspected */ thd->send_explain_fields(result); - res= mysql_explain_union(thd, &thd->lex->unit, result); + /* - The code which prints the extended description is not robust - against malformed queries, so skip it if we have an error. + This will call optimize() for all parts of query. The query plan is + printed out below. */ - if (!res && (lex->describe & DESCRIBE_EXTENDED)) + res= mysql_explain_union(thd, &thd->lex->unit, result); + + /* Print EXPLAIN only if we don't have an error */ + if (!res) { - char buff[1024]; - String str(buff,(uint32) sizeof(buff), system_charset_info); - str.length(0); - /* - The warnings system requires input in utf8, @see - mysqld_show_warnings(). - */ - thd->lex->unit.print(&str, QT_TO_SYSTEM_CHARSET); - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_YES, str.c_ptr_safe()); + /* + Do like the original select_describe did: remove OFFSET from the + top-level LIMIT + */ + result->reset_offset_limit(); + thd->lex->explain->print_explain(result, thd->lex->describe); + if (lex->describe & DESCRIBE_EXTENDED) + { + char buff[1024]; + String str(buff,(uint32) sizeof(buff), system_charset_info); + str.length(0); + /* + The warnings system requires input in utf8, @see + mysqld_show_warnings(). + */ + thd->lex->unit.print(&str, QT_TO_SYSTEM_CHARSET); + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_YES, str.c_ptr_safe()); + } } + if (res) result->abort_result_set(); else @@ -4686,10 +5315,10 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) } } /* Count number of empty select queries */ - if (!thd->sent_row_count) + if (!thd->get_sent_row_count()) status_var_increment(thd->status_var.empty_queries); else - status_var_add(thd->status_var.rows_sent, thd->sent_row_count); + status_var_add(thd->status_var.rows_sent, thd->get_sent_row_count()); return res; } @@ -4712,12 +5341,67 @@ static bool execute_show_status(THD *thd, TABLE_LIST *all_tables) mysql_mutex_lock(&LOCK_status); add_diff_to_status(&global_status_var, &thd->status_var, &old_status_var); - thd->status_var= old_status_var; + memcpy(&thd->status_var, &old_status_var, + offsetof(STATUS_VAR, last_cleared_system_status_var)); mysql_mutex_unlock(&LOCK_status); return res; } +/* + Find out if a table is a temporary table + + A table is a temporary table if it's a temporary table or + there has been before a temporary table that has been renamed + to the current name. + + Some examples: + A->B B is a temporary table if and only if A is a temp. + A->B, B->C Second B is temp if A is temp + A->B, A->C Second A can't be temp as if A was temp then B is temp + and Second A can only be a normal table. C is also not temp +*/ + +static TABLE *find_temporary_table_for_rename(THD *thd, + TABLE_LIST *first_table, + TABLE_LIST *cur_table) +{ + TABLE_LIST *table; + TABLE *res= 0; + bool found= 0; + DBUG_ENTER("find_temporary_table_for_rename"); + + /* Find last instance when cur_table is in TO part */ + for (table= first_table; + table != cur_table; + table= table->next_local->next_local) + { + TABLE_LIST *next= table->next_local; + + if (!strcmp(table->get_db_name(), cur_table->get_db_name()) && + !strcmp(table->get_table_name(), cur_table->get_table_name())) + { + /* Table was moved away, can't be same as 'table' */ + found= 1; + res= 0; // Table can't be a temporary table + } + if (!strcmp(next->get_db_name(), cur_table->get_db_name()) && + !strcmp(next->get_table_name(), cur_table->get_table_name())) + { + /* + Table has matching name with new name of this table. cur_table should + have same temporary type as this table. + */ + found= 1; + res= table->table; + } + } + if (!found) + res= find_temporary_table(thd, table); + DBUG_RETURN(res); +} + + static bool execute_rename_table(THD *thd, TABLE_LIST *first_table, TABLE_LIST *all_tables) { @@ -4734,13 +5418,19 @@ static bool execute_rename_table(THD *thd, TABLE_LIST *first_table, &table->next_local->grant.m_internal, 0, 0)) return 1; + + /* check if these are referring to temporary tables */ + table->table= find_temporary_table_for_rename(thd, first_table, table); + table->next_local->table= table->table; + 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 + copy 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) && @@ -4753,100 +5443,6 @@ static bool execute_rename_table(THD *thd, TABLE_LIST *first_table, } -#ifndef NO_EMBEDDED_ACCESS_CHECKS -/** - Check grants for commands which work only with one table. - - @param thd Thread handler - @param privilege requested privilege - @param all_tables global table list of query - @param no_errors FALSE/TRUE - report/don't report error to - the client (using my_error() call). - - @retval - 0 OK - @retval - 1 access denied, error is sent to client -*/ - -bool check_single_table_access(THD *thd, ulong privilege, - TABLE_LIST *all_tables, bool no_errors) -{ - Security_context * backup_ctx= thd->security_ctx; - - /* we need to switch to the saved context (if any) */ - if (all_tables->security_ctx) - thd->security_ctx= all_tables->security_ctx; - - const char *db_name; - if ((all_tables->view || all_tables->field_translation) && - !all_tables->schema_table) - db_name= all_tables->view_db.str; - else - db_name= all_tables->db; - - if (check_access(thd, privilege, db_name, - &all_tables->grant.privilege, - &all_tables->grant.m_internal, - 0, no_errors)) - goto deny; - - /* Show only 1 table for check_grant */ - if (!(all_tables->belong_to_view && - (thd->lex->sql_command == SQLCOM_SHOW_FIELDS)) && - check_grant(thd, privilege, all_tables, FALSE, 1, no_errors)) - goto deny; - - thd->security_ctx= backup_ctx; - return 0; - -deny: - thd->security_ctx= backup_ctx; - return 1; -} - -/** - Check grants for commands which work only with one table and all other - tables belonging to subselects or implicitly opened tables. - - @param thd Thread handler - @param privilege requested privilege - @param all_tables global table list of query - - @retval - 0 OK - @retval - 1 access denied, error is sent to client -*/ - -bool check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *all_tables) -{ - if (check_single_table_access (thd,privilege,all_tables, FALSE)) - return 1; - - /* Check rights on tables of subselects and implictly opened tables */ - TABLE_LIST *subselects_tables, *view= all_tables->view ? all_tables : 0; - if ((subselects_tables= all_tables->next_global)) - { - /* - Access rights asked for the first table of a view should be the same - as for the view - */ - if (view && subselects_tables->belong_to_view == view) - { - if (check_single_table_access (thd, privilege, subselects_tables, FALSE)) - return 1; - subselects_tables= subselects_tables->next_global; - } - if (subselects_tables && - (check_table_access(thd, SELECT_ACL, subselects_tables, FALSE, - UINT_MAX, FALSE))) - return 1; - } - return 0; -} - - /** @brief Compare requested privileges with the privileges acquired from the User- and Db-tables. @@ -4879,6 +5475,11 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, GRANT_INTERNAL_INFO *grant_internal_info, bool dont_check_global_grants, bool no_errors) { +#ifdef NO_EMBEDDED_ACCESS_CHECKS + if (save_priv) + *save_priv= GLOBAL_ACLS; + return false; +#else Security_context *sctx= thd->security_ctx; ulong db_access; @@ -4905,7 +5506,7 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, dummy= 0; } - thd_proc_info(thd, "checking permissions"); + THD_STAGE_INFO(thd, stage_checking_permissions); if ((!db || !db[0]) && !thd->db && !dont_check_global_grants) { DBUG_PRINT("error",("No database")); @@ -4917,6 +5518,10 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, if ((db != NULL) && (db != any_db)) { + /* + Check if this is reserved database, like information schema or + performance schema + */ const ACL_internal_schema_access *access; access= get_cached_schema_access(grant_internal_info, db); if (access) @@ -4959,8 +5564,12 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, if (!(sctx->master_access & SELECT_ACL)) { if (db && (!thd->db || db_is_pattern || strcmp(db, thd->db))) + { db_access= acl_get(sctx->host, sctx->ip, sctx->priv_user, db, db_is_pattern); + if (sctx->priv_role[0]) + db_access|= acl_get("", "", sctx->priv_role, db, db_is_pattern); + } else { /* get access for current db */ @@ -5004,8 +5613,14 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, } if (db && (!thd->db || db_is_pattern || strcmp(db,thd->db))) + { db_access= acl_get(sctx->host, sctx->ip, sctx->priv_user, db, db_is_pattern); + if (sctx->priv_role[0]) + { + db_access|= acl_get("", "", sctx->priv_role, db, db_is_pattern); + } + } else db_access= sctx->db_access; DBUG_PRINT("info",("db_access: %lu want_access: %lu", @@ -5057,6 +5672,101 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, "unknown"))); } DBUG_RETURN(TRUE); +#endif // NO_EMBEDDED_ACCESS_CHECKS +} + + +#ifndef NO_EMBEDDED_ACCESS_CHECKS +/** + Check grants for commands which work only with one table. + + @param thd Thread handler + @param privilege requested privilege + @param all_tables global table list of query + @param no_errors FALSE/TRUE - report/don't report error to + the client (using my_error() call). + + @retval + 0 OK + @retval + 1 access denied, error is sent to client +*/ + +bool check_single_table_access(THD *thd, ulong privilege, + TABLE_LIST *all_tables, bool no_errors) +{ + Security_context * backup_ctx= thd->security_ctx; + + /* we need to switch to the saved context (if any) */ + if (all_tables->security_ctx) + thd->security_ctx= all_tables->security_ctx; + + const char *db_name; + if ((all_tables->view || all_tables->field_translation) && + !all_tables->schema_table) + db_name= all_tables->view_db.str; + else + db_name= all_tables->db; + + if (check_access(thd, privilege, db_name, + &all_tables->grant.privilege, + &all_tables->grant.m_internal, + 0, no_errors)) + goto deny; + + /* Show only 1 table for check_grant */ + if (!(all_tables->belong_to_view && + (thd->lex->sql_command == SQLCOM_SHOW_FIELDS)) && + check_grant(thd, privilege, all_tables, FALSE, 1, no_errors)) + goto deny; + + thd->security_ctx= backup_ctx; + return 0; + +deny: + thd->security_ctx= backup_ctx; + return 1; +} + +/** + Check grants for commands which work only with one table and all other + tables belonging to subselects or implicitly opened tables. + + @param thd Thread handler + @param privilege requested privilege + @param all_tables global table list of query + + @retval + 0 OK + @retval + 1 access denied, error is sent to client +*/ + +bool check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *all_tables) +{ + if (check_single_table_access (thd,privilege,all_tables, FALSE)) + return 1; + + /* Check rights on tables of subselects and implictly opened tables */ + TABLE_LIST *subselects_tables, *view= all_tables->view ? all_tables : 0; + if ((subselects_tables= all_tables->next_global)) + { + /* + Access rights asked for the first table of a view should be the same + as for the view + */ + if (view && subselects_tables->belong_to_view == view) + { + if (check_single_table_access (thd, privilege, subselects_tables, FALSE)) + return 1; + subselects_tables= subselects_tables->next_global; + } + if (subselects_tables && + (check_table_access(thd, SELECT_ACL, subselects_tables, FALSE, + UINT_MAX, FALSE))) + return 1; + } + return 0; } @@ -5114,6 +5824,12 @@ static bool check_show_access(THD *thd, TABLE_LIST *table) DBUG_ASSERT(dst_table); + /* + Open temporary tables to be able to detect them during privilege check. + */ + if (open_temporary_tables(thd, dst_table)) + return TRUE; + if (check_access(thd, SELECT_ACL, dst_table->db, &dst_table->grant.privilege, &dst_table->grant.m_internal, @@ -5127,6 +5843,9 @@ static bool check_show_access(THD *thd, TABLE_LIST *table) if (check_grant(thd, SELECT_ACL, dst_table, TRUE, UINT_MAX, FALSE)) return TRUE; /* Access denied */ + close_thread_tables(thd); + dst_table->table= NULL; + /* Access granted */ return FALSE; } @@ -5215,10 +5934,10 @@ check_table_access(THD *thd, ulong requirements,TABLE_LIST *tables, DBUG_PRINT("info", ("derived: %d view: %d", table_ref->derived != 0, table_ref->view != 0)); - if (table_ref->is_anonymous_derived_table() || - (table_ref->table && table_ref->table->s && - (int)table_ref->table->s->tmp_table)) + + if (table_ref->is_anonymous_derived_table()) continue; + thd->security_ctx= sctx; if (check_access(thd, want_access, table_ref->get_db_name(), @@ -5412,8 +6131,8 @@ bool check_fk_parent_table_access(THD *thd, bool is_qualified_table_name; Foreign_key *fk_key= (Foreign_key *)key; LEX_STRING db_name; - LEX_STRING table_name= { fk_key->ref_table->table.str, - fk_key->ref_table->table.length }; + LEX_STRING table_name= { fk_key->ref_table.str, + fk_key->ref_table.length }; const ulong privileges= (SELECT_ACL | INSERT_ACL | UPDATE_ACL | DELETE_ACL | REFERENCES_ACL); @@ -5425,15 +6144,15 @@ bool check_fk_parent_table_access(THD *thd, return true; } - if (fk_key->ref_table->db.str) + if (fk_key->ref_db.str) { is_qualified_table_name= true; - db_name.str= (char *) thd->memdup(fk_key->ref_table->db.str, - fk_key->ref_table->db.length+1); - db_name.length= fk_key->ref_table->db.length; + db_name.str= (char *) thd->memdup(fk_key->ref_db.str, + fk_key->ref_db.length+1); + db_name.length= fk_key->ref_db.length; // Check if database name is valid or not. - if (fk_key->ref_table->db.str && check_db_name(&db_name)) + if (fk_key->ref_db.str && check_db_name(&db_name)) { my_error(ER_WRONG_DB_NAME, MYF(0), db_name.str); return true; @@ -5465,9 +6184,10 @@ bool check_fk_parent_table_access(THD *thd, // if lower_case_table_names is set then convert tablename to lower case. if (lower_case_table_names) { - table_name.str= (char *) thd->memdup(fk_key->ref_table->table.str, - fk_key->ref_table->table.length+1); + table_name.str= (char *) thd->memdup(fk_key->ref_table.str, + fk_key->ref_table.length+1); table_name.length= my_casedn_str(files_charset_info, table_name.str); + db_name.length = my_casedn_str(files_charset_info, db_name.str); } parent_table.init_one_table(db_name.str, db_name.length, @@ -5514,12 +6234,6 @@ bool check_fk_parent_table_access(THD *thd, ****************************************************************************/ -#if STACK_DIRECTION < 0 -#define used_stack(A,B) (long) (A - B) -#else -#define used_stack(A,B) (long) (B - A) -#endif - #ifndef DBUG_OFF long max_stack_used; #endif @@ -5536,9 +6250,10 @@ bool check_stack_overrun(THD *thd, long margin, { long stack_used; DBUG_ASSERT(thd == current_thd); - if ((stack_used=used_stack(thd->thread_stack,(char*) &stack_used)) >= + if ((stack_used= available_stack_size(thd->thread_stack, &stack_used)) >= (long) (my_thread_stack_size - margin)) { + thd->is_fatal_error= 1; /* Do not use stack for the message buffer to ensure correct behaviour in cases we have close to no stack left. @@ -5553,7 +6268,7 @@ bool check_stack_overrun(THD *thd, long margin, return 1; } #ifndef DBUG_OFF - max_stack_used= max(max_stack_used, stack_used); + max_stack_used= MY_MAX(max_stack_used, stack_used); #endif return 0; } @@ -5621,7 +6336,6 @@ void THD::reset_for_next_command() DBUG_ENTER("THD::reset_for_next_command"); DBUG_ASSERT(!thd->spcont); /* not for substatements of routines */ DBUG_ASSERT(! thd->in_sub_stmt); - DBUG_ASSERT(thd->transaction.on); thd->free_list= 0; thd->select_number= 1; /* @@ -5634,6 +6348,8 @@ void THD::reset_for_next_command() thd->query_start_used= 0; thd->query_start_sec_part_used= 0; thd->is_fatal_error= thd->time_zone_used= 0; + thd->log_current_statement= 0; + /* Clear the status flag that are expected to be cleared at the beginning of each SQL statement. @@ -5658,10 +6374,10 @@ void THD::reset_for_next_command() thd->user_var_events_alloc= thd->mem_root; } thd->clear_error(); - thd->stmt_da->reset_diagnostics_area(); - thd->warning_info->reset_for_next_command(); + thd->get_stmt_da()->reset_diagnostics_area(); + thd->get_stmt_da()->reset_for_next_command(); thd->rand_used= 0; - thd->sent_row_count= thd->examined_row_count= 0; + thd->m_sent_row_count= thd->m_examined_row_count= 0; thd->accessed_rows_and_keys= 0; thd->query_plan_flags= QPLAN_INIT; @@ -5880,10 +6596,14 @@ void mysql_parse(THD *thd, char *rawbuf, uint length, { LEX *lex= thd->lex; - bool err= parse_sql(thd, parser_state, NULL); + bool err= parse_sql(thd, parser_state, NULL, true); if (!err) { + thd->m_statement_psi= + MYSQL_REFINE_STATEMENT(thd->m_statement_psi, + sql_statement_info[thd->lex->sql_command]. + m_key); #ifndef NO_EMBEDDED_ACCESS_CHECKS if (mqh_used && thd->user_connect && check_mqh(thd, lex->sql_command)) @@ -5932,13 +6652,17 @@ void mysql_parse(THD *thd, char *rawbuf, uint length, } else { + /* Instrument this broken statement as "statement/sql/error" */ + thd->m_statement_psi= + MYSQL_REFINE_STATEMENT(thd->m_statement_psi, + sql_statement_info[SQLCOM_END].m_key); DBUG_ASSERT(thd->is_error()); DBUG_PRINT("info",("Command aborted. Fatal_error: %d", thd->is_fatal_error)); query_cache_abort(&thd->query_cache_tls); } - thd_proc_info(thd, "freeing items"); + THD_STAGE_INFO(thd, stage_freeing_items); sp_cache_enforce_limit(thd->sp_proc_cache, stored_program_cache_size); sp_cache_enforce_limit(thd->sp_func_cache, stored_program_cache_size); thd->end_statement(); @@ -5949,6 +6673,9 @@ void mysql_parse(THD *thd, char *rawbuf, uint length, { /* Update statistics for getting the query from the cache */ thd->lex->sql_command= SQLCOM_SELECT; + thd->m_statement_psi= + MYSQL_REFINE_STATEMENT(thd->m_statement_psi, + sql_statement_info[SQLCOM_SELECT].m_key); status_var_increment(thd->status_var.com_stat[SQLCOM_SELECT]); thd->update_stats(); } @@ -5979,7 +6706,7 @@ bool mysql_test_parse_for_slave(THD *thd, char *rawbuf, uint length) lex_start(thd); mysql_reset_thd_for_next_command(thd); - if (!parse_sql(thd, & parser_state, NULL) && + if (!parse_sql(thd, & parser_state, NULL, true) && all_tables_not_ok(thd, lex->select_lex.table_list.first)) error= 1; /* Ignore question */ thd->end_statement(); @@ -6011,6 +6738,7 @@ bool add_field_to_list(THD *thd, LEX_STRING *field_name, enum_field_types type, { register Create_field *new_field; LEX *lex= thd->lex; + uint8 datetime_precision= length ? atoi(length) : 0; DBUG_ENTER("add_field_to_list"); if (check_ident_length(field_name)) @@ -6022,7 +6750,7 @@ bool add_field_to_list(THD *thd, LEX_STRING *field_name, enum_field_types type, lex->col_list.push_back(new Key_part_spec(*field_name, 0)); key= new Key(Key::PRIMARY, null_lex_str, &default_key_create_info, - 0, lex->col_list, NULL); + 0, lex->col_list, NULL, lex->check_exists); lex->alter_info.key_list.push_back(key); lex->col_list.empty(); } @@ -6032,7 +6760,7 @@ bool add_field_to_list(THD *thd, LEX_STRING *field_name, enum_field_types type, lex->col_list.push_back(new Key_part_spec(*field_name, 0)); key= new Key(Key::UNIQUE, null_lex_str, &default_key_create_info, 0, - lex->col_list, NULL); + lex->col_list, NULL, lex->check_exists); lex->alter_info.key_list.push_back(key); lex->col_list.empty(); } @@ -6044,11 +6772,13 @@ bool add_field_to_list(THD *thd, LEX_STRING *field_name, enum_field_types type, no need fix_fields() We allow only one function as part of default value - - NOW() as default for TIMESTAMP type. + NOW() as default for TIMESTAMP and DATETIME type. */ if (default_value->type() == Item::FUNC_ITEM && - !(((Item_func*)default_value)->functype() == Item_func::NOW_FUNC && - type == MYSQL_TYPE_TIMESTAMP)) + (static_cast<Item_func*>(default_value)->functype() != + Item_func::NOW_FUNC || + (mysql_type_to_time_type(type) != MYSQL_TIMESTAMP_DATETIME) || + default_value->decimals < datetime_precision)) { my_error(ER_INVALID_DEFAULT, MYF(0), field_name->str); DBUG_RETURN(1); @@ -6070,7 +6800,9 @@ bool add_field_to_list(THD *thd, LEX_STRING *field_name, enum_field_types type, } } - if (on_update_value && type != MYSQL_TYPE_TIMESTAMP) + if (on_update_value && + (mysql_type_to_time_type(type) != MYSQL_TIMESTAMP_DATETIME || + on_update_value->decimals < datetime_precision)) { my_error(ER_INVALID_ON_UPDATE, MYF(0), field_name->str); DBUG_RETURN(1); @@ -6080,7 +6812,7 @@ bool add_field_to_list(THD *thd, LEX_STRING *field_name, enum_field_types type, new_field->init(thd, field_name->str, type, length, decimals, type_modifier, default_value, on_update_value, comment, change, interval_list, cs, uint_geom_type, vcol_info, - create_options)) + create_options, lex->check_exists)) DBUG_RETURN(1); lex->alter_info.create_list.push_back(new_field); @@ -6129,6 +6861,7 @@ bool add_to_list(THD *thd, SQL_I_List<ORDER> &list, Item *item,bool asc) order->free_me=0; order->used=0; order->counter_used= 0; + order->fast_field_copier_setup= 0; list.link_in_list(order, &order->next); DBUG_RETURN(0); } @@ -6161,6 +6894,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, thr_lock_type lock_type, enum_mdl_type mdl_type, List<Index_hint> *index_hints_arg, + List<String> *partition_names, LEX_STRING *option) { register TABLE_LIST *ptr; @@ -6173,7 +6907,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, if (!table) DBUG_RETURN(0); // End of memory alias_str= alias ? alias->str : table->table.str; - if (!test(table_options & TL_OPTION_ALIAS) && + if (!MY_TEST(table_options & TL_OPTION_ALIAS) && check_table_name(table->table.str, table->table.length, FALSE)) { my_error(ER_WRONG_TABLE_NAME, MYF(0), table->table.str); @@ -6213,15 +6947,21 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->alias= alias_str; ptr->is_alias= alias ? TRUE : FALSE; - if (lower_case_table_names && table->table.length) - table->table.length= my_casedn_str(files_charset_info, table->table.str); + if (lower_case_table_names) + { + if (table->table.length) + table->table.length= my_casedn_str(files_charset_info, table->table.str); + if (ptr->db_length && ptr->db != any_db) + ptr->db_length= my_casedn_str(files_charset_info, ptr->db); + } + ptr->table_name=table->table.str; ptr->table_name_length=table->table.length; ptr->lock_type= lock_type; - ptr->updating= test(table_options & TL_OPTION_UPDATING); + ptr->updating= MY_TEST(table_options & TL_OPTION_UPDATING); /* TODO: remove TL_OPTION_FORCE_INDEX as it looks like it's not used */ - ptr->force_index= test(table_options & TL_OPTION_FORCE_INDEX); - ptr->ignore_leaves= test(table_options & TL_OPTION_IGNORE_LEAVES); + ptr->force_index= MY_TEST(table_options & TL_OPTION_FORCE_INDEX); + ptr->ignore_leaves= MY_TEST(table_options & TL_OPTION_IGNORE_LEAVES); ptr->derived= table->sel; if (!ptr->derived && is_infoschema_db(ptr->db, ptr->db_length)) { @@ -6309,11 +7049,14 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, */ table_list.link_in_list(ptr, &ptr->next_local); ptr->next_name_resolution_table= NULL; +#ifdef WITH_PARTITION_STORAGE_ENGINE + ptr->partition_names= partition_names; +#endif /* WITH_PARTITION_STORAGE_ENGINE */ /* Link table in global list (all used tables) */ lex->add_to_query_tables(ptr); // Pure table aliases do not need to be locked: - if (!test(table_options & TL_OPTION_ALIAS)) + if (!MY_TEST(table_options & TL_OPTION_ALIAS)) { ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name, mdl_type, MDL_TRANSACTION); @@ -6924,7 +7667,7 @@ bool st_select_lex_unit::add_fake_select_lex(THD *thd_arg) @retval FALSE if all is OK @retval - TRUE if a memory allocation error occured + TRUE if a memory allocation error occurred */ bool @@ -7042,37 +7785,56 @@ void add_join_natural(TABLE_LIST *a, TABLE_LIST *b, List<String> *using_fields, /** - kill on thread. + Find a thread by id and return it, locking it LOCK_thd_data - @param thd Thread class - @param id Thread id - @param only_kill_query Should it kill the query or the connection + @param id Identifier of the thread we're looking for + @param query_id If true, search by query_id instead of thread_id - @note - This is written such that we have a short lock on LOCK_thread_count + @return NULL - not found + pointer - thread found, and its LOCK_thd_data is locked. */ -uint kill_one_thread(THD *thd, ulong id, killed_state kill_signal) +THD *find_thread_by_id(longlong id, bool query_id) { THD *tmp; - uint error=ER_NO_SUCH_THREAD; - DBUG_ENTER("kill_one_thread"); - DBUG_PRINT("enter", ("id: %lu signal: %u", id, (uint) kill_signal)); - mysql_mutex_lock(&LOCK_thread_count); // For unlink from list I_List_iterator<THD> it(threads); while ((tmp=it++)) { - if (tmp->command == COM_DAEMON) + if (tmp->get_command() == COM_DAEMON) continue; - if (tmp->thread_id == id) + if (id == (query_id ? tmp->query_id : (longlong) tmp->thread_id)) { mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete break; } } mysql_mutex_unlock(&LOCK_thread_count); - if (tmp) + return tmp; +} + + +/** + kill one thread. + + @param thd Thread class + @param id Thread id or query id + @param kill_signal Should it kill the query or the connection + @param type Type of id: thread id or query id + + @note + This is written such that we have a short lock on LOCK_thread_count +*/ + +uint +kill_one_thread(THD *thd, longlong id, killed_state kill_signal, killed_type type) +{ + THD *tmp; + uint error= (type == KILL_TYPE_QUERY ? ER_NO_SUCH_QUERY : ER_NO_SUCH_THREAD); + DBUG_ENTER("kill_one_thread"); + DBUG_PRINT("enter", ("id: %lld signal: %u", id, (uint) kill_signal)); + + if (id && (tmp= find_thread_by_id(id, type == KILL_TYPE_QUERY))) { /* If we're SUPER, we can KILL anything, including system-threads. @@ -7160,7 +7922,7 @@ static uint kill_threads_for_user(THD *thd, LEX_USER *user, mysql_mutex_unlock(&LOCK_thread_count); DBUG_RETURN(ER_KILL_DENIED_ERROR); } - if (!threads_to_kill.push_back(tmp, tmp->mem_root)) + if (!threads_to_kill.push_back(tmp, thd->mem_root)) mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete } } @@ -7190,21 +7952,20 @@ static uint kill_threads_for_user(THD *thd, LEX_USER *user, } -/* - kills a thread and sends response +/** + kills a thread and sends response. - SYNOPSIS - sql_kill() - thd Thread class - id Thread id - only_kill_query Should it kill the query or the connection + @param thd Thread class + @param id Thread id or query id + @param state Should it kill the query or the connection + @param type Type of id: thread id or query id */ static -void sql_kill(THD *thd, ulong id, killed_state state) +void sql_kill(THD *thd, longlong id, killed_state state, killed_type type) { uint error; - if (!(error= kill_one_thread(thd, id, state))) + if (!(error= kill_one_thread(thd, id, state, type))) { if ((!thd->killed)) my_ok(thd); @@ -7279,7 +8040,7 @@ bool check_simple_select() char command[80]; Lex_input_stream *lip= & thd->m_parser_state->m_lip; strmake(command, lip->yylval->symbol.str, - min(lip->yylval->symbol.length, sizeof(command)-1)); + MY_MIN(lip->yylval->symbol.length, sizeof(command)-1)); my_error(ER_CANT_USE_OPTION_HERE, MYF(0), command); return 1; } @@ -7385,6 +8146,8 @@ bool multi_update_precheck(THD *thd, TABLE_LIST *tables) */ for (table= tables; table; table= table->next_local) { + if (table->is_jtbm()) + continue; if (table->derived) table->grant.privilege= SELECT_ACL; else if ((check_access(thd, UPDATE_ACL, table->db, @@ -7399,6 +8162,7 @@ bool multi_update_precheck(THD *thd, TABLE_LIST *tables) check_grant(thd, SELECT_ACL, table, FALSE, 1, FALSE))) DBUG_RETURN(TRUE); + table->grant.orig_want_privilege= 0; table->table_in_first_from_clause= 1; } /* @@ -7452,6 +8216,19 @@ bool multi_delete_precheck(THD *thd, TABLE_LIST *tables) TABLE_LIST **save_query_tables_own_last= thd->lex->query_tables_own_last; DBUG_ENTER("multi_delete_precheck"); + /* + Temporary tables are pre-opened in 'tables' list only. Here we need to + initialize TABLE instances in 'aux_tables' list. + */ + for (TABLE_LIST *tl= aux_tables; tl; tl= tl->next_global) + { + if (tl->table) + continue; + + if (tl->correspondent_table) + tl->table= tl->correspondent_table->table; + } + /* sql_yacc guarantees that tables and aux_tables are not zero */ DBUG_ASSERT(aux_tables != 0); if (check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE)) @@ -7674,7 +8451,7 @@ void create_table_set_open_action_and_adjust_tables(LEX *lex) { TABLE_LIST *create_table= lex->query_tables; - if (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) + if (lex->create_info.tmp_table()) create_table->open_type= OT_TEMPORARY_ONLY; else create_table->open_type= OT_BASE_ONLY; @@ -7720,10 +8497,14 @@ bool create_table_precheck(THD *thd, TABLE_LIST *tables, CREATE TABLE ... SELECT, also require INSERT. */ - want_priv= ((lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) ? - CREATE_TMP_ACL : CREATE_ACL) | - (select_lex->item_list.elements ? INSERT_ACL : 0); + want_priv= lex->create_info.tmp_table() ? CREATE_TMP_ACL : + (CREATE_ACL | (select_lex->item_list.elements ? INSERT_ACL : 0)); + /* CREATE OR REPLACE on not temporary tables require DROP_ACL */ + if ((lex->create_info.options & HA_LEX_CREATE_REPLACE) && + !lex->create_info.tmp_table()) + want_priv|= DROP_ACL; + if (check_access(thd, want_priv, create_table->db, &create_table->grant.privilege, &create_table->grant.m_internal, @@ -7731,11 +8512,48 @@ bool create_table_precheck(THD *thd, TABLE_LIST *tables, goto err; /* If it is a merge table, check privileges for merge children. */ - if (lex->create_info.merge_list.first && - check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL, - lex->create_info.merge_list.first, - FALSE, UINT_MAX, FALSE)) - goto err; + if (lex->create_info.merge_list.first) + { + /* + The user must have (SELECT_ACL | UPDATE_ACL | DELETE_ACL) on the + underlying base tables, even if there are temporary tables with the same + names. + + From user's point of view, it might look as if the user must have these + privileges on temporary tables to create a merge table over them. This is + one of two cases when a set of privileges is required for operations on + temporary tables (see also CREATE TABLE). + + The reason for this behavior stems from the following facts: + + - For merge tables, the underlying table privileges are checked only + at CREATE TABLE / ALTER TABLE time. + + In other words, once a merge table is created, the privileges of + the underlying tables can be revoked, but the user will still have + access to the merge table (provided that the user has privileges on + the merge table itself). + + - Temporary tables shadow base tables. + + I.e. there might be temporary and base tables with the same name, and + the temporary table takes the precedence in all operations. + + - For temporary MERGE tables we do not track if their child tables are + base or temporary. As result we can't guarantee that privilege check + which was done in presence of temporary child will stay relevant + later as this temporary table might be removed. + + If SELECT_ACL | UPDATE_ACL | DELETE_ACL privileges were not checked for + the underlying *base* tables, it would create a security breach as in + Bug#12771903. + */ + + if (check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL, + lex->create_info.merge_list.first, + FALSE, UINT_MAX, FALSE)) + goto err; + } if (want_priv != CREATE_TMP_ACL && check_grant(thd, want_priv, create_table, FALSE, 1, FALSE)) @@ -7758,12 +8576,42 @@ bool create_table_precheck(THD *thd, TABLE_LIST *tables, goto err; error= FALSE; + err: DBUG_RETURN(error); } /** + Check privileges for LOCK TABLES statement. + + @param thd Thread context. + @param tables List of tables to be locked. + + @retval FALSE - Success. + @retval TRUE - Failure. +*/ + +static bool lock_tables_precheck(THD *thd, TABLE_LIST *tables) +{ + TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table(); + + for (TABLE_LIST *table= tables; table != first_not_own_table && table; + table= table->next_global) + { + if (is_temporary_table(table)) + continue; + + if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, table, + FALSE, 1, FALSE)) + return TRUE; + } + + return FALSE; +} + + +/** negate given expression. @param thd thread handler @@ -7804,16 +8652,23 @@ Item *negate_expression(THD *thd, Item *expr) @param[out] definer definer */ -void get_default_definer(THD *thd, LEX_USER *definer) +void get_default_definer(THD *thd, LEX_USER *definer, bool role) { const Security_context *sctx= thd->security_ctx; - definer->user.str= (char *) sctx->priv_user; + if (role) + { + definer->user.str= const_cast<char*>(sctx->priv_role); + definer->host= empty_lex_str; + } + else + { + definer->user.str= const_cast<char*>(sctx->priv_user); + definer->host.str= const_cast<char*>(sctx->priv_host); + definer->host.length= strlen(definer->host.str); + } definer->user.length= strlen(definer->user.str); - definer->host.str= (char *) sctx->priv_host; - definer->host.length= strlen(definer->host.str); - definer->password= null_lex_str; definer->plugin= empty_lex_str; definer->auth= empty_lex_str; @@ -7831,16 +8686,22 @@ void get_default_definer(THD *thd, LEX_USER *definer) - On error, return 0. */ -LEX_USER *create_default_definer(THD *thd) +LEX_USER *create_default_definer(THD *thd, bool role) { LEX_USER *definer; if (! (definer= (LEX_USER*) thd->alloc(sizeof(LEX_USER)))) return 0; - thd->get_definer(definer); + thd->get_definer(definer, role); - return definer; + if (role && definer->user.length == 0) + { + my_error(ER_MALFORMED_DEFINER, MYF(0)); + return 0; + } + else + return definer; } @@ -7876,27 +8737,6 @@ LEX_USER *create_definer(THD *thd, LEX_STRING *user_name, LEX_STRING *host_name) /** - Retuns information about user or current user. - - @param[in] thd thread handler - @param[in] user user - - @return - - On success, return a valid pointer to initialized - LEX_USER, which contains user information. - - On error, return 0. -*/ - -LEX_USER *get_current_user(THD *thd, LEX_USER *user) -{ - if (!user->user.str) // current_user - return create_default_definer(thd); - - return user; -} - - -/** Check that byte length of a string does not exceed some limit. @param str string to be checked @@ -8043,6 +8883,22 @@ int test_if_data_home_dir(const char *dir) } +int error_if_data_home_dir(const char *path, const char *what) +{ + size_t dirlen; + char dirpath[FN_REFLEN]; + if (path) + { + dirname_part(dirpath, path, &dirlen); + if (test_if_data_home_dir(dirpath)) + { + my_error(ER_WRONG_ARGUMENTS, MYF(0), what); + return 1; + } + } + return 0; +} + /** Check that host name string is valid. @@ -8092,14 +8948,13 @@ extern int MYSQLparse(THD *thd); // from sql_yacc.cc @retval TRUE on parsing error. */ -bool parse_sql(THD *thd, - Parser_state *parser_state, - Object_creation_ctx *creation_ctx) +bool parse_sql(THD *thd, Parser_state *parser_state, + Object_creation_ctx *creation_ctx, bool do_pfs_digest) { bool ret_value; DBUG_ENTER("parse_sql"); DBUG_ASSERT(thd->m_parser_state == NULL); - DBUG_ASSERT(thd->lex->m_stmt == NULL); + DBUG_ASSERT(thd->lex->m_sql_cmd == NULL); MYSQL_QUERY_PARSE_START(thd->query()); /* Backup creation context. */ @@ -8113,6 +8968,28 @@ bool parse_sql(THD *thd, thd->m_parser_state= parser_state; + parser_state->m_digest_psi= NULL; + parser_state->m_lip.m_digest= NULL; + + if (do_pfs_digest) + { + /* Start Digest */ + parser_state->m_digest_psi= MYSQL_DIGEST_START(thd->m_statement_psi); + + if (parser_state->m_input.m_compute_digest || + (parser_state->m_digest_psi != NULL)) + { + /* + If either: + - the caller wants to compute a digest + - the performance schema wants to compute a digest + set the digest listener in the lexer. + */ + parser_state->m_lip.m_digest= thd->m_digest; + parser_state->m_lip.m_digest->m_digest_storage.m_charset_number= thd->charset()->number; + } + } + /* Parse the query. */ bool mysql_parse_status= MYSQLparse(thd) != 0; @@ -8144,6 +9021,18 @@ bool parse_sql(THD *thd, /* That's it. */ ret_value= mysql_parse_status || thd->is_fatal_error; + + if ((ret_value == 0) && (parser_state->m_digest_psi != NULL)) + { + /* + On parsing success, record the digest in the performance schema. + */ + DBUG_ASSERT(do_pfs_digest); + DBUG_ASSERT(thd->m_digest != NULL); + MYSQL_DIGEST_END(parser_state->m_digest_psi, + & thd->m_digest->m_digest_storage); + } + MYSQL_QUERY_PARSE_DONE(ret_value); DBUG_RETURN(ret_value); } |