diff options
Diffstat (limited to 'sql/sql_parse.cc')
-rw-r--r-- | sql/sql_parse.cc | 3892 |
1 files changed, 1601 insertions, 2291 deletions
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 6ce800312ff..2086100db98 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -14,20 +14,88 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define MYSQL_LEX 1 -#include "mysql_priv.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_cache.h" // QUERY_CACHE_FLAGS_SIZE, query_cache_* +#include "sql_show.h" // mysqld_list_*, mysqld_show_*, + // calc_sum_of_all_status +#include "mysqld.h" +#include "sql_locale.h" // my_locale_en_US +#include "log.h" // flush_error_log +#include "sql_view.h" // mysql_create_view, mysql_drop_view +#include "sql_delete.h" // mysql_delete +#include "sql_insert.h" // mysql_insert +#include "sql_update.h" // mysql_update, mysql_multi_update +#include "sql_partition.h" // struct partition_info +#include "sql_db.h" // mysql_change_db, mysql_create_db, + // mysql_rm_db, mysql_upgrade_db, + // mysql_alter_db, + // check_db_dir_existence, + // my_dbopt_cleanup +#include "sql_table.h" // mysql_create_like_table, + // mysql_create_table, + // mysql_alter_table, + // mysql_recreate_table, + // mysql_backup_table, + // mysql_restore_table +#include "sql_reload.h" // reload_acl_and_cache +#include "sql_admin.h" // mysql_assign_to_keycache +#include "sql_connect.h" // check_user, + // decrease_user_connections, + // thd_init_client_charset, check_mqh, + // reset_mqh +#include "sql_rename.h" // mysql_rename_table +#include "sql_tablespace.h" // mysql_alter_tablespace +#include "hostname.h" // hostname_cache_refresh +#include "sql_acl.h" // *_ACL, check_grant, is_acl_user, + // has_any_table_level_privileges, + // mysql_drop_user, mysql_rename_user, + // check_grant_routine, + // mysql_routine_grant, + // mysql_show_grants, + // sp_grant_privileges, ... +#include "sql_test.h" // mysql_print_status +#include "sql_select.h" // handle_select, mysql_select, + // mysql_explain_union +#include "sql_load.h" // mysql_load +#include "sql_servers.h" // create_servers, alter_servers, + // drop_servers, servers_reload +#include "sql_handler.h" // mysql_ha_open, mysql_ha_close, + // mysql_ha_read +#include "sql_binlog.h" // mysql_client_binlog_statement +#include "sql_do.h" // mysql_do +#include "sql_help.h" // mysqld_help +#include "rpl_constants.h" // Incident, INCIDENT_LOST_EVENTS +#include "log_event.h" #include "sql_repl.h" #include "rpl_filter.h" #include "repl_failsafe.h" #include <m_ctype.h> #include <myisam.h> #include <my_dir.h> +#include "rpl_handler.h" #include "sp_head.h" #include "sp.h" #include "sp_cache.h" #include "events.h" #include "sql_trigger.h" +#include "transaction.h" +#include "sql_audit.h" +#include "sql_prepare.h" #include "debug_sync.h" +#include "probes_mysql.h" +#include "set_var.h" + +#define FLAGSTR(V,F) ((V)&(F)?#F" ":"") /** @defgroup Runtime_Environment Runtime Environment @@ -45,7 +113,7 @@ "FUNCTION" : "PROCEDURE") static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables); -static bool check_show_create_table_access(THD *thd, TABLE_LIST *table); +static void sql_kill(THD *thd, ulong id, bool only_kill_query); const char *any_db="*any*"; // Special symbol for check_access @@ -87,127 +155,6 @@ const char *xa_state_names[]={ "NON-EXISTING", "ACTIVE", "IDLE", "PREPARED", "ROLLBACK ONLY" }; -/** - Mark a XA transaction as rollback-only if the RM unilaterally - rolled back the transaction branch. - - @note If a rollback was requested by the RM, this function sets - the appropriate rollback error code and transits the state - to XA_ROLLBACK_ONLY. - - @return TRUE if transaction was rolled back or if the transaction - state is XA_ROLLBACK_ONLY. FALSE otherwise. -*/ -static bool xa_trans_rolled_back(XID_STATE *xid_state) -{ - if (xid_state->rm_error) - { - switch (xid_state->rm_error) { - case ER_LOCK_WAIT_TIMEOUT: - my_error(ER_XA_RBTIMEOUT, MYF(0)); - break; - case ER_LOCK_DEADLOCK: - my_error(ER_XA_RBDEADLOCK, MYF(0)); - break; - default: - my_error(ER_XA_RBROLLBACK, MYF(0)); - } - xid_state->xa_state= XA_ROLLBACK_ONLY; - } - - return (xid_state->xa_state == XA_ROLLBACK_ONLY); -} - -/** - Rollback work done on behalf of at ransaction branch. -*/ -static bool xa_trans_rollback(THD *thd) -{ - /* - Resource Manager error is meaningless at this point, as we perform - explicit rollback request by user. We must reset rm_error before - calling ha_rollback(), so thd->transaction.xid structure gets reset - by ha_rollback()/THD::transaction::cleanup(). - */ - thd->transaction.xid_state.rm_error= 0; - - bool status= test(ha_rollback(thd)); - - thd->options&= ~(ulong) OPTION_BEGIN; - thd->transaction.all.modified_non_trans_table= FALSE; - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - xid_cache_delete(&thd->transaction.xid_state); - thd->transaction.xid_state.xa_state= XA_NOTR; - - return status; -} - -static void unlock_locked_tables(THD *thd) -{ - if (thd->locked_tables) - { - thd->lock=thd->locked_tables; - thd->locked_tables=0; // Will be automatically closed - close_thread_tables(thd); // Free tables - } -} - - -bool end_active_trans(THD *thd) -{ - int error=0; - DBUG_ENTER("end_active_trans"); - if (unlikely(thd->in_sub_stmt)) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - DBUG_RETURN(1); - } - if (thd->transaction.xid_state.xa_state != XA_NOTR) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - DBUG_RETURN(1); - } - if (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN | - OPTION_TABLE_LOCK)) - { - DBUG_PRINT("info",("options: 0x%llx", thd->options)); - /* Safety if one did "drop table" on locked tables */ - if (!thd->locked_tables) - thd->options&= ~OPTION_TABLE_LOCK; - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - if (ha_commit(thd)) - error=1; - } - thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - DBUG_RETURN(error); -} - - -bool begin_trans(THD *thd) -{ - int error=0; - if (unlikely(thd->in_sub_stmt)) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - return 1; - } - if (thd->locked_tables) - { - thd->lock=thd->locked_tables; - thd->locked_tables=0; // Will be automatically closed - close_thread_tables(thd); // Free tables - } - if (end_active_trans(thd)) - error= -1; - else - { - thd->options|= OPTION_BEGIN; - thd->server_status|= SERVER_STATUS_IN_TRANS; - } - return error; -} #ifdef HAVE_REPLICATION /** @@ -226,14 +173,49 @@ static bool some_non_temp_table_to_be_updated(THD *thd, TABLE_LIST *tables) for (TABLE_LIST *table= tables; table; table= table->next_global) { DBUG_ASSERT(table->db && table->table_name); - if (table->updating && - !find_temporary_table(thd, table->db, table->table_name)) + if (table->updating && !find_temporary_table(thd, table)) return 1; } return 0; } +/* + Implicitly commit a active transaction if statement requires so. + + @param thd Thread handle. + @param mask Bitmask used for the SQL command match. + +*/ +static bool stmt_causes_implicit_commit(THD *thd, uint mask) +{ + LEX *lex= thd->lex; + bool skip= FALSE; + DBUG_ENTER("stmt_causes_implicit_commit"); + + if (!(sql_command_flags[lex->sql_command] & mask)) + DBUG_RETURN(FALSE); + + switch (lex->sql_command) { + case SQLCOM_DROP_TABLE: + skip= lex->drop_temporary; + break; + case SQLCOM_ALTER_TABLE: + case SQLCOM_CREATE_TABLE: + /* If CREATE TABLE of non-temporary table, do implicit commit */ + skip= (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE); + break; + case SQLCOM_SET_OPTION: + skip= lex->autocommit ? FALSE : TRUE; + break; + default: + break; + } + + DBUG_RETURN(!skip); +} + + /** Mark all commands that somehow changes a table. @@ -248,48 +230,79 @@ static bool some_non_temp_table_to_be_updated(THD *thd, TABLE_LIST *tables) */ uint sql_command_flags[SQLCOM_END+1]; +uint server_command_flags[COM_END+1]; void init_update_queries(void) { - bzero((uchar*) &sql_command_flags, sizeof(sql_command_flags)); - - sql_command_flags[SQLCOM_CREATE_TABLE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_CREATE_INDEX]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_ALTER_TABLE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND; - sql_command_flags[SQLCOM_TRUNCATE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND; - sql_command_flags[SQLCOM_DROP_TABLE]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_LOAD]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_RENAME_TABLE]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_BACKUP_TABLE]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_RESTORE_TABLE]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_DROP_INDEX]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_CREATE_VIEW]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_DROP_VIEW]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_CREATE_EVENT]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_ALTER_EVENT]= CF_CHANGES_DATA; - sql_command_flags[SQLCOM_DROP_EVENT]= CF_CHANGES_DATA; - - sql_command_flags[SQLCOM_UPDATE]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_UPDATE_MULTI]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_INSERT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_INSERT_SELECT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_DELETE]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_DELETE_MULTI]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_REPLACE]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_REPLACE_SELECT]= CF_CHANGES_DATA | CF_HAS_ROW_COUNT | - CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_SELECT]= CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_SET_OPTION]= CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_DO]= CF_REEXECUTION_FRAGILE; + /* Initialize the server command flags array. */ + memset(server_command_flags, 0, sizeof(server_command_flags)); + + server_command_flags[COM_STATISTICS]= CF_SKIP_QUERY_ID | CF_SKIP_QUESTIONS; + server_command_flags[COM_PING]= CF_SKIP_QUERY_ID | CF_SKIP_QUESTIONS; + server_command_flags[COM_STMT_PREPARE]= CF_SKIP_QUESTIONS; + server_command_flags[COM_STMT_CLOSE]= CF_SKIP_QUESTIONS; + server_command_flags[COM_STMT_RESET]= CF_SKIP_QUESTIONS; + + /* Initialize the sql command flags array. */ + memset(sql_command_flags, 0, sizeof(sql_command_flags)); + + /* + In general, DDL statements do not generate row events and do not go + through a cache before being written to the binary log. However, the + CREATE TABLE...SELECT is an exception because it may generate row + events. For that reason, the SQLCOM_CREATE_TABLE which represents + a CREATE TABLE, including the CREATE TABLE...SELECT, has the + CF_CAN_GENERATE_ROW_EVENTS flag. The distinction between a regular + CREATE TABLE and the CREATE TABLE...SELECT is made in other parts of + the code, in particular in the Query_log_event's constructor. + */ + sql_command_flags[SQLCOM_CREATE_TABLE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_AUTO_COMMIT_TRANS | + CF_CAN_GENERATE_ROW_EVENTS; + sql_command_flags[SQLCOM_CREATE_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_TABLE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND | + CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_TRUNCATE]= CF_CHANGES_DATA | CF_WRITE_LOGS_COMMAND | + CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_LOAD]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_CAN_GENERATE_ROW_EVENTS; + sql_command_flags[SQLCOM_CREATE_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_DB_UPGRADE]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_DB]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_RENAME_TABLE]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_INDEX]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_VIEW]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_VIEW]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_TRIGGER]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_TRIGGER]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_EVENT]= CF_CHANGES_DATA | CF_AUTO_COMMIT_TRANS; + + sql_command_flags[SQLCOM_UPDATE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_CAN_GENERATE_ROW_EVENTS; + sql_command_flags[SQLCOM_UPDATE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_CAN_GENERATE_ROW_EVENTS; + sql_command_flags[SQLCOM_INSERT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_CAN_GENERATE_ROW_EVENTS; + sql_command_flags[SQLCOM_INSERT_SELECT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_CAN_GENERATE_ROW_EVENTS; + sql_command_flags[SQLCOM_DELETE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_CAN_GENERATE_ROW_EVENTS; + sql_command_flags[SQLCOM_DELETE_MULTI]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_CAN_GENERATE_ROW_EVENTS; + sql_command_flags[SQLCOM_REPLACE]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_CAN_GENERATE_ROW_EVENTS; + sql_command_flags[SQLCOM_REPLACE_SELECT]= CF_CHANGES_DATA | CF_REEXECUTION_FRAGILE | + CF_CAN_GENERATE_ROW_EVENTS; + 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; + sql_command_flags[SQLCOM_DO]= CF_REEXECUTION_FRAGILE | + CF_CAN_GENERATE_ROW_EVENTS; 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; @@ -303,35 +316,35 @@ void init_update_queries(void) sql_command_flags[SQLCOM_SHOW_VARIABLES]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_CHARSETS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; sql_command_flags[SQLCOM_SHOW_COLLATIONS]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_SHOW_NEW_MASTER]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_BINLOGS]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_NEW_MASTER]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_BINLOGS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_SLAVE_HOSTS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_BINLOG_EVENTS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_COLUMN_TYPES]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_STORAGE_ENGINES]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_AUTHORS]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_AUTHORS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CONTRIBUTORS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_PRIVILEGES]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_WARNS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_ERRORS]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_PRIVILEGES]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_WARNS]= CF_STATUS_COMMAND | CF_DIAGNOSTIC_STMT; + sql_command_flags[SQLCOM_SHOW_ERRORS]= CF_STATUS_COMMAND | CF_DIAGNOSTIC_STMT; sql_command_flags[SQLCOM_SHOW_ENGINE_STATUS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_ENGINE_MUTEX]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_ENGINE_LOGS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_PROCESSLIST]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_GRANTS]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_CREATE_DB]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_GRANTS]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_CREATE_DB]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CREATE]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_MASTER_STAT]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_MASTER_STAT]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_SLAVE_STAT]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_CREATE_PROC]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_CREATE_FUNC]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_CREATE_PROC]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_CREATE_FUNC]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CREATE_TRIGGER]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_STATUS_FUNC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_SHOW_PROC_CODE]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_FUNC_CODE]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_CREATE_EVENT]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_PROFILES]= CF_STATUS_COMMAND; - sql_command_flags[SQLCOM_SHOW_PROFILE]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_STATUS_FUNC]= CF_STATUS_COMMAND | CF_REEXECUTION_FRAGILE; + sql_command_flags[SQLCOM_SHOW_PROC_CODE]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_FUNC_CODE]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_CREATE_EVENT]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_PROFILES]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_PROFILE]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_BINLOG_BASE64_EVENT]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_TABLES]= (CF_STATUS_COMMAND | CF_SHOW_TABLE_COMMAND | @@ -340,25 +353,65 @@ void init_update_queries(void) CF_SHOW_TABLE_COMMAND | CF_REEXECUTION_FRAGILE); + + sql_command_flags[SQLCOM_CREATE_USER]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_RENAME_USER]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_DROP_USER]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_GRANT]= CF_CHANGES_DATA; + sql_command_flags[SQLCOM_REVOKE]= 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_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; + /* The following is used to preserver CF_ROW_COUNT during the a CALL or EXECUTE statement, so the value generated by the last called (or executed) statement is preserved. See mysql_execute_command() for how CF_ROW_COUNT is used. */ - sql_command_flags[SQLCOM_CALL]= CF_HAS_ROW_COUNT | CF_REEXECUTION_FRAGILE; - sql_command_flags[SQLCOM_EXECUTE]= CF_HAS_ROW_COUNT; + sql_command_flags[SQLCOM_CALL]= CF_REEXECUTION_FRAGILE | + CF_CAN_GENERATE_ROW_EVENTS; + sql_command_flags[SQLCOM_EXECUTE]= CF_CAN_GENERATE_ROW_EVENTS; /* The following admin table operations are allowed on log tables. */ - sql_command_flags[SQLCOM_REPAIR]= CF_WRITE_LOGS_COMMAND; - sql_command_flags[SQLCOM_OPTIMIZE]= CF_WRITE_LOGS_COMMAND; - sql_command_flags[SQLCOM_ANALYZE]= CF_WRITE_LOGS_COMMAND; + sql_command_flags[SQLCOM_REPAIR]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_OPTIMIZE]|= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ANALYZE]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CHECK]= CF_WRITE_LOGS_COMMAND | CF_AUTO_COMMIT_TRANS; + + sql_command_flags[SQLCOM_CREATE_USER]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_USER]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_RENAME_USER]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_REVOKE_ALL]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_REVOKE]|= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_GRANT]|= CF_AUTO_COMMIT_TRANS; + + sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_PRELOAD_KEYS]= CF_AUTO_COMMIT_TRANS; + + sql_command_flags[SQLCOM_FLUSH]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_RESET]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CREATE_SERVER]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_ALTER_SERVER]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_DROP_SERVER]= CF_AUTO_COMMIT_TRANS; } - +bool sqlcom_can_generate_row_events(const THD *thd) +{ + return (sql_command_flags[thd->lex->sql_command] & + CF_CAN_GENERATE_ROW_EVENTS); +} + bool is_update_query(enum enum_sql_command command) { DBUG_ASSERT(command >= 0 && command <= SQLCOM_END); @@ -376,25 +429,34 @@ bool is_log_table_write_query(enum enum_sql_command command) return (sql_command_flags[command] & CF_WRITE_LOGS_COMMAND) != 0; } -void execute_init_command(THD *thd, sys_var_str *init_command_var, - rw_lock_t *var_mutex) +void execute_init_command(THD *thd, LEX_STRING *init_command, + mysql_rwlock_t *var_lock) { Vio* save_vio; ulong save_client_capabilities; -#if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) + mysql_rwlock_rdlock(var_lock); + if (!init_command->length) + { + mysql_rwlock_unlock(var_lock); + return; + } + + /* + copy the value under a lock, and release the lock. + init_command has to be executed without a lock held, + as it may try to change itself + */ + size_t len= init_command->length; + char *buf= thd->strmake(init_command->str, len); + mysql_rwlock_unlock(var_lock); + +#if defined(ENABLED_PROFILING) thd->profiling.start_new_query(); - thd->profiling.set_query_source(init_command_var->value, - init_command_var->value_length); + thd->profiling.set_query_source(buf, len); #endif thd_proc_info(thd, "Execution of init_command"); - /* - We need to lock init_command_var because - during execution of init_command_var query - values of init_command_var can't be changed - */ - rw_rdlock(var_mutex); save_client_capabilities= thd->client_capabilities; thd->client_capabilities|= CLIENT_MULTI_QUERIES; /* @@ -403,14 +465,11 @@ void execute_init_command(THD *thd, sys_var_str *init_command_var, */ save_vio= thd->net.vio; thd->net.vio= 0; - dispatch_command(COM_QUERY, thd, - init_command_var->value, - init_command_var->value_length); - rw_unlock(var_mutex); + dispatch_command(COM_QUERY, thd, buf, len); thd->client_capabilities= save_client_capabilities; thd->net.vio= save_vio; -#if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) +#if defined(ENABLED_PROFILING) thd->profiling.finish_current_query(); #endif } @@ -418,9 +477,8 @@ void execute_init_command(THD *thd, sys_var_str *init_command_var, static void handle_bootstrap_impl(THD *thd) { - FILE *file=bootstrap_file; + MYSQL_FILE *file= bootstrap_file; char *buff; - const char* found_semicolon= NULL; DBUG_ENTER("handle_bootstrap"); @@ -429,14 +487,9 @@ static void handle_bootstrap_impl(THD *thd) thd->thread_stack= (char*) &thd; #endif /* EMBEDDED_LIBRARY */ - if (thd->variables.max_join_size == HA_POS_ERROR) - thd->options |= OPTION_BIG_SELECTS; - thd_proc_info(thd, 0); - thd->version=refresh_version; - thd->security_ctx->priv_user= - thd->security_ctx->user= (char*) my_strdup("boot", MYF(MY_WME)); - thd->security_ctx->priv_host[0]=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; /* Make the "client" handle multiple results. This is necessary to enable stored procedures with SELECTs and Dynamic SQL @@ -446,12 +499,12 @@ static void handle_bootstrap_impl(THD *thd) buff= (char*) thd->net.buff; thd->init_for_queries(); - while (fgets(buff, thd->net.max_packet, file)) + while (mysql_file_fgets(buff, thd->net.max_packet, file)) { - char *query, *res; - /* strlen() can't be deleted because fgets() doesn't return 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' && !feof(file)) + while (buff[length-1] != '\n' && !mysql_file_feof(file)) { /* We got only a part of the current string. Will try to increase @@ -460,18 +513,12 @@ static void handle_bootstrap_impl(THD *thd) /* purecov: begin tested */ if (net_realloc(&(thd->net), 2 * thd->net.max_packet)) { - net_end_statement(thd); + thd->protocol->end_statement(); bootstrap_error= 1; break; } buff= (char*) thd->net.buff; - res= fgets(buff + length, thd->net.max_packet - length, file); - if (!res && !feof(file)) - { - net_end_statement(thd); - bootstrap_error= 1; - break; - } + mysql_file_fgets(buff + length, thd->net.max_packet - length, file); length+= (ulong) strlen(buff + length); /* purecov: end */ } @@ -490,9 +537,9 @@ static void handle_bootstrap_impl(THD *thd) query= (char *) thd->memdup_w_gap(buff, length + 1, thd->db_length + 1 + QUERY_CACHE_FLAGS_SIZE); - thd->set_query(query, length); - DBUG_PRINT("query",("%-.4096s", thd->query())); -#if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) + thd->set_query_and_id(query, length, thd->charset(), next_query_id()); + DBUG_PRINT("query",("%-.4096s",thd->query())); +#if defined(ENABLED_PROFILING) thd->profiling.start_new_query(); thd->profiling.set_query_source(thd->query(), length); #endif @@ -501,15 +548,21 @@ static void handle_bootstrap_impl(THD *thd) We don't need to obtain LOCK_thread_count here because in bootstrap mode we have only one thread. */ - thd->query_id=next_query_id(); thd->set_time(); - mysql_parse(thd, thd->query(), length, & found_semicolon); - close_thread_tables(thd); // Free tables + Parser_state parser_state; + if (parser_state.init(thd, thd->query(), length)) + { + thd->protocol->end_statement(); + bootstrap_error= 1; + break; + } + + mysql_parse(thd, thd->query(), length, &parser_state); bootstrap_error= thd->is_error(); - net_end_statement(thd); + thd->protocol->end_statement(); -#if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) +#if defined(ENABLED_PROFILING) thd->profiling.finish_current_query(); #endif @@ -517,9 +570,7 @@ static void handle_bootstrap_impl(THD *thd) break; free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC)); -#ifdef USING_TRANSACTIONS free_root(&thd->transaction.mem_root,MYF(MY_KEEP_PREALLOC)); -#endif } DBUG_VOID_RETURN; @@ -536,12 +587,20 @@ pthread_handler_t handle_bootstrap(void *arg) { THD *thd=(THD*) arg; + mysql_thread_set_psi_id(thd->thread_id); + + do_handle_bootstrap(thd); + return 0; +} + +void do_handle_bootstrap(THD *thd) +{ /* The following must be called before DBUG_ENTER */ thd->thread_stack= (char*) &thd; if (my_thread_init() || thd->store_globals()) { #ifndef EMBEDDED_LIBRARY - close_connection(thd, ER_OUT_OF_RESOURCES, 1); + close_connection(thd, ER_OUT_OF_RESOURCES); #endif thd->fatal_error(); goto end; @@ -555,59 +614,19 @@ end: delete thd; #ifndef EMBEDDED_LIBRARY - (void) pthread_mutex_lock(&LOCK_thread_count); + mysql_mutex_lock(&LOCK_thread_count); thread_count--; in_bootstrap= FALSE; - (void) pthread_cond_broadcast(&COND_thread_count); - (void) pthread_mutex_unlock(&LOCK_thread_count); + mysql_cond_broadcast(&COND_thread_count); + mysql_mutex_unlock(&LOCK_thread_count); my_thread_end(); pthread_exit(0); #endif - return 0; + return; } -/** - @brief Check access privs for a MERGE table and fix children lock types. - - @param[in] thd thread handle - @param[in] db database name - @param[in,out] table_list list of child tables (merge_list) - lock_type and optionally db set per table - - @return status - @retval 0 OK - @retval != 0 Error - - @detail - This function is used for write access to MERGE tables only - (CREATE TABLE, ALTER TABLE ... UNION=(...)). Set TL_WRITE for - every child. Set 'db' for every child if not present. -*/ -#ifndef NO_EMBEDDED_ACCESS_CHECKS -static bool check_merge_table_access(THD *thd, char *db, - TABLE_LIST *table_list) -{ - int error= 0; - - if (table_list) - { - /* Check that all tables use the current database */ - TABLE_LIST *tlist; - - for (tlist= table_list; tlist; tlist= tlist->next_local) - { - if (!tlist->db || !tlist->db[0]) - tlist->db= db; /* purecov: inspected */ - } - error= check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL, - table_list, UINT_MAX, FALSE); - } - return error; -} -#endif - /* This works because items are allocated with sql_alloc() */ void free_items(Item *item) @@ -634,154 +653,6 @@ void cleanup_items(Item *item) DBUG_VOID_RETURN; } -/** - Handle COM_TABLE_DUMP command. - - @param thd thread handle - @param db database name or an empty string. If empty, - the current database of the connection is used - @param tbl_name name of the table to dump - - @note - This function is written to handle one specific command only. - - @retval - 0 success - @retval - 1 error, the error message is set in THD -*/ - -static -int mysql_table_dump(THD *thd, LEX_STRING *db, LEX_STRING *table_name) -{ - TABLE* table; - TABLE_LIST* table_list; - int error = 0; - DBUG_ENTER("mysql_table_dump"); - if (db->length == 0) - { - db->str= thd->db; /* purecov: inspected */ - db->length= thd->db_length; /* purecov: inspected */ - } - if (!(table_list = (TABLE_LIST*) thd->calloc(sizeof(TABLE_LIST)))) - DBUG_RETURN(1); // out of memory - table_list->db= db->str; - table_list->table_name= table_list->alias= table_name->str; - table_list->lock_type= TL_READ_NO_INSERT; - table_list->prev_global= &table_list; // can be removed after merge with 4.1 - - if (check_db_name(db)) - { - /* purecov: begin inspected */ - my_error(ER_WRONG_DB_NAME ,MYF(0), db->str ? db->str : "NULL"); - goto err; - /* purecov: end */ - } - if (!table_name->length || - check_table_name(table_name->str, table_name->length, TRUE)) - { - my_error(ER_WRONG_TABLE_NAME, MYF(0), - table_name->str ? table_name->str : "NULL"); - error= 1; - goto err; - } - if (lower_case_table_names) - my_casedn_str(files_charset_info, table_name->str); - - if (!(table=open_ltable(thd, table_list, TL_READ_NO_INSERT, 0))) - DBUG_RETURN(1); - - if (check_one_table_access(thd, SELECT_ACL, table_list)) - goto err; - thd->free_list = 0; - thd->set_query(table_name->str, table_name->length); - if ((error = mysqld_dump_create_info(thd, table_list, -1))) - { - my_error(ER_GET_ERRNO, MYF(0), my_errno); - goto err; - } - net_flush(&thd->net); - if ((error= table->file->dump(thd,-1))) - my_error(ER_GET_ERRNO, MYF(0), error); - -err: - DBUG_RETURN(error); -} - -/** - Ends the current transaction and (maybe) begin the next. - - @param thd Current thread - @param completion Completion type - - @retval - 0 OK -*/ - -int end_trans(THD *thd, enum enum_mysql_completiontype completion) -{ - bool do_release= 0; - int res= 0; - DBUG_ENTER("end_trans"); - - if (unlikely(thd->in_sub_stmt)) - { - my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0)); - DBUG_RETURN(1); - } - if (thd->transaction.xid_state.xa_state != XA_NOTR) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - DBUG_RETURN(1); - } - switch (completion) { - case COMMIT: - /* - We don't use end_active_trans() here to ensure that this works - even if there is a problem with the OPTION_AUTO_COMMIT flag - (Which of course should never happen...) - */ - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - res= ha_commit(thd); - thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - break; - case COMMIT_RELEASE: - do_release= 1; /* fall through */ - case COMMIT_AND_CHAIN: - res= end_active_trans(thd); - if (!res && completion == COMMIT_AND_CHAIN) - res= begin_trans(thd); - break; - case ROLLBACK_RELEASE: - do_release= 1; /* fall through */ - case ROLLBACK: - case ROLLBACK_AND_CHAIN: - { - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - if (ha_rollback(thd)) - res= -1; - thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - if (!res && (completion == ROLLBACK_AND_CHAIN)) - res= begin_trans(thd); - break; - } - default: - res= -1; - my_error(ER_UNKNOWN_COM_ERROR, MYF(0)); - DBUG_RETURN(-1); - } - - if (res < 0) - my_error(thd->killed_errno(), MYF(0)); - else if ((res == 0) && do_release) - thd->killed= THD::KILL_CONNECTION; - - DBUG_RETURN(res); -} - #ifndef EMBEDDED_LIBRARY /** @@ -815,7 +686,7 @@ bool do_command(THD *thd) This thread will do a blocking read from the client which will be interrupted when the next command is received from the client, the connection is closed or "net_wait_timeout" - number of seconds has passed + number of seconds has passed. */ my_net_set_read_timeout(net, thd->variables.net_wait_timeout); @@ -824,15 +695,27 @@ bool do_command(THD *thd) Consider moving to init_connect() instead. */ thd->clear_error(); // Clear error message - thd->main_da.reset_diagnostics_area(); + thd->stmt_da->reset_diagnostics_area(); net_new_transaction(net); - packet_length= my_net_read(net); -#if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) - thd->profiling.start_new_query(); -#endif - if (packet_length == packet_error) + /* + Synchronization point for testing of KILL_CONNECTION. + This sync point can wait here, to simulate slow code execution + between the last test of thd->killed and blocking in read(). + + The goal of this test is to verify that a connection does not + hang, if it is killed at this point of execution. + (Bug#37780 - main.kill fails randomly) + + Note that the sync point wait itself will be terminated by a + kill. In this case it consumes a condition broadcast, but does + not change anything else. The consumed broadcast should not + matter here, because the read/recv() below doesn't use it. + */ + DEBUG_SYNC(thd, "before_do_command_net_read"); + + if ((packet_length= my_net_read(net)) == packet_error) { DBUG_PRINT("info",("Got error %d reading command from socket %s", net->error, @@ -842,7 +725,7 @@ bool do_command(THD *thd) /* The error must be set. */ DBUG_ASSERT(thd->is_error()); - net_end_statement(thd); + thd->protocol->end_statement(); if (net->error != 3) { @@ -889,9 +772,6 @@ bool do_command(THD *thd) return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1)); out: -#if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) - thd->profiling.finish_current_query(); -#endif DBUG_RETURN(return_value); } #endif /* EMBEDDED_LIBRARY */ @@ -993,6 +873,13 @@ bool dispatch_command(enum enum_server_command command, THD *thd, DBUG_ENTER("dispatch_command"); DBUG_PRINT("info",("packet: '%*.s'; command: %d", packet_length, packet, command)); +#if defined(ENABLED_PROFILING) + thd->profiling.start_new_query(); +#endif + MYSQL_COMMAND_START(thd->thread_id, command, + &thd->security_ctx->priv_user[0], + (char *) thd->security_ctx->host_or_ip); + thd->command=command; /* Commands which always take a long time are logged into @@ -1013,30 +900,13 @@ bool dispatch_command(enum enum_server_command command, THD *thd, thd->security_ctx->master_access|= SHUTDOWN_ACL; command= COM_SHUTDOWN; } - - VOID(pthread_mutex_lock(&LOCK_thread_count)); - thd->query_id= global_query_id; - - switch( command ) { - /* Ignore these statements. */ - case COM_STATISTICS: - case COM_PING: - break; - /* Only increase id on these statements but don't count them. */ - case COM_STMT_PREPARE: - case COM_STMT_CLOSE: - case COM_STMT_RESET: - next_query_id(); - break; - /* Increase id and count all other statements. */ - default: - statistic_increment(thd->status_var.questions, &LOCK_status); + thd->set_query_id(get_query_id()); + if (!(server_command_flags[command] & CF_SKIP_QUERY_ID)) next_query_id(); - } + inc_thread_running(); - thread_running++; - /* TODO: set thd->lex->sql_command to SQLCOM_END here */ - VOID(pthread_mutex_unlock(&LOCK_thread_count)); + if (!(server_command_flags[command] & CF_SKIP_QUESTIONS)) + statistic_increment(thd->status_var.questions, &LOCK_status); /** Clear the set of flags that are expected to be cleared at the @@ -1065,133 +935,40 @@ bool dispatch_command(enum enum_server_command command, THD *thd, break; } #endif - case COM_TABLE_DUMP: - { - LEX_STRING db, table; - /* Safe because there is always a trailing \0 at the end of the packet */ - uint db_len= *(uchar*) packet; - if (db_len + 1 > packet_length || db_len > NAME_LEN) - { - my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); - break; - } - /* Safe because there is always a trailing \0 at the end of the packet */ - uint tbl_len= *(uchar*) (packet + db_len + 1); - if (db_len + tbl_len + 2 > packet_length || tbl_len > NAME_LEN) - { - my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); - break; - } - - status_var_increment(thd->status_var.com_other); - thd->enable_slow_log= opt_log_slow_admin_statements; - db.str= (char*) thd->alloc(db_len + tbl_len + 2); - if (!db.str) - { - my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0)); - break; - } - db.length= db_len; - table.length= tbl_len; - table.str= strmake(db.str, packet + 1, db_len) + 1; - strmake(table.str, packet + db_len + 2, tbl_len); - if (mysql_table_dump(thd, &db, &table) == 0) - thd->main_da.disable_status(); - break; - } case COM_CHANGE_USER: { + bool rc; status_var_increment(thd->status_var.com_other); - char *user= (char*) packet, *packet_end= packet + packet_length; - /* Safe because there is always a trailing \0 at the end of the packet */ - char *passwd= strend(user)+1; thd->change_user(); thd->clear_error(); // if errors from rollback - /* - Old clients send null-terminated string ('\0' for empty string) for - password. New clients send the size (1 byte) + string (not null - terminated, so also '\0' for empty string). + /* acl_authenticate() takes the data from net->read_pos */ + net->read_pos= (uchar*)packet; - Cast *passwd to an unsigned char, so that it doesn't extend the sign - for *passwd > 127 and become 2**32-127 after casting to uint. - */ - char db_buff[NAME_LEN+1]; // buffer to store db in utf8 - char *db= passwd; - char *save_db; - /* - If there is no password supplied, the packet must contain '\0', - in any type of handshake (4.1 or pre-4.1). - */ - if (passwd >= packet_end) - { - my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); - break; - } - uint passwd_len= (thd->client_capabilities & CLIENT_SECURE_CONNECTION ? - (uchar)(*passwd++) : strlen(passwd)); - uint dummy_errors, save_db_length, db_length; - int res; + uint save_db_length= thd->db_length; + char *save_db= thd->db; + USER_CONN *save_user_connect= thd->user_connect; Security_context save_security_ctx= *thd->security_ctx; - USER_CONN *save_user_connect; - - db+= passwd_len + 1; - /* - Database name is always NUL-terminated, so in case of empty database - the packet must contain at least the trailing '\0'. - */ - if (db >= packet_end) - { - my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); - break; - } - db_length= strlen(db); - - char *ptr= db + db_length + 1; - uint cs_number= 0; - - if (ptr < packet_end) - { - if (ptr + 2 > packet_end) - { - my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); - break; - } - - cs_number= uint2korr(ptr); - } - - /* Convert database name to utf8 */ - db_buff[copy_and_convert(db_buff, sizeof(db_buff)-1, - system_charset_info, db, db_length, - thd->charset(), &dummy_errors)]= 0; - db= db_buff; - - /* Save user and privileges */ - save_db_length= thd->db_length; - save_db= thd->db; - save_user_connect= thd->user_connect; - - if (!(thd->security_ctx->user= my_strdup(user, MYF(0)))) - { - thd->security_ctx->user= save_security_ctx.user; - my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0)); - break; - } - - /* Clear variables that are allocated */ - thd->user_connect= 0; - thd->security_ctx->priv_user= thd->security_ctx->user; - res= check_user(thd, COM_CHANGE_USER, passwd, passwd_len, db, FALSE); - - if (res) - { - x_free(thd->security_ctx->user); + CHARSET_INFO *save_character_set_client= + thd->variables.character_set_client; + CHARSET_INFO *save_collation_connection= + thd->variables.collation_connection; + CHARSET_INFO *save_character_set_results= + thd->variables.character_set_results; + + rc= acl_authenticate(thd, 0, packet_length); + MYSQL_AUDIT_NOTIFY_CONNECTION_CHANGE_USER(thd); + if (rc) + { + my_free(thd->security_ctx->user); *thd->security_ctx= save_security_ctx; thd->user_connect= save_user_connect; - thd->db= save_db; - thd->db_length= save_db_length; + thd->reset_db (save_db, save_db_length); + thd->variables.character_set_client= save_character_set_client; + thd->variables.collation_connection= save_collation_connection; + thd->variables.character_set_results= save_character_set_results; + thd->update_charset(); } else { @@ -1200,14 +977,8 @@ bool dispatch_command(enum enum_server_command command, THD *thd, if (save_user_connect) decrease_user_connections(save_user_connect); #endif /* NO_EMBEDDED_ACCESS_CHECKS */ - x_free(save_db); - x_free(save_security_ctx.user); - - if (cs_number) - { - thd_init_client_charset(thd, cs_number); - thd->update_charset(); - } + my_free(save_db); + my_free(save_security_ctx.user); } break; } @@ -1245,31 +1016,36 @@ bool dispatch_command(enum enum_server_command command, THD *thd, { if (alloc_query(thd, packet, packet_length)) break; // fatal error is set + MYSQL_QUERY_START(thd->query(), thd->thread_id, + (char *) (thd->db ? thd->db : ""), + &thd->security_ctx->priv_user[0], + (char *) thd->security_ctx->host_or_ip); char *packet_end= thd->query() + thd->query_length(); /* 'b' stands for 'buffer' parameter', special for 'my_snprintf' */ - const char* end_of_stmt= NULL; general_log_write(thd, command, thd->query(), thd->query_length()); DBUG_PRINT("query",("%-.4096s",thd->query())); -#if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) +#if defined(ENABLED_PROFILING) thd->profiling.set_query_source(thd->query(), thd->query_length()); #endif + Parser_state parser_state; + if (parser_state.init(thd, thd->query(), thd->query_length())) + break; - if (!(specialflag & SPECIAL_NO_PRIOR)) - my_pthread_setprio(pthread_self(),QUERY_PRIOR); - - mysql_parse(thd, thd->query(), thd->query_length(), &end_of_stmt); + mysql_parse(thd, thd->query(), thd->query_length(), &parser_state); - while (!thd->killed && (end_of_stmt != NULL) && ! thd->is_error()) + while (!thd->killed && (parser_state.m_lip.found_semicolon != NULL) && + ! thd->is_error()) { - char *beginning_of_next_stmt= (char*) end_of_stmt; - - net_end_statement(thd); - query_cache_end_of_result(thd); /* Multiple queries exits, execute them individually */ - close_thread_tables(thd); + char *beginning_of_next_stmt= (char*) parser_state.m_lip.found_semicolon; + + /* Finalize server status flags after executing a statement. */ + thd->update_server_status(); + thd->protocol->end_statement(); + query_cache_end_of_result(thd); ulong length= (ulong)(packet_end - beginning_of_next_stmt); log_slow_statement(thd); @@ -1281,27 +1057,34 @@ bool dispatch_command(enum enum_server_command command, THD *thd, length--; } -#if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) + if (MYSQL_QUERY_DONE_ENABLED()) + { + MYSQL_QUERY_DONE(thd->is_error()); + } + +#if defined(ENABLED_PROFILING) thd->profiling.finish_current_query(); thd->profiling.start_new_query("continuing"); thd->profiling.set_query_source(beginning_of_next_stmt, length); #endif - thd->set_query(beginning_of_next_stmt, length); - VOID(pthread_mutex_lock(&LOCK_thread_count)); + 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); + + thd->set_query_and_id(beginning_of_next_stmt, length, + thd->charset(), next_query_id()); /* Count each statement from the client. */ statistic_increment(thd->status_var.questions, &LOCK_status); - thd->query_id= next_query_id(); thd->set_time(); /* Reset the query start time. */ + parser_state.reset(beginning_of_next_stmt, length); /* TODO: set thd->lex->sql_command to SQLCOM_END here */ - VOID(pthread_mutex_unlock(&LOCK_thread_count)); - mysql_parse(thd, beginning_of_next_stmt, length, &end_of_stmt); + mysql_parse(thd, beginning_of_next_stmt, length, &parser_state); } - if (!(specialflag & SPECIAL_NO_PRIOR)) - my_pthread_setprio(pthread_self(),WAIT_PRIOR); DBUG_PRINT("info",("query ready")); break; } @@ -1315,40 +1098,56 @@ bool dispatch_command(enum enum_server_command command, THD *thd, char *fields, *packet_end= packet + packet_length, *arg_end; /* Locked closure of all tables */ TABLE_LIST table_list; - LEX_STRING conv_name; - - /* used as fields initializator */ - lex_start(thd); + LEX_STRING table_name; + LEX_STRING db; + /* + SHOW statements should not add the used tables to the list of tables + used in a transaction. + */ + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); status_var_increment(thd->status_var.com_stat[SQLCOM_SHOW_FIELDS]); - bzero((char*) &table_list,sizeof(table_list)); - if (thd->copy_db_to(&table_list.db, &table_list.db_length)) + if (thd->copy_db_to(&db.str, &db.length)) break; /* We have name + wildcard in packet, separated by endzero */ arg_end= strend(packet); uint arg_length= arg_end - packet; - + /* Check given table name length. */ if (arg_length >= packet_length || arg_length > NAME_LEN) { my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); break; } - thd->convert_string(&conv_name, system_charset_info, + thd->convert_string(&table_name, system_charset_info, packet, arg_length, thd->charset()); - if (check_table_name(conv_name.str, conv_name.length, FALSE)) + if (check_table_name(table_name.str, table_name.length, FALSE)) { /* this is OK due to convert_string() null-terminating the string */ - my_error(ER_WRONG_TABLE_NAME, MYF(0), conv_name.str); + my_error(ER_WRONG_TABLE_NAME, MYF(0), table_name.str); break; } - - table_list.alias= table_list.table_name= conv_name.str; packet= arg_end + 1; + mysql_reset_thd_for_next_command(thd); + lex_start(thd); + /* Must be before we init the table list. */ + if (lower_case_table_names) + table_name.length= my_casedn_str(files_charset_info, table_name.str); + table_list.init_one_table(db.str, db.length, table_name.str, + table_name.length, table_name.str, TL_READ); + /* + Init TABLE_LIST members necessary when the undelrying + table is view. + */ + table_list.select_lex= &(thd->lex->select_lex); + thd->lex-> + select_lex.table_list.link_in_list(&table_list, + &table_list.next_local); + thd->lex->add_to_query_tables(&table_list); - if (is_schema_db(table_list.db, table_list.db_length)) + if (is_infoschema_db(table_list.db, table_list.db_length)) { ST_SCHEMA_TABLE *schema_table= find_schema_table(thd, table_list.alias); if (schema_table) @@ -1360,29 +1159,23 @@ bool dispatch_command(enum enum_server_command command, THD *thd, break; thd->set_query(fields, query_length); general_log_print(thd, command, "%s %s", table_list.table_name, fields); - if (lower_case_table_names) - my_casedn_str(files_charset_info, table_list.table_name); - if (check_access(thd,SELECT_ACL,table_list.db,&table_list.grant.privilege, - 0, 0, test(table_list.schema_table))) + if (check_table_access(thd, SELECT_ACL, &table_list, + TRUE, UINT_MAX, FALSE)) break; - if (check_grant(thd, SELECT_ACL, &table_list, 2, UINT_MAX, 0)) - break; - /* init structures for VIEW processing */ - table_list.select_lex= &(thd->lex->select_lex); - - lex_start(thd); - mysql_reset_thd_for_next_command(thd); - - thd->lex-> - select_lex.table_list.link_in_list(&table_list, - &table_list.next_local); - thd->lex->add_to_query_tables(&table_list); - - /* switch on VIEW optimisation: do not fill temporary tables */ + /* + Turn on an optimization relevant if the underlying table + is a view: do not fill derived tables. + */ thd->lex->sql_command= SQLCOM_SHOW_FIELDS; + mysqld_list_fields(thd,&table_list,fields); thd->lex->unit.cleanup(); + /* No need to rollback statement transaction, it's not started. */ + DBUG_ASSERT(thd->transaction.stmt.is_empty()); + close_thread_tables(thd); + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); + thd->cleanup_after_query(); break; } @@ -1391,58 +1184,9 @@ 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->main_da.disable_status(); // Don't send anything back + thd->stmt_da->disable_status(); // Don't send anything back error=TRUE; // End server break; - -#ifdef REMOVED - case COM_CREATE_DB: // QQ: To be removed - { - LEX_STRING db, alias; - HA_CREATE_INFO create_info; - - status_var_increment(thd->status_var.com_stat[SQLCOM_CREATE_DB]); - if (thd->make_lex_string(&db, packet, packet_length, FALSE) || - thd->make_lex_string(&alias, db.str, db.length, FALSE) || - check_db_name(&db)) - { - my_error(ER_WRONG_DB_NAME, MYF(0), db.str ? db.str : "NULL"); - break; - } - if (check_access(thd, CREATE_ACL, db.str , 0, 1, 0, - is_schema_db(db.str, db.length))) - break; - general_log_print(thd, command, "%.*s", db.length, db.str); - bzero(&create_info, sizeof(create_info)); - mysql_create_db(thd, (lower_case_table_names == 2 ? alias.str : db.str), - &create_info, 0); - break; - } - case COM_DROP_DB: // QQ: To be removed - { - status_var_increment(thd->status_var.com_stat[SQLCOM_DROP_DB]); - LEX_STRING db; - - if (thd->make_lex_string(&db, packet, packet_length, FALSE) || - check_db_name(&db)) - { - my_error(ER_WRONG_DB_NAME, MYF(0), db.str ? db.str : "NULL"); - break; - } - if (check_access(thd, DROP_ACL, db.str, 0, 1, 0, - is_schema_db(db.str, db.length))) - break; - if (thd->locked_tables || thd->active_transaction()) - { - my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, - ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); - break; - } - general_log_write(thd, command, "%.*s", db.length, db.str); - mysql_rm_db(thd, db.str, 0, 0); - break; - } -#endif #ifndef EMBEDDED_LIBRARY case COM_BINLOG_DUMP: { @@ -1477,6 +1221,9 @@ bool dispatch_command(enum enum_server_command command, THD *thd, int not_used; status_var_increment(thd->status_var.com_stat[SQLCOM_FLUSH]); ulong options= (ulong) (uchar) packet[0]; + if (trans_commit_implicit(thd)) + break; + thd->mdl_context.release_transactional_locks(); if (check_global_access(thd,RELOAD_ACL)) break; general_log_print(thd, command, NullS); @@ -1496,13 +1243,18 @@ bool dispatch_command(enum enum_server_command command, THD *thd, res= reload_acl_and_cache(NULL, options | REFRESH_FAST, NULL, ¬_used); my_pthread_setspecific_ptr(THR_THD, thd); - if (!res) - my_ok(thd); - break; + if (res) + break; } + else #endif - if (!reload_acl_and_cache(thd, options, NULL, ¬_used)) - my_ok(thd); + if (reload_acl_and_cache(thd, options, (TABLE_LIST*) 0, ¬_used)) + break; + if (trans_commit_implicit(thd)) + break; + close_thread_tables(thd); + thd->mdl_context.release_transactional_locks(); + my_ok(thd); break; } #ifndef EMBEDDED_LIBRARY @@ -1532,7 +1284,6 @@ bool dispatch_command(enum enum_server_command command, THD *thd, DBUG_PRINT("quit",("Got shutdown command for level %u", level)); general_log_print(thd, command, NullS); my_eof(thd); - close_thread_tables(thd); // Free before kill kill_mysql(); error=TRUE; break; @@ -1567,23 +1318,13 @@ bool dispatch_command(enum enum_server_command command, THD *thd, cached_open_tables(), (uint) (queries_per_second1000 / 1000), (uint) (queries_per_second1000 % 1000)); -#ifdef SAFEMALLOC - if (sf_malloc_cur_memory) // Using SAFEMALLOC - { - char *end= buff + length; - length+= my_snprintf(end, buff_len - length - 1, - end," Memory in use: %ldK Max memory used: %ldK", - (sf_malloc_cur_memory+1023L)/1024L, - (sf_malloc_max_memory+1023L)/1024L); - } -#endif -#ifndef EMBEDDED_LIBRARY - VOID(my_net_write(net, (uchar*) buff, length)); - VOID(net_flush(net)); - thd->main_da.disable_status(); -#else +#ifdef EMBEDDED_LIBRARY /* Store the buffer in permanent memory */ my_ok(thd, 0, 0, buff); +#else + (void) my_net_write(net, (uchar*) buff, length); + (void) net_flush(net); + thd->stmt_da->disable_status(); #endif break; } @@ -1645,44 +1386,45 @@ bool dispatch_command(enum enum_server_command command, THD *thd, my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); break; } + DBUG_ASSERT(thd->derived_tables == NULL && + (thd->open_tables == NULL || + (thd->locked_tables_mode == LTM_LOCK_TABLES))); - /* report error issued during command execution */ - if (thd->killed_errno()) - { - if (! thd->main_da.is_set()) - thd->send_kill_message(); - } - if (thd->killed == THD::KILL_QUERY || thd->killed == THD::KILL_BAD_DATA) - { - thd->killed= THD::NOT_KILLED; - thd->mysys_var->abort= 0; - } - - /* If commit fails, we should be able to reset the OK status. */ - thd->main_da.can_overwrite_status= TRUE; - ha_autocommit_or_rollback(thd, thd->is_error()); - thd->main_da.can_overwrite_status= FALSE; - - thd->transaction.stmt.reset(); - - net_end_statement(thd); + /* Finalize server status flags after executing a command. */ + thd->update_server_status(); + thd->protocol->end_statement(); query_cache_end_of_result(thd); - thd->proc_info= "closing tables"; - /* Free tables */ - close_thread_tables(thd); + if (!thd->is_error() && !thd->killed_errno()) + mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_RESULT, 0, 0); + + mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_STATUS, + thd->stmt_da->is_error() ? thd->stmt_da->sql_errno() : 0, + command_name[command].str); log_slow_statement(thd); thd_proc_info(thd, "cleaning up"); - thd->set_query(NULL, 0); + thd->reset_query(); thd->command=COM_SLEEP; - VOID(pthread_mutex_lock(&LOCK_thread_count)); // For process list - thread_running--; - VOID(pthread_mutex_unlock(&LOCK_thread_count)); + 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)); + +#if defined(ENABLED_PROFILING) + thd->profiling.finish_current_query(); +#endif + if (MYSQL_QUERY_DONE_ENABLED() || MYSQL_COMMAND_DONE_ENABLED()) + { + int res __attribute__((unused)); + res= (int) thd->is_error(); + if (command == COM_QUERY) + { + MYSQL_QUERY_DONE(res); + } + MYSQL_COMMAND_DONE(res); + } DBUG_RETURN(error); } @@ -1708,8 +1450,7 @@ void log_slow_statement(THD *thd) ulonglong end_utime_of_query= thd->current_utime(); thd_proc_info(thd, "logging slow query"); - if (((end_utime_of_query - thd->utime_after_lock) > - thd->variables.long_query_time || + 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 && @@ -1813,7 +1554,8 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident, /* 'parent_lex' is used in init_query() so it must be before it. */ schema_select_lex->parent_lex= lex; schema_select_lex->init_query(); - if (!schema_select_lex->add_table_to_list(thd, table_ident, 0, 0, TL_READ)) + if (!schema_select_lex->add_table_to_list(thd, table_ident, 0, 0, TL_READ, + MDL_SHARED_READ)) DBUG_RETURN(1); lex->query_tables_last= query_tables_last; break; @@ -1824,7 +1566,7 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident, Mark this current profiling record to be discarded. We don't wish to have SHOW commands show up in profiling. */ -#if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) +#if defined(ENABLED_PROFILING) thd->profiling.discard_current_query(); #endif break; @@ -2010,16 +1752,6 @@ bool sp_process_definer(THD *thd) /** Execute command saved in thd and lex->sql_command. - Before every operation that can request a write lock for a table - wait if a global read lock exists. However do not wait if this - thread has locked tables already. No new locks can be requested - until the other locks are released. The thread that requests the - global read lock waits for write locked tables to become unlocked. - - Note that wait_if_global_read_lock() sets a protection against a new - global read lock when it succeeds. This needs to be released by - start_waiting_global_read_lock() after the operation. - @param thd Thread handle @todo @@ -2028,8 +1760,6 @@ bool sp_process_definer(THD *thd) TODO: this is workaround. right way will be move invalidating in the unlock procedure. - TODO: use check_change_password() - - JOIN is not supported yet. TODO - - SUSPEND and FOR MIGRATE are not supported yet. TODO @retval FALSE OK @@ -2041,7 +1771,6 @@ int mysql_execute_command(THD *thd) { int res= FALSE; - bool need_start_waiting= FALSE; // have protection against global read lock int up_result= 0; LEX *lex= thd->lex; /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ @@ -2056,12 +1785,12 @@ mysql_execute_command(THD *thd) /* have table map for update for multi-update statement (BUG#37051) */ bool have_table_map_for_update= FALSE; #endif - /* Saved variable value */ DBUG_ENTER("mysql_execute_command"); #ifdef WITH_PARTITION_STORAGE_ENGINE thd->work_part_info= 0; #endif + DBUG_ASSERT(thd->transaction.stmt.is_empty() || thd->in_sub_stmt); /* In many cases first table of main SELECT_LEX have special meaning => check that it is first table in global list and relink it first in @@ -2090,10 +1819,15 @@ mysql_execute_command(THD *thd) A better approach would be to reset this for any commands that is not a SHOW command or a select that only access local variables, but for now this is probably good enough. - Don't reset warnings when executing a stored routine. */ - if ((all_tables || !lex->is_single_level_stmt()) && !thd->spcont) - mysql_reset_errors(thd, 0); + if ((sql_command_flags[lex->sql_command] & CF_DIAGNOSTIC_STMT) != 0) + thd->warning_info->set_read_only(TRUE); + else + { + thd->warning_info->set_read_only(FALSE); + if (all_tables) + thd->warning_info->opt_clear_warning_info(thd->query_id); + } #ifdef HAVE_REPLICATION if (unlikely(thd->slave_thread)) @@ -2220,10 +1954,33 @@ mysql_execute_command(THD *thd) #ifdef HAVE_REPLICATION } /* endif unlikely slave */ #endif + status_var_increment(thd->status_var.com_stat[lex->sql_command]); DBUG_ASSERT(thd->transaction.stmt.modified_non_trans_table == FALSE); - + + /* + End a active transaction so that this command will have it's + own transaction and will also sync the binary log. If a DDL is + not run in it's own transaction it may simply never appear on + the slave in case the outside transaction rolls back. + */ + if (stmt_causes_implicit_commit(thd, CF_IMPLICT_COMMIT_BEGIN)) + { + /* Commit or rollback the statement transaction. */ + thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); + /* 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 + switch (lex->sql_command) { case SQLCOM_SHOW_EVENTS: @@ -2233,14 +1990,17 @@ mysql_execute_command(THD *thd) #endif case SQLCOM_SHOW_STATUS_PROC: case SQLCOM_SHOW_STATUS_FUNC: - if (!(res= check_table_access(thd, SELECT_ACL, all_tables, UINT_MAX, FALSE))) - res= execute_sqlcom_select(thd, all_tables); + 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: { system_status_var old_status_var= thd->status_var; thd->initial_status_var= &old_status_var; - if (!(res= check_table_access(thd, SELECT_ACL, all_tables, UINT_MAX, FALSE))) + if (!(res= check_table_access(thd, SELECT_ACL, all_tables, FALSE, + UINT_MAX, FALSE))) res= execute_sqlcom_select(thd, all_tables); /* Don't log SHOW STATUS commands to slow query log */ thd->server_status&= ~(SERVER_QUERY_NO_INDEX_USED | @@ -2249,11 +2009,11 @@ mysql_execute_command(THD *thd) restore status variables, as we don't want 'show status' to cause changes */ - pthread_mutex_lock(&LOCK_status); + mysql_mutex_lock(&LOCK_status); add_diff_to_status(&global_status_var, &thd->status_var, &old_status_var); thd->status_var= old_status_var; - pthread_mutex_unlock(&LOCK_status); + mysql_mutex_unlock(&LOCK_status); break; } case SQLCOM_SHOW_DATABASES: @@ -2270,29 +2030,30 @@ mysql_execute_command(THD *thd) case SQLCOM_SHOW_STORAGE_ENGINES: case SQLCOM_SHOW_PROFILE: case SQLCOM_SELECT: + { thd->status_var.last_query_cost= 0.0; + + /* + lex->exchange != NULL implies SELECT .. INTO OUTFILE and this + requires FILE_ACL access. + */ + ulong privileges_requested= lex->exchange ? SELECT_ACL | FILE_ACL : + SELECT_ACL; + if (all_tables) - { res= check_table_access(thd, - lex->exchange ? SELECT_ACL | FILE_ACL : - SELECT_ACL, - all_tables, UINT_MAX, FALSE); - } + privileges_requested, + all_tables, FALSE, UINT_MAX, FALSE); else - res= check_access(thd, - lex->exchange ? SELECT_ACL | FILE_ACL : SELECT_ACL, - any_db, 0, 0, 0, 0); + res= check_access(thd, privileges_requested, any_db, NULL, NULL, 0, 0); if (res) break; - if (!thd->locked_tables && lex->protect_against_global_read_lock && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - break; - res= execute_sqlcom_select(thd, all_tables); break; - case SQLCOM_PREPARE: + } +case SQLCOM_PREPARE: { mysql_sql_stmt_prepare(thd); break; @@ -2308,8 +2069,8 @@ mysql_execute_command(THD *thd) break; } case SQLCOM_DO: - if (check_table_access(thd, SELECT_ACL, all_tables, UINT_MAX, FALSE) || - open_and_lock_tables(thd, all_tables)) + if (check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE) + || open_and_lock_tables(thd, all_tables, TRUE, 0)) goto error; res= mysql_do(thd, *lex->insert_list); @@ -2373,7 +2134,7 @@ mysql_execute_command(THD *thd) } case SQLCOM_SHOW_PROFILES: { -#if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) +#if defined(ENABLED_PROFILING) thd->profiling.discard_current_query(); res= thd->profiling.show_profiles(); if (res) @@ -2406,6 +2167,7 @@ mysql_execute_command(THD *thd) res = show_slave_hosts(thd); break; } + case SQLCOM_SHOW_RELAYLOG_EVENTS: /* fall through */ case SQLCOM_SHOW_BINLOG_EVENTS: { if (check_global_access(thd, REPL_SLAVE_ACL)) @@ -2415,36 +2177,13 @@ mysql_execute_command(THD *thd) } #endif - case SQLCOM_BACKUP_TABLE: - { - DBUG_ASSERT(first_table == all_tables && first_table != 0); - if (check_table_access(thd, SELECT_ACL, all_tables, UINT_MAX, FALSE) || - check_global_access(thd, FILE_ACL)) - goto error; /* purecov: inspected */ - thd->enable_slow_log= opt_log_slow_admin_statements; - res = mysql_backup_table(thd, first_table); - select_lex->table_list.first= first_table; - lex->query_tables=all_tables; - break; - } - case SQLCOM_RESTORE_TABLE: - { - DBUG_ASSERT(first_table == all_tables && first_table != 0); - if (check_table_access(thd, INSERT_ACL, all_tables, UINT_MAX, FALSE) || - check_global_access(thd, FILE_ACL)) - goto error; /* purecov: inspected */ - thd->enable_slow_log= opt_log_slow_admin_statements; - res = mysql_restore_table(thd, first_table); - select_lex->table_list.first= first_table; - lex->query_tables=all_tables; - break; - } case SQLCOM_ASSIGN_TO_KEYCACHE: { DBUG_ASSERT(first_table == all_tables && first_table != 0); if (check_access(thd, INDEX_ACL, first_table->db, - &first_table->grant.privilege, 0, 0, - test(first_table->schema_table))) + &first_table->grant.privilege, + &first_table->grant.m_internal, + 0, 0)) goto error; res= mysql_assign_to_keycache(thd, first_table, &lex->ident); break; @@ -2453,8 +2192,9 @@ mysql_execute_command(THD *thd) { DBUG_ASSERT(first_table == all_tables && first_table != 0); if (check_access(thd, INDEX_ACL, first_table->db, - &first_table->grant.privilege, 0, 0, - test(first_table->schema_table))) + &first_table->grant.privilege, + &first_table->grant.m_internal, + 0, 0)) goto error; res = mysql_preload_keys(thd, first_table); break; @@ -2464,9 +2204,9 @@ mysql_execute_command(THD *thd) { if (check_global_access(thd, SUPER_ACL)) goto error; - pthread_mutex_lock(&LOCK_active_mi); + mysql_mutex_lock(&LOCK_active_mi); res = change_master(thd,active_mi); - pthread_mutex_unlock(&LOCK_active_mi); + mysql_mutex_unlock(&LOCK_active_mi); break; } case SQLCOM_SHOW_SLAVE_STAT: @@ -2474,7 +2214,7 @@ mysql_execute_command(THD *thd) /* Accept one of two privileges */ if (check_global_access(thd, SUPER_ACL | REPL_CLIENT_ACL)) goto error; - pthread_mutex_lock(&LOCK_active_mi); + mysql_mutex_lock(&LOCK_active_mi); if (active_mi != NULL) { res = show_master_info(thd, active_mi); @@ -2485,7 +2225,7 @@ mysql_execute_command(THD *thd) WARN_NO_MASTER_INFO, ER(WARN_NO_MASTER_INFO)); my_ok(thd); } - pthread_mutex_unlock(&LOCK_active_mi); + mysql_mutex_unlock(&LOCK_active_mi); break; } case SQLCOM_SHOW_MASTER_STAT: @@ -2497,13 +2237,6 @@ mysql_execute_command(THD *thd) break; } - case SQLCOM_LOAD_MASTER_DATA: // sync with master - if (check_global_access(thd, SUPER_ACL)) - goto error; - if (end_active_trans(thd)) - goto error; - res = load_master_data(thd); - break; #endif /* HAVE_REPLICATION */ case SQLCOM_SHOW_ENGINE_STATUS: { @@ -2519,51 +2252,13 @@ mysql_execute_command(THD *thd) res = ha_show_status(thd, lex->create_info.db_type, HA_ENGINE_MUTEX); break; } -#ifdef HAVE_REPLICATION - case SQLCOM_LOAD_MASTER_TABLE: - { - DBUG_ASSERT(first_table == all_tables && first_table != 0); - DBUG_ASSERT(first_table->db); /* Must be set in the parser */ - - if (check_access(thd, CREATE_ACL, first_table->db, - &first_table->grant.privilege, 0, 0, - test(first_table->schema_table))) - goto error; /* purecov: inspected */ - /* Check that the first table has CREATE privilege */ - if (check_grant(thd, CREATE_ACL, all_tables, 0, 1, 0)) - goto error; - - pthread_mutex_lock(&LOCK_active_mi); - /* - fetch_master_table will send the error to the client on failure. - Give error if the table already exists. - */ - if (!fetch_master_table(thd, first_table->db, first_table->table_name, - active_mi, 0, 0)) - { - my_ok(thd); - } - pthread_mutex_unlock(&LOCK_active_mi); - break; - } -#endif /* HAVE_REPLICATION */ - case SQLCOM_CREATE_TABLE: { - /* If CREATE TABLE of non-temporary table, do implicit commit */ - if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE)) - { - if (end_active_trans(thd)) - { - res= -1; - break; - } - } DBUG_ASSERT(first_table == all_tables && first_table != 0); bool link_to_local; - // Skip first table, which is the table we are creating - TABLE_LIST *create_table= lex->unlink_first_table(&link_to_local); - TABLE_LIST *select_tables= lex->query_tables; + TABLE_LIST *create_table= first_table; + TABLE_LIST *select_tables= lex->create_last_non_select_table->next_global; + /* Code below (especially in mysql_create_table() and select_create methods) may modify HA_CREATE_INFO structure in LEX, so we have to @@ -2613,25 +2308,7 @@ mysql_execute_command(THD *thd) create_info.default_table_charset= create_info.table_charset; create_info.table_charset= 0; } - /* - The create-select command will open and read-lock the select table - and then create, open and write-lock the new table. If a global - read lock steps in, we get a deadlock. The write lock waits for - the global read lock, while the global read lock waits for the - select table to be closed. So we wait until the global readlock is - gone before starting both steps. Note that - wait_if_global_read_lock() sets a protection against a new global - read lock when it succeeds. This needs to be released by - start_waiting_global_read_lock(). We protect the normal CREATE - TABLE in the same way. That way we avoid that a new table is - created during a gobal read lock. - */ - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - { - res= 1; - goto end_with_restore_list; - } + #ifdef WITH_PARTITION_STORAGE_ENGINE { partition_info *part_info= thd->lex->part_info; @@ -2643,6 +2320,10 @@ mysql_execute_command(THD *thd) thd->work_part_info= part_info; } #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; @@ -2700,71 +2381,37 @@ mysql_execute_command(THD *thd) goto end_with_restore_list; } - if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) - { - lex->link_first_table_back(create_table, link_to_local); - create_table->create= TRUE; - /* Base table and temporary table are not in the same name space. */ - create_table->skip_temporary= 1; - } - - if (!(res= open_and_lock_tables(thd, lex->query_tables))) + if (!(res= open_and_lock_tables(thd, lex->query_tables, TRUE, 0))) { - /* - Is table which we are changing used somewhere in other parts - of query - */ - if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) + /* The table already exists */ + if (create_table->table) { - TABLE_LIST *duplicate; - create_table= lex->unlink_first_table(&link_to_local); - - if (create_table->view) + if (create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS) { - 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; - } - goto end_with_restore_list; + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_TABLE_EXISTS_ERROR, + ER(ER_TABLE_EXISTS_ERROR), + create_info.alias); + my_ok(thd); } - - if ((duplicate= unique_table(thd, create_table, select_tables, 0))) + else { - update_non_unique_table_error(create_table, "CREATE", duplicate); + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), create_info.alias); res= 1; - goto end_with_restore_list; - } - } - /* If we create merge table, we have to test tables in merge, too */ - if (create_info.used_fields & HA_CREATE_USED_UNION) - { - TABLE_LIST *tab; - for (tab= create_info.merge_list.first; - tab; - tab= tab->next_local) - { - TABLE_LIST *duplicate; - if ((duplicate= unique_table(thd, tab, select_tables, 0))) - { - update_non_unique_table_error(tab, "CREATE", duplicate); - res= 1; - goto end_with_restore_list; - } } + goto end_with_restore_list; } + /* + Remove target table from main select and name resolution + context. This can't be done earlier as it will break view merging in + statements like "CREATE TABLE IF NOT EXISTS existing_view SELECT". + */ + lex->unlink_first_table(&link_to_local); + /* So that CREATE TEMPORARY TABLE gets to binlog at commit/rollback */ if (create_info.options & HA_LEX_CREATE_TMP_TABLE) - thd->options|= OPTION_KEEP_LOG; + thd->variables.option_bits|= OPTION_KEEP_LOG; /* select_create is currently not re-execution friendly and @@ -2785,33 +2432,33 @@ mysql_execute_command(THD *thd) res= handle_select(thd, lex, result, 0); delete result; } - } - else if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE)) - create_table= lex->unlink_first_table(&link_to_local); + 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->options|= OPTION_KEEP_LOG; + thd->variables.option_bits|= OPTION_KEEP_LOG; /* regular create */ if (create_info.options & HA_LEX_CREATE_TABLE_LIKE) + { + /* CREATE TABLE ... LIKE ... */ res= mysql_create_like_table(thd, create_table, select_tables, &create_info); + } else { - res= mysql_create_table(thd, create_table->db, - create_table->table_name, &create_info, - &alter_info, 0, 0); + /* Regular CREATE TABLE */ + res= mysql_create_table(thd, create_table, + &create_info, &alter_info); } if (!res) - my_ok(thd); + my_ok(thd); } - /* put tables back for PS rexecuting */ end_with_restore_list: - lex->link_first_table_back(create_table, link_to_local); break; } case SQLCOM_CREATE_INDEX: @@ -2836,8 +2483,6 @@ end_with_restore_list: DBUG_ASSERT(first_table == all_tables && first_table != 0); if (check_one_table_access(thd, INDEX_ACL, all_tables)) goto error; /* purecov: inspected */ - if (end_active_trans(thd)) - goto error; /* Currently CREATE INDEX or DROP INDEX cause a full table rebuild and thus classify as slow administrative statements just like @@ -2858,9 +2503,9 @@ end_with_restore_list: #ifdef HAVE_REPLICATION case SQLCOM_SLAVE_START: { - pthread_mutex_lock(&LOCK_active_mi); + mysql_mutex_lock(&LOCK_active_mi); start_slave(thd,active_mi,1 /* net report*/); - pthread_mutex_unlock(&LOCK_active_mi); + mysql_mutex_unlock(&LOCK_active_mi); break; } case SQLCOM_SLAVE_STOP: @@ -2877,98 +2522,21 @@ end_with_restore_list: To prevent that, refuse SLAVE STOP if the client thread has locked tables */ - if (thd->locked_tables || thd->active_transaction() || thd->global_read_lock) + 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; } { - pthread_mutex_lock(&LOCK_active_mi); + mysql_mutex_lock(&LOCK_active_mi); stop_slave(thd,active_mi,1/* net report*/); - pthread_mutex_unlock(&LOCK_active_mi); + mysql_mutex_unlock(&LOCK_active_mi); break; } #endif /* HAVE_REPLICATION */ - case SQLCOM_ALTER_TABLE: - DBUG_ASSERT(first_table == all_tables && first_table != 0); - { - ulong priv=0; - ulong priv_needed= ALTER_ACL; - /* - Code in mysql_alter_table() may modify its HA_CREATE_INFO argument, - so we have to use a copy of this structure to make execution - prepared statement- safe. A shallow copy is enough as no memory - referenced from this structure will be modified. - */ - HA_CREATE_INFO create_info(lex->create_info); - Alter_info alter_info(lex->alter_info, thd->mem_root); - - if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */ - goto error; - /* - We also require DROP priv for ALTER TABLE ... DROP PARTITION, as well - as for RENAME TO, as being done by SQLCOM_RENAME_TABLE - */ - if (alter_info.flags & (ALTER_DROP_PARTITION | ALTER_RENAME)) - priv_needed|= DROP_ACL; - - /* Must be set in the parser */ - DBUG_ASSERT(select_lex->db); - if (check_access(thd, priv_needed, first_table->db, - &first_table->grant.privilege, 0, 0, - test(first_table->schema_table)) || - check_access(thd,INSERT_ACL | CREATE_ACL,select_lex->db,&priv,0,0, - is_schema_db(select_lex->db))|| - check_merge_table_access(thd, first_table->db, - create_info.merge_list.first)) - goto error; /* purecov: inspected */ - if (check_grant(thd, priv_needed, all_tables, 0, UINT_MAX, 0)) - goto error; - if (lex->name.str && !test_all_bits(priv,INSERT_ACL | CREATE_ACL)) - { // Rename of table - TABLE_LIST tmp_table; - bzero((char*) &tmp_table,sizeof(tmp_table)); - tmp_table.table_name= lex->name.str; - tmp_table.db=select_lex->db; - tmp_table.grant.privilege=priv; - if (check_grant(thd, INSERT_ACL | CREATE_ACL, &tmp_table, 0, - UINT_MAX, 0)) - goto error; - } - - /* Don't yet allow changing of symlinks with ALTER TABLE */ - if (create_info.data_file_name) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), - "DATA DIRECTORY"); - if (create_info.index_file_name) - push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED), - "INDEX DIRECTORY"); - create_info.data_file_name= create_info.index_file_name= NULL; - /* ALTER TABLE ends previous transaction */ - if (end_active_trans(thd)) - goto error; - - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - { - res= 1; - break; - } - - thd->enable_slow_log= opt_log_slow_admin_statements; - res= mysql_alter_table(thd, select_lex->db, lex->name.str, - &create_info, - first_table, - &alter_info, - select_lex->order_list.elements, - (ORDER *) select_lex->order_list.first, - lex->ignore); - break; - } case SQLCOM_RENAME_TABLE: { DBUG_ASSERT(first_table == all_tables && first_table != 0); @@ -2976,10 +2544,13 @@ end_with_restore_list: for (table= first_table; table; table= table->next_local->next_local) { if (check_access(thd, ALTER_ACL | DROP_ACL, table->db, - &table->grant.privilege,0,0, test(table->schema_table)) || - check_access(thd, INSERT_ACL | CREATE_ACL, table->next_local->db, - &table->next_local->grant.privilege, 0, 0, - test(table->next_local->schema_table))) + &table->grant.privilege, + &table->grant.m_internal, + 0, 0) || + check_access(thd, INSERT_ACL | CREATE_ACL, table->next_local->db, + &table->next_local->grant.privilege, + &table->next_local->grant.m_internal, + 0, 0)) goto error; TABLE_LIST old_list, new_list; /* @@ -2988,14 +2559,15 @@ end_with_restore_list: */ old_list= table[0]; new_list= table->next_local[0]; - if (check_grant(thd, ALTER_ACL | DROP_ACL, &old_list, 0, 1, 0) || + if (check_grant(thd, ALTER_ACL | DROP_ACL, &old_list, FALSE, 1, FALSE) || (!test_all_bits(table->next_local->grant.privilege, INSERT_ACL | CREATE_ACL) && - check_grant(thd, INSERT_ACL | CREATE_ACL, &new_list, 0, 1, 0))) + check_grant(thd, INSERT_ACL | CREATE_ACL, &new_list, FALSE, 1, + FALSE))) goto error; } - if (end_active_trans(thd) || mysql_rename_tables(thd, first_table, 0)) + if (mysql_rename_tables(thd, first_table, 0)) goto error; break; } @@ -3022,11 +2594,54 @@ end_with_restore_list: goto error; #else { - /* Ignore temporary tables if this is "SHOW CREATE VIEW" */ + /* + Access check: + SHOW CREATE TABLE require any privileges on the table level (ie + effecting all columns in the table). + SHOW CREATE VIEW require the SHOW_VIEW and SELECT ACLs on the table + level. + NOTE: SHOW_VIEW ACL is checked when the view is created. + */ + + DBUG_PRINT("debug", ("lex->only_view: %d, table: %s.%s", + lex->only_view, + first_table->db, first_table->table_name)); if (lex->only_view) - first_table->skip_temporary= 1; - if (check_show_create_table_access(thd, first_table)) - goto error; + { + if (check_table_access(thd, SELECT_ACL, first_table, FALSE, 1, FALSE)) + { + DBUG_PRINT("debug", ("check_table_access failed")); + my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), + "SHOW", thd->security_ctx->priv_user, + thd->security_ctx->host_or_ip, first_table->alias); + goto error; + } + DBUG_PRINT("debug", ("check_table_access succeeded")); + + /* Ignore temporary tables if this is "SHOW CREATE VIEW" */ + first_table->open_type= OT_BASE_ONLY; + + } + else + { + /* + The fact that check_some_access() returned FALSE does not mean that + access is granted. We need to check if first_table->grant.privilege + contains any table-specific privilege. + */ + DBUG_PRINT("debug", ("first_table->grant.privilege: %lx", + first_table->grant.privilege)); + if (check_some_access(thd, SHOW_CREATE_TABLE_ACLS, first_table) || + (first_table->grant.privilege & SHOW_CREATE_TABLE_ACLS) == 0) + { + my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), + "SHOW", thd->security_ctx->priv_user, + thd->security_ctx->host_or_ip, first_table->alias); + goto error; + } + } + + /* Access is granted. Execute the command. */ res= mysqld_show_create(thd, first_table); break; } @@ -3034,96 +2649,22 @@ end_with_restore_list: case SQLCOM_CHECKSUM: { DBUG_ASSERT(first_table == all_tables && first_table != 0); - if (check_table_access(thd, SELECT_ACL | EXTRA_ACL, all_tables, - UINT_MAX, FALSE)) - goto error; /* purecov: inspected */ - res = mysql_checksum_table(thd, first_table, &lex->check_opt); - break; - } - case SQLCOM_REPAIR: - { - DBUG_ASSERT(first_table == all_tables && first_table != 0); - if (check_table_access(thd, SELECT_ACL | INSERT_ACL, all_tables, - UINT_MAX, FALSE)) - goto error; /* purecov: inspected */ - thd->enable_slow_log= opt_log_slow_admin_statements; - res= mysql_repair_table(thd, first_table, &lex->check_opt); - /* ! we write after unlocking the table */ - if (!res && !lex->no_write_to_binlog) - { - /* - Presumably, REPAIR and binlog writing doesn't require synchronization - */ - res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - } - select_lex->table_list.first= first_table; - lex->query_tables=all_tables; - break; - } - case SQLCOM_CHECK: - { - DBUG_ASSERT(first_table == all_tables && first_table != 0); - if (check_table_access(thd, SELECT_ACL | EXTRA_ACL , all_tables, - UINT_MAX, FALSE)) + if (check_table_access(thd, SELECT_ACL, all_tables, + FALSE, UINT_MAX, FALSE)) goto error; /* purecov: inspected */ - thd->enable_slow_log= opt_log_slow_admin_statements; - res = mysql_check_table(thd, first_table, &lex->check_opt); - select_lex->table_list.first= first_table; - lex->query_tables=all_tables; - break; - } - case SQLCOM_ANALYZE: - { - DBUG_ASSERT(first_table == all_tables && first_table != 0); - if (check_table_access(thd, SELECT_ACL | INSERT_ACL, all_tables, - UINT_MAX, FALSE)) - goto error; /* purecov: inspected */ - thd->enable_slow_log= opt_log_slow_admin_statements; - res= mysql_analyze_table(thd, first_table, &lex->check_opt); - /* ! we write after unlocking the table */ - if (!res && !lex->no_write_to_binlog) - { - /* - Presumably, ANALYZE and binlog writing doesn't require synchronization - */ - res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - } - select_lex->table_list.first= first_table; - lex->query_tables=all_tables; - break; - } - case SQLCOM_OPTIMIZE: - { - DBUG_ASSERT(first_table == all_tables && first_table != 0); - if (check_table_access(thd, SELECT_ACL | INSERT_ACL, all_tables, - UINT_MAX, FALSE)) - goto error; /* purecov: inspected */ - thd->enable_slow_log= opt_log_slow_admin_statements; - res= (specialflag & (SPECIAL_SAFE_MODE | SPECIAL_NO_NEW_FUNC)) ? - mysql_recreate_table(thd, first_table) : - mysql_optimize_table(thd, first_table, &lex->check_opt); - /* ! we write after unlocking the table */ - if (!res && !lex->no_write_to_binlog) - { - /* - Presumably, OPTIMIZE and binlog writing doesn't require synchronization - */ - res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - } - select_lex->table_list.first= first_table; - lex->query_tables=all_tables; + res = mysql_checksum_table(thd, first_table, &lex->check_opt); break; } case SQLCOM_UPDATE: + { + ha_rows found= 0, updated= 0; DBUG_ASSERT(first_table == all_tables && first_table != 0); if (update_precheck(thd, all_tables)) break; - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - goto error; DBUG_ASSERT(select_lex->offset_limit == 0); unit->set_limit(select_lex); + MYSQL_UPDATE_START(thd->query()); res= (up_result= mysql_update(thd, all_tables, select_lex->item_list, lex->value_list, @@ -3131,11 +2672,14 @@ end_with_restore_list: select_lex->order_list.elements, select_lex->order_list.first, unit->select_limit_cnt, - lex->duplicates, lex->ignore)); + lex->duplicates, lex->ignore, + &found, &updated)); + MYSQL_UPDATE_DONE(res, found, updated); /* mysql_update return 2 if we need to switch to multi-update */ if (up_result != 2) break; /* Fall through */ + } case SQLCOM_UPDATE_MULTI: { DBUG_ASSERT(first_table == all_tables && first_table != 0); @@ -3148,15 +2692,6 @@ end_with_restore_list: else res= 0; - /* - Protection might have already been risen if its a fall through - from the SQLCOM_UPDATE case above. - */ - if (!thd->locked_tables && - lex->sql_command == SQLCOM_UPDATE_MULTI && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - goto error; - res= mysql_multi_update_prepare(thd); #ifdef HAVE_REPLICATION @@ -3192,13 +2727,31 @@ end_with_restore_list: #ifdef HAVE_REPLICATION } /* unlikely */ #endif - - res= mysql_multi_update(thd, all_tables, - &select_lex->item_list, - &lex->value_list, - select_lex->where, - select_lex->options, - lex->duplicates, lex->ignore, unit, select_lex); + { + multi_update *result_obj; + MYSQL_MULTI_UPDATE_START(thd->query()); + res= mysql_multi_update(thd, all_tables, + &select_lex->item_list, + &lex->value_list, + select_lex->where, + select_lex->options, + lex->duplicates, + lex->ignore, + unit, + select_lex, + &result_obj); + if (result_obj) + { + MYSQL_MULTI_UPDATE_DONE(res, result_obj->num_found(), + result_obj->num_updated()); + res= FALSE; /* Ignore errors here */ + delete result_obj; + } + else + { + MYSQL_MULTI_UPDATE_DONE(1, 0, 0); + } + } break; } case SQLCOM_REPLACE: @@ -3242,17 +2795,11 @@ end_with_restore_list: if ((res= insert_precheck(thd, all_tables))) break; - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - { - res= 1; - break; - } - + MYSQL_INSERT_START(thd->query()); res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values, lex->update_list, lex->value_list, lex->duplicates, lex->ignore); - + MYSQL_INSERT_DONE(res, (ulong) thd->get_row_count_func()); /* If we have inserted into a VIEW, and the base table has AUTO_INCREMENT column, but this column is not accessible through @@ -3291,15 +2838,9 @@ end_with_restore_list: unit->set_limit(select_lex); - if (! thd->locked_tables && - ! (need_start_waiting= ! wait_if_global_read_lock(thd, 0, 1))) - { - res= 1; - break; - } - - if (!(res= open_and_lock_tables(thd, all_tables))) + if (!(res= open_and_lock_tables(thd, all_tables, TRUE, 0))) { + MYSQL_INSERT_SELECT_START(thd->query()); /* Skip first table, which is the table we are inserting in */ TABLE_LIST *second_table= first_table->next_local; select_lex->table_list.first= second_table; @@ -3333,9 +2874,9 @@ end_with_restore_list: delete sel_result; } /* revert changes for SP */ + MYSQL_INSERT_SELECT_DONE(res, (ulong) thd->get_row_count_func()); select_lex->table_list.first= first_table; } - /* If we have inserted into a VIEW, and the base table has AUTO_INCREMENT column, but this column is not accessible through @@ -3348,29 +2889,6 @@ end_with_restore_list: break; } - case SQLCOM_TRUNCATE: - if (end_active_trans(thd)) - { - res= -1; - break; - } - DBUG_ASSERT(first_table == all_tables && first_table != 0); - if (check_one_table_access(thd, DROP_ACL, all_tables)) - goto error; - /* - Don't allow this within a transaction because we want to use - re-generate table - */ - if (thd->locked_tables || thd->active_transaction()) - { - my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, - ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); - goto error; - } - if (!(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - goto error; - res= mysql_truncate(thd, first_table, 0); - break; case SQLCOM_DELETE: { DBUG_ASSERT(first_table == all_tables && first_table != 0); @@ -3379,17 +2897,11 @@ end_with_restore_list: DBUG_ASSERT(select_lex->offset_limit == 0); unit->set_limit(select_lex); - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - { - res= 1; - break; - } - + 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, - FALSE); + unit->select_limit_cnt, select_lex->options); + MYSQL_DELETE_DONE(res, (ulong) thd->get_row_count_func()); break; } case SQLCOM_DELETE_MULTI: @@ -3398,13 +2910,6 @@ end_with_restore_list: TABLE_LIST *aux_tables= thd->lex->auxiliary_table_list.first; multi_delete *del_result; - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - { - res= 1; - break; - } - if ((res= multi_delete_precheck(thd, all_tables))) break; @@ -3415,11 +2920,15 @@ end_with_restore_list: goto error; thd_proc_info(thd, "init"); - if ((res= open_and_lock_tables(thd, all_tables))) + if ((res= open_and_lock_tables(thd, all_tables, TRUE, 0))) break; + MYSQL_MULTI_DELETE_START(thd->query()); if ((res= mysql_multi_delete_prepare(thd))) + { + MYSQL_MULTI_DELETE_DONE(1, 0); goto error; + } if (!thd->is_fatal_error && (del_result= new multi_delete(aux_tables, lex->table_count))) @@ -3431,17 +2940,21 @@ end_with_restore_list: select_lex->where, 0, (ORDER *)NULL, (ORDER *)NULL, (Item *)NULL, (ORDER *)NULL, - (select_lex->options | thd->options | + (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(); + del_result->abort_result_set(); delete del_result; } else + { res= TRUE; // Error + MYSQL_MULTI_DELETE_DONE(1, 0); + } break; } case SQLCOM_DROP_TABLE: @@ -3449,17 +2962,15 @@ end_with_restore_list: DBUG_ASSERT(first_table == all_tables && first_table != 0); if (!lex->drop_temporary) { - if (check_table_access(thd, DROP_ACL, all_tables, UINT_MAX, FALSE)) + if (check_table_access(thd, DROP_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; /* purecov: inspected */ - if (end_active_trans(thd)) - goto error; } else { /* So that DROP TEMPORARY TABLE gets to binlog at commit/rollback */ - thd->options|= OPTION_KEEP_LOG; + thd->variables.option_bits|= OPTION_KEEP_LOG; } - /* DDL and binlog write order protected by LOCK_open */ + /* DDL and binlog write order are protected by metadata locks. */ res= mysql_rm_table(thd, first_table, lex->drop_if_exists, lex->drop_temporary); } @@ -3483,9 +2994,6 @@ end_with_restore_list: case SQLCOM_SHOW_PRIVILEGES: res= mysqld_show_privileges(thd); break; - case SQLCOM_SHOW_COLUMN_TYPES: - res= mysqld_show_column_types(thd); - break; case SQLCOM_SHOW_ENGINE_LOGS: #ifdef DONT_ALLOW_SHOW_COMMANDS my_message(ER_NOT_ALLOWED_COMMAND, ER(ER_NOT_ALLOWED_COMMAND), @@ -3493,7 +3001,7 @@ end_with_restore_list: goto error; #else { - if (check_access(thd, FILE_ACL, any_db,0,0,0,0)) + if (check_access(thd, FILE_ACL, any_db, NULL, NULL, 0, 0)) goto error; res= ha_show_status(thd, lex->create_info.db_type, HA_ENGINE_LOGS); break; @@ -3529,10 +3037,6 @@ end_with_restore_list: if (check_one_table_access(thd, privilege, all_tables)) goto error; - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - goto error; - res= mysql_load(thd, lex->exchange, first_table, lex->field_list, lex->update_list, lex->value_list, lex->duplicates, lex->ignore, (bool) lex->local_file); @@ -3543,17 +3047,9 @@ end_with_restore_list: { List<set_var_base> *lex_var_list= &lex->var_list; - if (lex->autocommit && end_active_trans(thd)) - goto error; - - if ((check_table_access(thd, SELECT_ACL, all_tables, UINT_MAX, FALSE) || - open_and_lock_tables(thd, all_tables))) - goto error; - if (lex->one_shot_set && not_all_support_one_shot(lex_var_list)) - { - my_error(ER_RESERVED_SYNTAX, MYF(0), "SET ONE_SHOT"); + if ((check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE) + || open_and_lock_tables(thd, all_tables, TRUE, 0))) goto error; - } if (!(res= sql_set_variables(thd, lex_var_list))) { /* @@ -3585,52 +3081,67 @@ end_with_restore_list: done FLUSH TABLES WITH READ LOCK + BEGIN. If this assumption becomes false, mysqldump will not work. */ - unlock_locked_tables(thd); - if (thd->options & OPTION_TABLE_LOCK) + if (thd->variables.option_bits & OPTION_TABLE_LOCK) { - end_active_trans(thd); - thd->options&= ~(OPTION_TABLE_LOCK); + res= trans_commit_implicit(thd); + thd->locked_tables_list.unlock_locked_tables(thd); + thd->mdl_context.release_transactional_locks(); + thd->variables.option_bits&= ~(OPTION_TABLE_LOCK); } - if (thd->global_read_lock) - unlock_global_read_lock(thd); + if (thd->global_read_lock.is_acquired()) + thd->global_read_lock.unlock_global_read_lock(thd); + if (res) + goto error; my_ok(thd); break; case SQLCOM_LOCK_TABLES: - unlock_locked_tables(thd); - /* we must end the trasaction first, regardless of anything */ - if (end_active_trans(thd)) + /* We must end the transaction first, regardless of anything */ + res= trans_commit_implicit(thd); + thd->locked_tables_list.unlock_locked_tables(thd); + /* Release transactional metadata locks. */ + thd->mdl_context.release_transactional_locks(); + if (res) goto error; if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables, - UINT_MAX, FALSE)) - goto error; - if (lex->protect_against_global_read_lock && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) + FALSE, UINT_MAX, FALSE)) goto error; + + thd->variables.option_bits|= OPTION_TABLE_LOCK; thd->in_lock_tables=1; - thd->options|= OPTION_TABLE_LOCK; - if (!(res= simple_open_n_lock_tables(thd, all_tables))) { -#ifdef HAVE_QUERY_CACHE - if (thd->variables.query_cache_wlock_invalidate) - query_cache.invalidate_locked_for_write(first_table); -#endif /*HAVE_QUERY_CACHE*/ - thd->locked_tables=thd->lock; - thd->lock=0; - my_ok(thd); + Lock_tables_prelocking_strategy lock_tables_prelocking_strategy; + + res= (open_and_lock_tables(thd, all_tables, FALSE, 0, + &lock_tables_prelocking_strategy) || + thd->locked_tables_list.init_locked_tables(thd)); } - else + + thd->in_lock_tables= 0; + + if (res) { - /* + trans_rollback_stmt(thd); + /* Need to end the current transaction, so the storage engine (InnoDB) can free its locks if LOCK TABLES locked some tables before finding that it can't lock a table in its list */ - ha_autocommit_or_rollback(thd, 1); - end_active_trans(thd); - thd->options&= ~(OPTION_TABLE_LOCK); + trans_commit_implicit(thd); + /* Close tables and release metadata locks. */ + close_thread_tables(thd); + DBUG_ASSERT(!thd->locked_tables_mode); + thd->mdl_context.release_transactional_locks(); + thd->variables.option_bits&= ~(OPTION_TABLE_LOCK); + } + else + { +#ifdef HAVE_QUERY_CACHE + if (thd->variables.query_cache_wlock_invalidate) + query_cache.invalidate_locked_for_write(first_table); +#endif /*HAVE_QUERY_CACHE*/ + my_ok(thd); } - thd->in_lock_tables=0; break; case SQLCOM_CREATE_DB: { @@ -3640,11 +3151,6 @@ end_with_restore_list: prepared statement- safe. */ HA_CREATE_INFO create_info(lex->create_info); - if (end_active_trans(thd)) - { - res= -1; - break; - } char *alias; if (!(alias=thd->strmake(lex->name.str, lex->name.length)) || check_db_name(&lex->name)) @@ -3668,8 +3174,7 @@ end_with_restore_list: break; } #endif - if (check_access(thd,CREATE_ACL,lex->name.str, 0, 1, 0, - is_schema_db(lex->name.str, lex->name.length))) + 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); @@ -3677,11 +3182,6 @@ end_with_restore_list: } case SQLCOM_DROP_DB: { - if (end_active_trans(thd)) - { - res= -1; - break; - } if (check_db_name(&lex->name)) { my_error(ER_WRONG_DB_NAME, MYF(0), lex->name.str); @@ -3703,26 +3203,14 @@ end_with_restore_list: break; } #endif - if (check_access(thd,DROP_ACL,lex->name.str,0,1,0, - is_schema_db(lex->name.str, lex->name.length))) + if (check_access(thd, DROP_ACL, lex->name.str, NULL, NULL, 1, 0)) break; - if (thd->locked_tables || thd->active_transaction()) - { - my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, - ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); - goto error; - } res= mysql_rm_db(thd, lex->name.str, lex->drop_if_exists, 0); break; } case SQLCOM_ALTER_DB_UPGRADE: { LEX_STRING *db= & lex->name; - if (end_active_trans(thd)) - { - res= 1; - break; - } #ifdef HAVE_REPLICATION if (thd->slave_thread && (!rpl_filter->db_ok(db->str) || @@ -3738,24 +3226,13 @@ end_with_restore_list: my_error(ER_WRONG_DB_NAME, MYF(0), db->str); break; } - if (check_access(thd, ALTER_ACL, db->str, 0, 1, 0, - is_schema_db(db->str, db->length)) || - check_access(thd, DROP_ACL, db->str, 0, 1, 0, - is_schema_db(db->str, db->length)) || - check_access(thd, CREATE_ACL, db->str, 0, 1, 0, - is_schema_db(db->str, db->length))) + if (check_access(thd, ALTER_ACL, db->str, NULL, NULL, 1, 0) || + check_access(thd, DROP_ACL, db->str, NULL, NULL, 1, 0) || + check_access(thd, CREATE_ACL, db->str, NULL, NULL, 1, 0)) { res= 1; break; } - if (thd->locked_tables || thd->active_transaction()) - { - res= 1; - my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, - ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); - goto error; - } - res= mysql_upgrade_db(thd, db); if (!res) my_ok(thd); @@ -3786,15 +3263,8 @@ end_with_restore_list: break; } #endif - if (check_access(thd, ALTER_ACL, db->str, 0, 1, 0, - is_schema_db(db->str, db->length))) + if (check_access(thd, ALTER_ACL, db->str, NULL, NULL, 1, 0)) break; - if (thd->locked_tables || thd->active_transaction()) - { - my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, - ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); - goto error; - } res= mysql_alter_db(thd, db->str, &create_info); break; } @@ -3872,7 +3342,7 @@ end_with_restore_list: #endif case SQLCOM_CREATE_FUNCTION: // UDF function { - if (check_access(thd,INSERT_ACL,"mysql",0,1,0,0)) + if (check_access(thd, INSERT_ACL, "mysql", NULL, NULL, 1, 0)) break; #ifdef HAVE_DLOPEN if (!(res = mysql_create_function(thd, &lex->udf))) @@ -3886,11 +3356,9 @@ end_with_restore_list: #ifndef NO_EMBEDDED_ACCESS_CHECKS case SQLCOM_CREATE_USER: { - if (check_access(thd, INSERT_ACL, "mysql", 0, 1, 1, 0) && + if (check_access(thd, INSERT_ACL, "mysql", NULL, NULL, 1, 1) && check_global_access(thd,CREATE_USER_ACL)) break; - if (end_active_trans(thd)) - goto error; /* Conditionally writes to binlog */ if (!(res= mysql_create_user(thd, lex->users_list))) my_ok(thd); @@ -3898,11 +3366,9 @@ end_with_restore_list: } case SQLCOM_DROP_USER: { - if (check_access(thd, DELETE_ACL, "mysql", 0, 1, 1, 0) && + if (check_access(thd, DELETE_ACL, "mysql", NULL, NULL, 1, 1) && check_global_access(thd,CREATE_USER_ACL)) break; - if (end_active_trans(thd)) - goto error; /* Conditionally writes to binlog */ if (!(res= mysql_drop_user(thd, lex->users_list))) my_ok(thd); @@ -3910,11 +3376,9 @@ end_with_restore_list: } case SQLCOM_RENAME_USER: { - if (check_access(thd, UPDATE_ACL, "mysql", 0, 1, 1, 0) && + if (check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 1) && check_global_access(thd,CREATE_USER_ACL)) break; - if (end_active_trans(thd)) - goto error; /* Conditionally writes to binlog */ if (!(res= mysql_rename_user(thd, lex->users_list))) my_ok(thd); @@ -3922,9 +3386,7 @@ end_with_restore_list: } case SQLCOM_REVOKE_ALL: { - if (end_active_trans(thd)) - goto error; - if (check_access(thd, UPDATE_ACL, "mysql", 0, 1, 1, 0) && + if (check_access(thd, UPDATE_ACL, "mysql", NULL, NULL, 1, 1) && check_global_access(thd,CREATE_USER_ACL)) break; @@ -3939,16 +3401,12 @@ end_with_restore_list: case SQLCOM_REVOKE: case SQLCOM_GRANT: { - if (end_active_trans(thd)) - goto error; - - if (check_access(thd, lex->grant | lex->grant_tot_col | GRANT_ACL, - first_table ? first_table->db : select_lex->db, - first_table ? &first_table->grant.privilege : 0, - first_table ? 0 : 1, 0, - first_table ? (bool) first_table->schema_table : - select_lex->db ? - is_schema_db(select_lex->db) : 0)) + if (lex->type != TYPE_ENUM_PROXY && + check_access(thd, lex->grant | lex->grant_tot_col | GRANT_ACL, + first_table ? first_table->db : select_lex->db, + first_table ? &first_table->grant.privilege : NULL, + first_table ? &first_table->grant.m_internal : NULL, + first_table ? 0 : 1, 0)) goto error; /* Replicate current user as grantor */ @@ -3957,6 +3415,7 @@ end_with_restore_list: if (thd->security_ctx->user) // If not replication { LEX_USER *user, *tmp_user; + bool first_user= TRUE; List_iterator <LEX_USER> user_list(lex->users_list); while ((tmp_user= user_list++)) @@ -3971,20 +3430,23 @@ end_with_restore_list: user->host.str); // Are we trying to change a password of another user DBUG_ASSERT(user->host.str != 0); - if (strcmp(thd->security_ctx->user, user->user.str) || - my_strcasecmp(system_charset_info, - user->host.str, thd->security_ctx->host_or_ip)) + + /* + GRANT/REVOKE PROXY has the target user as a first entry in the list. + */ + if (lex->type == TYPE_ENUM_PROXY && first_user) { - // TODO: use check_change_password() - if (is_acl_user(user->host.str, user->user.str) && - user->password.str && - check_access(thd, UPDATE_ACL,"mysql",0,1,1,0)) - { - my_message(ER_PASSWORD_NOT_ALLOWED, - ER(ER_PASSWORD_NOT_ALLOWED), MYF(0)); + 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; } } if (first_table) @@ -4009,7 +3471,7 @@ end_with_restore_list: else { if (check_grant(thd,(lex->grant | lex->grant_tot_col | GRANT_ACL), - all_tables, 0, UINT_MAX, 0)) + all_tables, FALSE, UINT_MAX, FALSE)) goto error; /* Conditionally writes to binlog */ res= mysql_table_grant(thd, all_tables, lex->users_list, @@ -4019,16 +3481,19 @@ end_with_restore_list: } else { - if (lex->columns.elements || lex->type) + if (lex->columns.elements || (lex->type && lex->type != TYPE_ENUM_PROXY)) { my_message(ER_ILLEGAL_GRANT_FOR_TABLE, ER(ER_ILLEGAL_GRANT_FOR_TABLE), MYF(0)); goto error; } else - /* Conditionally writes to binlog */ - res = mysql_grant(thd, select_lex->db, lex->users_list, lex->grant, - lex->sql_command == SQLCOM_REVOKE); + { + /* 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); + } if (!res) { if (lex->sql_command == SQLCOM_GRANT) @@ -4059,6 +3524,18 @@ end_with_restore_list: if (check_global_access(thd,RELOAD_ACL)) goto error; + if (first_table && lex->type & REFRESH_READ_LOCK) + { + /* 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; + } + /* reload_acl_and_cache() will tell us if we are allowed to write to the binlog or not. @@ -4120,7 +3597,7 @@ end_with_restore_list: goto error; if ((thd->security_ctx->priv_user && !strcmp(thd->security_ctx->priv_user, grant_user->user.str)) || - !check_access(thd, SELECT_ACL, "mysql",0,1,0,0)) + !check_access(thd, SELECT_ACL, "mysql", NULL, NULL, 1, 0)) { res = mysql_show_grants(thd, grant_user); } @@ -4129,7 +3606,7 @@ end_with_restore_list: #endif case SQLCOM_HA_OPEN: DBUG_ASSERT(first_table == all_tables && first_table != 0); - if (check_table_access(thd, SELECT_ACL, all_tables, UINT_MAX, FALSE)) + if (check_table_access(thd, SELECT_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; res= mysql_ha_open(thd, first_table, 0); break; @@ -4151,128 +3628,84 @@ end_with_restore_list: break; case SQLCOM_BEGIN: - if (thd->transaction.xid_state.xa_state != XA_NOTR) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (begin_trans(thd)) + if (trans_begin(thd, lex->start_transaction_opt)) goto error; - if (lex->start_transaction_opt & MYSQL_START_TRANS_OPT_WITH_CONS_SNAPSHOT) - { - if (ha_start_consistent_snapshot(thd)) - goto error; - } my_ok(thd); break; case SQLCOM_COMMIT: - if (end_trans(thd, lex->tx_release ? COMMIT_RELEASE : - lex->tx_chain ? COMMIT_AND_CHAIN : COMMIT)) - goto error; - my_ok(thd); - break; - case SQLCOM_ROLLBACK: - if (end_trans(thd, lex->tx_release ? ROLLBACK_RELEASE : - lex->tx_chain ? ROLLBACK_AND_CHAIN : ROLLBACK)) - goto error; - my_ok(thd); - break; - case SQLCOM_RELEASE_SAVEPOINT: { - SAVEPOINT *sv; - for (sv=thd->transaction.savepoints; sv; sv=sv->prev) + DBUG_ASSERT(thd->lock == NULL || + thd->locked_tables_mode == LTM_LOCK_TABLES); + bool tx_chain= (lex->tx_chain == TVL_YES || + (thd->variables.completion_type == 1 && + lex->tx_chain != TVL_NO)); + bool tx_release= (lex->tx_release == TVL_YES || + (thd->variables.completion_type == 2 && + lex->tx_release != TVL_NO)); + if (trans_commit(thd)) + goto error; + thd->mdl_context.release_transactional_locks(); + /* Begin transaction with the same isolation level. */ + if (tx_chain) { - if (my_strnncoll(system_charset_info, - (uchar *)lex->ident.str, lex->ident.length, - (uchar *)sv->name, sv->length) == 0) - break; + if (trans_begin(thd)) + goto error; } - if (sv) + else { - if (ha_release_savepoint(thd, sv)) - res= TRUE; // cannot happen - else - my_ok(thd); - thd->transaction.savepoints=sv->prev; + /* Reset the isolation level if no chaining transaction. */ + thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; } - else - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", lex->ident.str); + /* Disconnect the current client connection. */ + if (tx_release) + thd->killed= THD::KILL_CONNECTION; + my_ok(thd); break; } - case SQLCOM_ROLLBACK_TO_SAVEPOINT: + case SQLCOM_ROLLBACK: { - SAVEPOINT *sv; - for (sv=thd->transaction.savepoints; sv; sv=sv->prev) + DBUG_ASSERT(thd->lock == NULL || + thd->locked_tables_mode == LTM_LOCK_TABLES); + bool tx_chain= (lex->tx_chain == TVL_YES || + (thd->variables.completion_type == 1 && + lex->tx_chain != TVL_NO)); + 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(); + /* Begin transaction with the same isolation level. */ + if (tx_chain) { - if (my_strnncoll(system_charset_info, - (uchar *)lex->ident.str, lex->ident.length, - (uchar *)sv->name, sv->length) == 0) - break; + if (trans_begin(thd)) + goto error; } - if (sv) + else { - if (ha_rollback_to_savepoint(thd, sv)) - res= TRUE; // cannot happen - else - { - if (((thd->options & OPTION_KEEP_LOG) || - thd->transaction.all.modified_non_trans_table) && - !thd->slave_thread) - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_WARNING_NOT_COMPLETE_ROLLBACK, - ER(ER_WARNING_NOT_COMPLETE_ROLLBACK)); - my_ok(thd); - } - thd->transaction.savepoints=sv; + /* Reset the isolation level if no chaining transaction. */ + thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; } - else - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "SAVEPOINT", lex->ident.str); + /* Disconnect the current client connection. */ + if (tx_release) + thd->killed= THD::KILL_CONNECTION; + my_ok(thd); break; } + case SQLCOM_RELEASE_SAVEPOINT: + if (trans_release_savepoint(thd, lex->ident)) + goto error; + my_ok(thd); + break; + case SQLCOM_ROLLBACK_TO_SAVEPOINT: + if (trans_rollback_to_savepoint(thd, lex->ident)) + goto error; + my_ok(thd); + break; case SQLCOM_SAVEPOINT: - if (!(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN) || - thd->in_sub_stmt) || !opt_using_transactions) - my_ok(thd); - else - { - SAVEPOINT **sv, *newsv; - for (sv=&thd->transaction.savepoints; *sv; sv=&(*sv)->prev) - { - if (my_strnncoll(system_charset_info, - (uchar *)lex->ident.str, lex->ident.length, - (uchar *)(*sv)->name, (*sv)->length) == 0) - break; - } - if (*sv) /* old savepoint of the same name exists */ - { - newsv=*sv; - ha_release_savepoint(thd, *sv); // it cannot fail - *sv=(*sv)->prev; - } - else if ((newsv=(SAVEPOINT *) alloc_root(&thd->transaction.mem_root, - savepoint_alloc_size)) == 0) - { - my_error(ER_OUT_OF_RESOURCES, MYF(0)); - break; - } - newsv->name=strmake_root(&thd->transaction.mem_root, - lex->ident.str, lex->ident.length); - newsv->length=lex->ident.length; - /* - if we'll get an error here, don't add new savepoint to the list. - we'll lose a little bit of memory in transaction mem_root, but it'll - be free'd when transaction ends anyway - */ - if (ha_savepoint(thd, newsv)) - res= TRUE; - else - { - newsv->prev=thd->transaction.savepoints; - thd->transaction.savepoints=newsv; - my_ok(thd); - } - } + if (trans_savepoint(thd, lex->ident)) + goto error; + my_ok(thd); break; case SQLCOM_CREATE_PROCEDURE: case SQLCOM_CREATE_SPFUNCTION: @@ -4304,12 +3737,8 @@ end_with_restore_list: goto create_sp_error; } - if (check_access(thd, CREATE_PROC_ACL, lex->sphead->m_db.str, 0, 0, 0, - is_schema_db(lex->sphead->m_db.str, - lex->sphead->m_db.length))) - goto create_sp_error; - - if (end_active_trans(thd)) + 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); @@ -4329,7 +3758,7 @@ end_with_restore_list: if (sp_process_definer(thd)) goto create_sp_error; - res= (sp_result= lex->sphead->create(thd)); + res= (sp_result= sp_create_routine(thd, lex->sphead->m_type, lex->sphead)); switch (sp_result) { case SP_OK: { #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -4340,6 +3769,23 @@ end_with_restore_list: Security_context *backup= NULL; LEX_USER *definer= thd->lex->definer; /* + We're going to issue an implicit GRANT statement so we close all + open tables. We have to keep metadata locks as this ensures that + this statement is atomic against concurent FLUSH TABLES WITH READ + LOCK. Deadlocks which can arise due to fact that this implicit + statement takes metadata locks should be detected by a deadlock + detector in MDL subsystem and reported as errors. + + No need to commit/rollback statement transaction, it's not started. + + TODO: Long-term we should either ensure that implicit GRANT statement + is written into binary log as a separate statement or make both + creation of routine and implicit GRANT parts of one fully atomic + statement. + */ + DBUG_ASSERT(thd->transaction.stmt.is_empty()); + close_thread_tables(thd); + /* Check if the definer exists on slave, then use definer privilege to insert routine privileges to mysql.procs_priv. @@ -4365,8 +3811,8 @@ 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, - ER_PROC_AUTO_GRANT_FAIL, - ER(ER_PROC_AUTO_GRANT_FAIL)); + ER_PROC_AUTO_GRANT_FAIL, ER(ER_PROC_AUTO_GRANT_FAIL)); + thd->clear_error(); } /* @@ -4411,13 +3857,13 @@ create_sp_error: case SQLCOM_CALL: { sp_head *sp; - /* This will cache all SP and SF and open and lock all tables required for execution. */ - if (check_table_access(thd, SELECT_ACL, all_tables, UINT_MAX, FALSE) || - open_and_lock_tables(thd, all_tables)) + if (check_table_access(thd, SELECT_ACL, all_tables, FALSE, + UINT_MAX, FALSE) || + open_and_lock_tables(thd, all_tables, TRUE, 0)) goto error; /* @@ -4486,20 +3932,15 @@ create_sp_error: So just execute the statement. */ res= sp->execute_procedure(thd, &lex->value_list); - /* - If warnings have been cleared, we have to clear total_warn_count - too, otherwise the clients get confused. - */ - if (thd->warn_list.is_empty()) - thd->total_warn_count= 0; thd->variables.select_limit= select_limit; thd->server_status&= ~bits_to_be_cleared; if (!res) - my_ok(thd, (ulong) (thd->row_count_func < 0 ? 0 : - thd->row_count_func)); + { + my_ok(thd, (thd->get_row_count_func() < 0) ? 0 : thd->get_row_count_func()); + } else { DBUG_ASSERT(thd->is_error() || thd->killed); @@ -4512,67 +3953,22 @@ create_sp_error: case SQLCOM_ALTER_FUNCTION: { int sp_result; - sp_head *sp; - st_sp_chistics chistics; - - memcpy(&chistics, &lex->sp_chistics, sizeof(chistics)); - if (lex->sql_command == SQLCOM_ALTER_PROCEDURE) - sp= sp_find_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname, - &thd->sp_proc_cache, FALSE); - else - sp= sp_find_routine(thd, TYPE_ENUM_FUNCTION, lex->spname, - &thd->sp_func_cache, FALSE); - mysql_reset_errors(thd, 0); - if (! sp) - { - if (lex->spname->m_db.str) - sp_result= SP_KEY_NOT_FOUND; - else - { - my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); - goto error; - } - } - else - { - if (check_routine_access(thd, ALTER_PROC_ACL, sp->m_db.str, - sp->m_name.str, - lex->sql_command == SQLCOM_ALTER_PROCEDURE, 0)) - goto error; - - if (end_active_trans(thd)) - goto error; - memcpy(&lex->sp_chistics, &chistics, sizeof(lex->sp_chistics)); - if ((sp->m_type == TYPE_ENUM_FUNCTION) && - !trust_function_creators && mysql_bin_log.is_open() && - !sp->m_chistics->detistic && - (chistics.daccess == SP_CONTAINS_SQL || - chistics.daccess == SP_MODIFIES_SQL_DATA)) - { - my_message(ER_BINLOG_UNSAFE_ROUTINE, - ER(ER_BINLOG_UNSAFE_ROUTINE), MYF(0)); - sp_result= SP_INTERNAL_ERROR; - } - else - { - /* - Note that if you implement the capability of ALTER FUNCTION to - alter the body of the function, this command should be made to - follow the restrictions that log-bin-trust-function-creators=0 - already puts on CREATE FUNCTION. - */ - /* Conditionally writes to binlog */ + int type= (lex->sql_command == SQLCOM_ALTER_PROCEDURE ? + TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION); - int type= lex->sql_command == SQLCOM_ALTER_PROCEDURE ? - TYPE_ENUM_PROCEDURE : - TYPE_ENUM_FUNCTION; + if (check_routine_access(thd, ALTER_PROC_ACL, lex->spname->m_db.str, + lex->spname->m_name.str, + lex->sql_command == SQLCOM_ALTER_PROCEDURE, 0)) + goto error; - sp_result= sp_update_routine(thd, - type, - lex->spname, - &lex->sp_chistics); - } - } + /* + Note that if you implement the capability of ALTER FUNCTION to + alter the body of the function, this command should be made to + follow the restrictions that log-bin-trust-function-creators=0 + already puts on CREATE FUNCTION. + */ + /* Conditionally writes to binlog */ + sp_result= sp_update_routine(thd, type, lex->spname, &lex->sp_chistics); switch (sp_result) { case SP_OK: @@ -4592,69 +3988,92 @@ create_sp_error: case SQLCOM_DROP_PROCEDURE: case SQLCOM_DROP_FUNCTION: { - int sp_result; - int type= (lex->sql_command == SQLCOM_DROP_PROCEDURE ? - TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION); - - sp_result= sp_routine_exists_in_table(thd, type, lex->spname); - mysql_reset_errors(thd, 0); - if (sp_result == SP_OK) +#ifdef HAVE_DLOPEN + if (lex->sql_command == SQLCOM_DROP_FUNCTION && + ! lex->spname->m_explicit_name) { - char *db= lex->spname->m_db.str; - char *name= lex->spname->m_name.str; + /* DROP FUNCTION <non qualified name> */ + udf_func *udf = find_udf(lex->spname->m_name.str, + lex->spname->m_name.length); + if (udf) + { + if (check_access(thd, DELETE_ACL, "mysql", NULL, NULL, 1, 0)) + goto error; - if (check_routine_access(thd, ALTER_PROC_ACL, db, name, - lex->sql_command == SQLCOM_DROP_PROCEDURE, 0)) + if (!(res = mysql_drop_function(thd, &lex->spname->m_name))) + { + my_ok(thd); + break; + } + my_error(ER_SP_DROP_FAILED, MYF(0), + "FUNCTION (UDF)", lex->spname->m_name.str); goto error; + } - if (end_active_trans(thd)) + if (lex->spname->m_db.str == NULL) + { + if (lex->drop_if_exists) + { + push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST), + "FUNCTION (UDF)", lex->spname->m_name.str); + res= FALSE; + my_ok(thd); + break; + } + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), + "FUNCTION (UDF)", lex->spname->m_name.str); goto error; -#ifndef NO_EMBEDDED_ACCESS_CHECKS - if (sp_automatic_privileges && !opt_noacl && - sp_revoke_privileges(thd, db, name, - lex->sql_command == SQLCOM_DROP_PROCEDURE)) - { - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, - ER_PROC_AUTO_REVOKE_FAIL, - ER(ER_PROC_AUTO_REVOKE_FAIL)); - } + } + /* Fall thought to test for a stored function */ + } #endif - /* Conditionally writes to binlog */ - int type= lex->sql_command == SQLCOM_DROP_PROCEDURE ? - TYPE_ENUM_PROCEDURE : - TYPE_ENUM_FUNCTION; + int sp_result; + int type= (lex->sql_command == SQLCOM_DROP_PROCEDURE ? + TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION); + char *db= lex->spname->m_db.str; + char *name= lex->spname->m_name.str; + + if (check_routine_access(thd, ALTER_PROC_ACL, db, name, + lex->sql_command == SQLCOM_DROP_PROCEDURE, 0)) + goto error; - sp_result= sp_drop_routine(thd, type, lex->spname); - } - else + /* Conditionally writes to binlog */ + sp_result= sp_drop_routine(thd, type, lex->spname); + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + /* + We're going to issue an implicit REVOKE statement so we close all + open tables. We have to keep metadata locks as this ensures that + this statement is atomic against concurent FLUSH TABLES WITH READ + LOCK. Deadlocks which can arise due to fact that this implicit + statement takes metadata locks should be detected by a deadlock + detector in MDL subsystem and reported as errors. + + No need to commit/rollback statement transaction, it's not started. + + TODO: Long-term we should either ensure that implicit REVOKE statement + is written into binary log as a separate statement or make both + dropping of routine and implicit REVOKE parts of one fully atomic + statement. + */ + DBUG_ASSERT(thd->transaction.stmt.is_empty()); + close_thread_tables(thd); + + if (sp_result != SP_KEY_NOT_FOUND && + sp_automatic_privileges && !opt_noacl && + sp_revoke_privileges(thd, db, name, + lex->sql_command == SQLCOM_DROP_PROCEDURE)) { -#ifdef HAVE_DLOPEN - if (lex->sql_command == SQLCOM_DROP_FUNCTION) - { - udf_func *udf = find_udf(lex->spname->m_name.str, - lex->spname->m_name.length); - if (udf) - { - if (check_access(thd, DELETE_ACL, "mysql", 0, 1, 0, 0)) - goto error; - - if (!(res = mysql_drop_function(thd, &lex->spname->m_name))) - { - my_ok(thd); - break; - } - } - } -#endif - if (lex->spname->m_db.str) - sp_result= SP_KEY_NOT_FOUND; - else - { - my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0)); - goto error; - } + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, + ER_PROC_AUTO_REVOKE_FAIL, + ER(ER_PROC_AUTO_REVOKE_FAIL)); + /* If this happens, an error should have been reported. */ + goto error; } +#endif + res= sp_result; switch (sp_result) { case SP_OK: @@ -4666,7 +4085,7 @@ create_sp_error: res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST), - SP_COM_STRING(lex), lex->spname->m_name.str); + SP_COM_STRING(lex), lex->spname->m_qname.str); if (!res) my_ok(thd); break; @@ -4684,35 +4103,25 @@ create_sp_error: case SQLCOM_SHOW_CREATE_PROC: { if (sp_show_create_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname)) - { - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), - SP_COM_STRING(lex), lex->spname->m_name.str); - goto error; - } + goto error; break; } case SQLCOM_SHOW_CREATE_FUNC: { if (sp_show_create_routine(thd, TYPE_ENUM_FUNCTION, lex->spname)) - { - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), - SP_COM_STRING(lex), lex->spname->m_name.str); goto error; - } break; } -#ifndef DBUG_OFF case SQLCOM_SHOW_PROC_CODE: case SQLCOM_SHOW_FUNC_CODE: { +#ifndef DBUG_OFF sp_head *sp; + int type= (lex->sql_command == SQLCOM_SHOW_PROC_CODE ? + TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION); - if (lex->sql_command == SQLCOM_SHOW_PROC_CODE) - sp= sp_find_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname, - &thd->sp_proc_cache, FALSE); - else - sp= sp_find_routine(thd, TYPE_ENUM_FUNCTION, lex->spname, - &thd->sp_func_cache, FALSE); + if (sp_cache_routine(thd, type, lex->spname, FALSE, &sp)) + goto error; if (!sp || sp->show_routine_code(thd)) { /* We don't distinguish between errors for now */ @@ -4721,8 +4130,12 @@ create_sp_error: goto error; } break; - } +#else + my_error(ER_FEATURE_DISABLED, MYF(0), + "SHOW PROCEDURE|FUNCTION CODE", "--with-debug"); + goto error; #endif // ifndef DBUG_OFF + } case SQLCOM_SHOW_CREATE_TRIGGER: { if (lex->spname->m_name.length > NAME_LEN) @@ -4742,16 +4155,12 @@ create_sp_error: Note: SQLCOM_CREATE_VIEW also handles 'ALTER VIEW' commands as specified through the thd->lex->create_view_mode flag. */ - if (end_active_trans(thd)) - goto error; - res= mysql_create_view(thd, first_table, thd->lex->create_view_mode); break; } case SQLCOM_DROP_VIEW: { - if (check_table_access(thd, DROP_ACL, all_tables, UINT_MAX, FALSE) || - end_active_trans(thd)) + if (check_table_access(thd, DROP_ACL, all_tables, FALSE, UINT_MAX, FALSE)) goto error; /* Conditionally writes to binlog. */ res= mysql_drop_view(thd, first_table, thd->lex->drop_mode); @@ -4759,9 +4168,6 @@ create_sp_error: } case SQLCOM_CREATE_TRIGGER: { - if (end_active_trans(thd)) - goto error; - /* Conditionally writes to binlog. */ res= mysql_create_or_drop_trigger(thd, all_tables, 1); @@ -4769,210 +4175,52 @@ create_sp_error: } case SQLCOM_DROP_TRIGGER: { - if (end_active_trans(thd)) - goto error; - /* Conditionally writes to binlog. */ res= mysql_create_or_drop_trigger(thd, all_tables, 0); break; } case SQLCOM_XA_START: - if (thd->transaction.xid_state.xa_state == XA_IDLE && - thd->lex->xa_opt == XA_RESUME) - { - if (! thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - my_error(ER_XAER_NOTA, MYF(0)); - break; - } - thd->transaction.xid_state.xa_state= XA_ACTIVE; - my_ok(thd); - break; - } - if (thd->lex->xa_opt != XA_NONE) - { // JOIN is not supported yet. TODO - my_error(ER_XAER_INVAL, MYF(0)); - break; - } - if (thd->transaction.xid_state.xa_state != XA_NOTR) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (thd->active_transaction() || thd->locked_tables) - { - my_error(ER_XAER_OUTSIDE, MYF(0)); - break; - } - DBUG_ASSERT(thd->transaction.xid_state.xid.is_null()); - thd->transaction.xid_state.xa_state= XA_ACTIVE; - thd->transaction.xid_state.rm_error= 0; - thd->transaction.xid_state.xid.set(thd->lex->xid); - if (xid_cache_insert(&thd->transaction.xid_state)) - { - thd->transaction.xid_state.xa_state= XA_NOTR; - thd->transaction.xid_state.xid.null(); - break; - } - thd->transaction.all.modified_non_trans_table= FALSE; - thd->options= ((thd->options & ~(OPTION_KEEP_LOG)) | OPTION_BEGIN); - thd->server_status|= SERVER_STATUS_IN_TRANS; + if (trans_xa_start(thd)) + goto error; my_ok(thd); break; case SQLCOM_XA_END: - /* fake it */ - if (thd->lex->xa_opt != XA_NONE) - { // SUSPEND and FOR MIGRATE are not supported yet. TODO - my_error(ER_XAER_INVAL, MYF(0)); - break; - } - if (thd->transaction.xid_state.xa_state != XA_ACTIVE) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - my_error(ER_XAER_NOTA, MYF(0)); - break; - } - if (xa_trans_rolled_back(&thd->transaction.xid_state)) - break; - thd->transaction.xid_state.xa_state=XA_IDLE; + if (trans_xa_end(thd)) + goto error; my_ok(thd); break; case SQLCOM_XA_PREPARE: - if (thd->transaction.xid_state.xa_state != XA_IDLE) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - my_error(ER_XAER_NOTA, MYF(0)); - break; - } - if (ha_prepare(thd)) - { - my_error(ER_XA_RBROLLBACK, MYF(0)); - xid_cache_delete(&thd->transaction.xid_state); - thd->transaction.xid_state.xa_state=XA_NOTR; - break; - } - thd->transaction.xid_state.xa_state=XA_PREPARED; + if (trans_xa_prepare(thd)) + goto error; my_ok(thd); break; case SQLCOM_XA_COMMIT: - if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - /* - xid_state.in_thd is always true beside of xa recovery - procedure. Note, that there is no race condition here - between xid_cache_search and xid_cache_delete, since we're always - deleting our own XID (thd->lex->xid == thd->transaction.xid_state.xid). - The only case when thd->lex->xid != thd->transaction.xid_state.xid - and xid_state->in_thd == 0 is in ha_recover() functionality, - which is called before starting client connections, and thus is - always single-threaded. - */ - XID_STATE *xs=xid_cache_search(thd->lex->xid); - if (!xs || xs->in_thd) - my_error(ER_XAER_NOTA, MYF(0)); - else if (xa_trans_rolled_back(xs)) - { - ha_commit_or_rollback_by_xid(thd->lex->xid, 0); - xid_cache_delete(xs); - break; - } - else - { - ha_commit_or_rollback_by_xid(thd->lex->xid, 1); - xid_cache_delete(xs); - my_ok(thd); - } - break; - } - if (xa_trans_rolled_back(&thd->transaction.xid_state)) - { - xa_trans_rollback(thd); - break; - } - if (thd->transaction.xid_state.xa_state == XA_IDLE && - thd->lex->xa_opt == XA_ONE_PHASE) - { - int r; - if ((r= ha_commit(thd))) - my_error(r == 1 ? ER_XA_RBROLLBACK : ER_XAER_RMERR, MYF(0)); - else - my_ok(thd); - } - else if (thd->transaction.xid_state.xa_state == XA_PREPARED && - thd->lex->xa_opt == XA_NONE) - { - if (wait_if_global_read_lock(thd, 0, 0)) - { - ha_rollback(thd); - my_error(ER_XAER_RMERR, MYF(0)); - } - else - { - if (ha_commit_one_phase(thd, 1)) - my_error(ER_XAER_RMERR, MYF(0)); - else - my_ok(thd); - start_waiting_global_read_lock(thd); - } - } - else - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - thd->options&= ~(OPTION_BEGIN | OPTION_KEEP_LOG); - thd->transaction.all.modified_non_trans_table= FALSE; - thd->server_status&= ~SERVER_STATUS_IN_TRANS; - xid_cache_delete(&thd->transaction.xid_state); - thd->transaction.xid_state.xa_state=XA_NOTR; + if (trans_xa_commit(thd)) + goto error; + thd->mdl_context.release_transactional_locks(); + /* + We've just done a commit, reset transaction + isolation level to the session default. + */ + thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + my_ok(thd); break; case SQLCOM_XA_ROLLBACK: - if (!thd->transaction.xid_state.xid.eq(thd->lex->xid)) - { - XID_STATE *xs=xid_cache_search(thd->lex->xid); - if (!xs || xs->in_thd) - my_error(ER_XAER_NOTA, MYF(0)); - else - { - bool ok= !xa_trans_rolled_back(xs); - ha_commit_or_rollback_by_xid(thd->lex->xid, 0); - xid_cache_delete(xs); - if (ok) - my_ok(thd); - } - break; - } - if (thd->transaction.xid_state.xa_state != XA_IDLE && - thd->transaction.xid_state.xa_state != XA_PREPARED && - thd->transaction.xid_state.xa_state != XA_ROLLBACK_ONLY) - { - my_error(ER_XAER_RMFAIL, MYF(0), - xa_state_names[thd->transaction.xid_state.xa_state]); - break; - } - if (xa_trans_rollback(thd)) - my_error(ER_XAER_RMERR, MYF(0)); - else - my_ok(thd); + if (trans_xa_rollback(thd)) + goto error; + thd->mdl_context.release_transactional_locks(); + /* + We've just done a rollback, reset transaction + isolation level to the session default. + */ + thd->tx_isolation= (enum_tx_isolation) thd->variables.tx_isolation; + my_ok(thd); break; case SQLCOM_XA_RECOVER: res= mysql_xa_recover(thd); break; case SQLCOM_ALTER_TABLESPACE: - if (check_access(thd, ALTER_ACL, thd->db, 0, 1, 0, - thd->db ? is_schema_db(thd->db, thd->db_length) : 0)) + if (check_global_access(thd, CREATE_TABLESPACE_ACL)) break; if (!(res= mysql_alter_tablespace(thd, lex->alter_tablespace_info))) my_ok(thd); @@ -5059,6 +4307,19 @@ create_sp_error: my_ok(thd, 1); break; } + case SQLCOM_ANALYZE: + case SQLCOM_CHECK: + case SQLCOM_OPTIMIZE: + case SQLCOM_REPAIR: + case SQLCOM_TRUNCATE: + case SQLCOM_ALTER_TABLE: + DBUG_ASSERT(first_table == all_tables && first_table != 0); + /* fall through */ + case SQLCOM_SIGNAL: + case SQLCOM_RESIGNAL: + DBUG_ASSERT(lex->m_stmt != NULL); + res= lex->m_stmt->execute(thd); + break; default: #ifndef EMBEDDED_LIBRARY DBUG_ASSERT(0); /* Impossible */ @@ -5081,29 +4342,82 @@ create_sp_error: if (thd->one_shot_set && lex->sql_command != SQLCOM_SET_OPTION) reset_one_shot_variables(thd); - /* - The return value for ROW_COUNT() is "implementation dependent" if the - statement is not DELETE, INSERT or UPDATE, but -1 is what JDBC and ODBC - wants. We also keep the last value in case of SQLCOM_CALL or - SQLCOM_EXECUTE. - */ - if (!(sql_command_flags[lex->sql_command] & CF_HAS_ROW_COUNT)) - thd->row_count_func= -1; - goto finish; error: res= TRUE; finish: - if (need_start_waiting) + + DBUG_ASSERT(!thd->in_active_multi_stmt_transaction() || + thd->in_multi_stmt_transaction_mode()); + + + if (! thd->in_sub_stmt) + { + /* report error issued during command execution */ + if (thd->killed_errno()) + { + if (! thd->stmt_da->is_set()) + thd->send_kill_message(); + } + if (thd->killed == THD::KILL_QUERY || thd->killed == THD::KILL_BAD_DATA) + { + thd->killed= THD::NOT_KILLED; + thd->mysys_var->abort= 0; + } + 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; + trans_commit_stmt(thd); + thd->stmt_da->can_overwrite_status= FALSE; + } + } + + lex->unit.cleanup(); + /* Free tables */ + thd_proc_info(thd, "closing 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 (stmt_causes_implicit_commit(thd, CF_IMPLICIT_COMMIT_END)) + { + /* 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(); + } + else if (! thd->in_sub_stmt && ! thd->in_multi_stmt_transaction_mode()) { /* - Release the protection against the global read lock and wake - everyone, who might want to set a global read lock. + - If inside a multi-statement transaction, + defer the release of metadata locks until the current + transaction is either committed or rolled back. This prevents + other statements from modifying the table for the entire + duration of this transaction. This provides commit ordering + and guarantees serializability across multiple transactions. + - If in autocommit mode, or outside a transactional context, + automatically release metadata locks of the current statement. */ - start_waiting_global_read_lock(thd); + thd->mdl_context.release_transactional_locks(); } + else if (! thd->in_sub_stmt) + { + thd->mdl_context.release_statement_locks(); + } + DBUG_RETURN(res || thd->is_error()); } @@ -5120,7 +4434,7 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) param->select_limit= new Item_int((ulonglong) thd->variables.select_limit); } - if (!(res= open_and_lock_tables(thd, all_tables))) + if (!(res= open_and_lock_tables(thd, all_tables, TRUE, 0))) { if (lex->describe) { @@ -5145,7 +4459,7 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) ER_YES, str.ptr()); } if (res) - result->abort(); + result->abort_result_set(); else result->send_eof(); delete result; @@ -5197,14 +4511,15 @@ bool check_single_table_access(THD *thd, ulong privilege, db_name= all_tables->db; if (check_access(thd, privilege, db_name, - &all_tables->grant.privilege, 0, no_errors, - test(all_tables->schema_table))) + &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, 0, 1, no_errors)) + check_grant(thd, privilege, all_tables, FALSE, 1, no_errors)) goto deny; thd->security_ctx= backup_ctx; @@ -5249,7 +4564,8 @@ bool check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *all_tables) subselects_tables= subselects_tables->next_global; } if (subselects_tables && - (check_table_access(thd, SELECT_ACL, subselects_tables, UINT_MAX, FALSE))) + (check_table_access(thd, SELECT_ACL, subselects_tables, FALSE, + UINT_MAX, FALSE))) return 1; } return 0; @@ -5257,50 +4573,62 @@ bool check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *all_tables) /** - Get the user (global) and database privileges for all used tables. - - @param save_priv In this we store global and db level grants for the - table. Note that we don't store db level grants if the - global grants is enough to satisfy the request and the - global grants contains a SELECT grant. - - @note - The idea of EXTRA_ACL is that one will be granted access to the table if - one has the asked privilege on any column combination of the table; For - example to be able to check a table one needs to have SELECT privilege on - any column of the table. - - @retval - 0 ok - @retval - 1 If we can't get the privileges and we don't use table/column - grants. + @brief Compare requested privileges with the privileges acquired from the + User- and Db-tables. + @param thd Thread handler + @param want_access The requested access privileges. + @param db A pointer to the Db name. + @param[out] save_priv A pointer to the granted privileges will be stored. + @param grant_internal_info A pointer to the internal grant cache. + @param dont_check_global_grants True if no global grants are checked. + @param no_error True if no errors should be sent to the client. + + 'save_priv' is used to save the User-table (global) and Db-table grants for + the supplied db name. Note that we don't store db level grants if the global + grants is enough to satisfy the request AND the global grants contains a + SELECT grant. + + For internal databases (INFORMATION_SCHEMA, PERFORMANCE_SCHEMA), + additional rules apply, see ACL_internal_schema_access. + + @see check_grant + + @return Status of denial of access by exclusive ACLs. + @retval FALSE Access can't exclusively be denied by Db- and User-table + access unless Column- and Table-grants are checked too. + @retval TRUE Access denied. */ + bool check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, - bool dont_check_global_grants, bool no_errors, bool schema_db) + GRANT_INTERNAL_INFO *grant_internal_info, + bool dont_check_global_grants, bool no_errors) { Security_context *sctx= thd->security_ctx; ulong db_access; + /* GRANT command: In case of database level grant the database name may be a pattern, in case of table|column level grant the database name can not be a pattern. We use 'dont_check_global_grants' as a flag to determine - if it's database level grant command + if it's database level grant command (see SQLCOM_GRANT case, mysql_execute_command() function) and set db_is_pattern according to 'dont_check_global_grants' value. */ - bool db_is_pattern= (test(want_access & GRANT_ACL) && - dont_check_global_grants); + bool db_is_pattern= ((want_access & GRANT_ACL) && dont_check_global_grants); ulong dummy; DBUG_ENTER("check_access"); DBUG_PRINT("enter",("db: %s want_access: %lu master_access: %lu", db ? db : "", want_access, sctx->master_access)); + if (save_priv) *save_priv=0; else + { save_priv= &dummy; + dummy= 0; + } thd_proc_info(thd, "checking permissions"); if ((!db || !db[0]) && !thd->db && !dont_check_global_grants) @@ -5312,88 +4640,166 @@ check_access(THD *thd, ulong want_access, const char *db, ulong *save_priv, DBUG_RETURN(TRUE); /* purecov: tested */ } - if (schema_db) + if ((db != NULL) && (db != any_db)) { - if ((!(sctx->master_access & FILE_ACL) && (want_access & FILE_ACL)) || - (want_access & ~(SELECT_ACL | EXTRA_ACL | FILE_ACL))) + const ACL_internal_schema_access *access; + access= get_cached_schema_access(grant_internal_info, db); + if (access) { - if (!no_errors) + switch (access->check(want_access, save_priv)) { - const char *db_name= db ? db : thd->db; - my_error(ER_DBACCESS_DENIED_ERROR, MYF(0), - sctx->priv_user, sctx->priv_host, db_name); + case ACL_INTERNAL_ACCESS_GRANTED: + /* + All the privileges requested have been granted internally. + [out] *save_privileges= Internal privileges. + */ + DBUG_RETURN(FALSE); + case ACL_INTERNAL_ACCESS_DENIED: + if (! no_errors) + { + my_error(ER_DBACCESS_DENIED_ERROR, MYF(0), + sctx->priv_user, sctx->priv_host, db); + } + DBUG_RETURN(TRUE); + case ACL_INTERNAL_ACCESS_CHECK_GRANT: + /* + Only some of the privilege requested have been granted internally, + proceed with the remaining bits of the request (want_access). + */ + want_access&= ~(*save_priv); + break; } - DBUG_RETURN(TRUE); - } - else - { - *save_priv= SELECT_ACL; - DBUG_RETURN(FALSE); } } if ((sctx->master_access & want_access) == want_access) { /* - If we don't have a global SELECT privilege, we have to get the database - specific access rights to be able to handle queries of type + 1. If we don't have a global SELECT privilege, we have to get the + database specific access rights to be able to handle queries of type UPDATE t1 SET a=1 WHERE b > 0 + 2. Change db access if it isn't current db which is being addressed */ - db_access= sctx->db_access; - if (!(sctx->master_access & SELECT_ACL) && - (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); - *save_priv=sctx->master_access | db_access; + 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); + else + { + /* get access for current db */ + db_access= sctx->db_access; + } + /* + The effective privileges are the union of the global privileges + and the intersection of db- and host-privileges, + plus the internal privileges. + */ + *save_priv|= sctx->master_access | db_access; + } + else + *save_priv|= sctx->master_access; DBUG_RETURN(FALSE); } - if (((want_access & ~sctx->master_access) & ~(DB_ACLS | EXTRA_ACL)) || + if (((want_access & ~sctx->master_access) & ~DB_ACLS) || (! db && dont_check_global_grants)) { // We can never grant this DBUG_PRINT("error",("No possible access")); if (!no_errors) - my_error(ER_ACCESS_DENIED_ERROR, MYF(0), - sctx->priv_user, - sctx->priv_host, - (thd->password ? - ER(ER_YES) : - ER(ER_NO))); /* purecov: tested */ + { + if (thd->password == 2) + my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0), + sctx->priv_user, + sctx->priv_host); + else + my_error(ER_ACCESS_DENIED_ERROR, MYF(0), + sctx->priv_user, + sctx->priv_host, + (thd->password ? + ER(ER_YES) : + ER(ER_NO))); /* purecov: tested */ + } DBUG_RETURN(TRUE); /* purecov: tested */ } if (db == any_db) - DBUG_RETURN(FALSE); // Allow select on anything + { + /* + Access granted; Allow select on *any* db. + [out] *save_privileges= 0 + */ + DBUG_RETURN(FALSE); + } 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); else db_access= sctx->db_access; - DBUG_PRINT("info",("db_access: %lu", db_access)); - /* Remove SHOW attribute and access rights we already have */ - want_access &= ~(sctx->master_access | EXTRA_ACL); DBUG_PRINT("info",("db_access: %lu want_access: %lu", db_access, want_access)); - db_access= ((*save_priv=(db_access | sctx->master_access)) & want_access); - if (db_access == want_access || + /* + Save the union of User-table and the intersection between Db-table and + Host-table privileges, with the already saved internal privileges. + */ + db_access= (db_access | sctx->master_access); + *save_priv|= db_access; + + /* + We need to investigate column- and table access if all requested privileges + belongs to the bit set of . + */ + bool need_table_or_column_check= + (want_access & (TABLE_ACLS | PROC_ACLS | db_access)) == want_access; + + /* + Grant access if the requested access is in the intersection of + host- and db-privileges (as retrieved from the acl cache), + also grant access if all the requested privileges are in the union of + TABLES_ACLS and PROC_ACLS; see check_grant. + */ + if ( (db_access & want_access) == want_access || (!dont_check_global_grants && - !(want_access & ~(db_access | TABLE_ACLS | PROC_ACLS)))) - DBUG_RETURN(FALSE); /* Ok */ + need_table_or_column_check)) + { + /* + Ok; but need to check table- and column privileges. + [out] *save_privileges is (User-priv | (Db-priv & Host-priv) | Internal-priv) + */ + DBUG_RETURN(FALSE); + } + /* + Access is denied; + [out] *save_privileges is (User-priv | (Db-priv & Host-priv) | Internal-priv) + */ DBUG_PRINT("error",("Access denied")); if (!no_errors) my_error(ER_DBACCESS_DENIED_ERROR, MYF(0), sctx->priv_user, sctx->priv_host, (db ? db : (thd->db ? thd->db : - "unknown"))); /* purecov: tested */ - DBUG_RETURN(TRUE); /* purecov: tested */ + "unknown"))); + DBUG_RETURN(TRUE); + } static bool check_show_access(THD *thd, TABLE_LIST *table) { + /* + This is a SHOW command using an INFORMATION_SCHEMA table. + check_access() has not been called for 'table', + and SELECT is currently always granted on the I_S, so we automatically + grant SELECT on table here, to bypass a call to check_access(). + Note that not calling check_access(table) is an optimization, + which needs to be revisited if the INFORMATION_SCHEMA does + not always automatically grant SELECT but use the grant tables. + See Bug#38837 need a way to disable information_schema for security + */ + table->grant.privilege= SELECT_ACL; + switch (get_schema_table_idx(table->schema_table)) { case SCH_SCHEMATA: return (specialflag & SPECIAL_SKIP_SHOW_DB) && @@ -5410,8 +4816,7 @@ static bool check_show_access(THD *thd, TABLE_LIST *table) DBUG_ASSERT(dst_db_name); if (check_access(thd, SELECT_ACL, dst_db_name, - &thd->col_access, FALSE, FALSE, - is_schema_db(dst_db_name))) + &thd->col_access, NULL, FALSE, FALSE)) return TRUE; if (!thd->col_access && check_grant_db(thd, dst_db_name)) @@ -5434,14 +4839,21 @@ static bool check_show_access(THD *thd, TABLE_LIST *table) DBUG_ASSERT(dst_table); - if (check_access(thd, SELECT_ACL | EXTRA_ACL, - dst_table->db, + if (check_access(thd, SELECT_ACL, dst_table->db, &dst_table->grant.privilege, - FALSE, FALSE, - test(dst_table->schema_table))) - return FALSE; + &dst_table->grant.m_internal, + FALSE, FALSE)) + return TRUE; /* Access denied */ - return (check_grant(thd, SELECT_ACL, dst_table, 2, UINT_MAX, FALSE)); + /* + Check_grant will grant access if there is any column privileges on + all of the tables thanks to the fourth parameter (bool show_table). + */ + if (check_grant(thd, SELECT_ACL, dst_table, TRUE, UINT_MAX, FALSE)) + return TRUE; /* Access denied */ + + /* Access granted */ + return FALSE; } default: break; @@ -5451,30 +4863,46 @@ static bool check_show_access(THD *thd, TABLE_LIST *table) } -/** - Check the privilege for all used tables. - @param thd Thread context - @param want_access Privileges requested - @param tables List of tables to be checked - @param number Check at most this number of tables. - @param no_errors FALSE/TRUE - report/don't report error to - the client (using my_error() call). +/** + @brief Check if the requested privileges exists in either User-, Host- or + Db-tables. + @param thd Thread context + @param want_access Privileges requested + @param tables List of tables to be compared against + @param no_errors Don't report error to the client (using my_error() call). + @param any_combination_of_privileges_will_do TRUE if any privileges on any + column combination is enough. + @param number Only the first 'number' tables in the linked list are + relevant. + + The suppled table list contains cached privileges. This functions calls the + help functions check_access and check_grant to verify the first three steps + in the privileges check queue: + 1. Global privileges + 2. OR (db privileges AND host privileges) + 3. OR table privileges + 4. OR column privileges (not checked by this function!) + 5. OR routine privileges (not checked by this function!) + + @see check_access + @see check_grant + + @note This functions assumes that table list used and + thd->lex->query_tables_own_last value correspond to each other + (the latter should be either 0 or point to next_global member + of one of elements of this table list). - @note - Table privileges are cached in the table list for GRANT checking. - This functions assumes that table list used and - thd->lex->query_tables_own_last value correspond to each other - (the latter should be either 0 or point to next_global member - of one of elements of this table list). - - @retval FALSE OK - @retval TRUE Access denied + @return + @retval FALSE OK + @retval TRUE Access denied; But column or routine privileges might need to + be checked also. */ bool -check_table_access(THD *thd, ulong want_access,TABLE_LIST *tables, - uint number, bool no_errors) +check_table_access(THD *thd, ulong requirements,TABLE_LIST *tables, + bool any_combination_of_privileges_will_do, + uint number, bool no_errors) { TABLE_LIST *org_tables= tables; TABLE_LIST *first_not_own_table= thd->lex->first_not_own_table(); @@ -5485,23 +4913,15 @@ check_table_access(THD *thd, ulong want_access,TABLE_LIST *tables, the given table list refers to the list for prelocking (contains tables of other queries). For simple queries first_not_own_table is 0. */ - for (; i < number && tables != first_not_own_table; + for (; i < number && tables != first_not_own_table && tables; tables= tables->next_global, i++) { + ulong want_access= requirements; if (tables->security_ctx) sctx= tables->security_ctx; else sctx= backup_ctx; - if (tables->schema_table && - (want_access & ~(SELECT_ACL | EXTRA_ACL | FILE_ACL))) - { - if (!no_errors) - my_error(ER_DBACCESS_DENIED_ERROR, MYF(0), - sctx->priv_user, sctx->priv_host, - INFORMATION_SCHEMA_NAME.str); - return TRUE; - } /* Register access for view underlying table. Remove SHOW_VIEW_ACL, because it will be checked during making view @@ -5512,33 +4932,27 @@ check_table_access(THD *thd, ulong want_access,TABLE_LIST *tables, { if (check_show_access(thd, tables)) goto deny; - continue; } + DBUG_PRINT("info", ("derived: %d view: %d", tables->derived != 0, + tables->view != 0)); if (tables->is_anonymous_derived_table() || - (tables->table && (int)tables->table->s->tmp_table)) + (tables->table && tables->table->s && + (int)tables->table->s->tmp_table)) continue; thd->security_ctx= sctx; - if ((sctx->master_access & want_access) == - (want_access & ~EXTRA_ACL) && - thd->db) - tables->grant.privilege= want_access; - else if (tables->db && thd->db && strcmp(tables->db, thd->db) == 0) - { - if (check_access(thd, want_access, tables->get_db_name(), - &tables->grant.privilege, 0, no_errors, - test(tables->schema_table))) - goto deny; // Access denied - } - else if (check_access(thd, want_access, tables->get_db_name(), - &tables->grant.privilege, 0, no_errors, - test(tables->schema_table))) + + if (check_access(thd, want_access, tables->get_db_name(), + &tables->grant.privilege, + &tables->grant.m_internal, + 0, no_errors)) goto deny; } thd->security_ctx= backup_ctx; - return check_grant(thd,want_access & ~EXTRA_ACL,org_tables, - test(want_access & EXTRA_ACL), number, no_errors); + return check_grant(thd,requirements,org_tables, + any_combination_of_privileges_will_do, + number, no_errors); deny: thd->security_ctx= backup_ctx; return TRUE; @@ -5560,14 +4974,23 @@ check_routine_access(THD *thd, ulong want_access,char *db, char *name, calculating db_access) under the assumption that it's common to give persons global right to execute all stored SP (but not necessary to create them). + Note that this effectively bypasses the ACL_internal_schema_access checks + that are implemented for the INFORMATION_SCHEMA and PERFORMANCE_SCHEMA, + which are located in check_access(). + Since the I_S and P_S do not contain routines, this bypass is ok, + as long as this code path is not abused to create routines. + The assert enforce that. */ + DBUG_ASSERT((want_access & CREATE_PROC_ACL) == 0); if ((thd->security_ctx->master_access & want_access) == want_access) tables->grant.privilege= want_access; - else if (check_access(thd,want_access,db,&tables->grant.privilege, - 0, no_errors, 0)) + else if (check_access(thd, want_access, db, + &tables->grant.privilege, + &tables->grant.m_internal, + 0, no_errors)) return TRUE; - return check_grant_routine(thd, want_access, tables, is_proc, no_errors); + return check_grant_routine(thd, want_access, tables, is_proc, no_errors); } @@ -5588,13 +5011,18 @@ bool check_some_routine_access(THD *thd, const char *db, const char *name, bool is_proc) { ulong save_priv; - if (thd->security_ctx->master_access & SHOW_PROC_ACLS) - return FALSE; /* - There are no routines in information_schema db. So we can safely - pass zero to last paramter of check_access function + The following test is just a shortcut for check_access() (to avoid + calculating db_access) + Note that this effectively bypasses the ACL_internal_schema_access checks + that are implemented for the INFORMATION_SCHEMA and PERFORMANCE_SCHEMA, + which are located in check_access(). + Since the I_S and P_S do not contain routines, this bypass is ok, + as it only opens SHOW_PROC_ACLS. */ - if (!check_access(thd, SHOW_PROC_ACLS, db, &save_priv, 0, 1, 0) || + if (thd->security_ctx->master_access & SHOW_PROC_ACLS) + return FALSE; + if (!check_access(thd, SHOW_PROC_ACLS, db, &save_priv, NULL, 0, 1) || (save_priv & SHOW_PROC_ACLS)) return FALSE; return check_routine_level_acl(thd, db, name, is_proc); @@ -5624,9 +5052,10 @@ bool check_some_access(THD *thd, ulong want_access, TABLE_LIST *table) if (access & want_access) { if (!check_access(thd, access, table->db, - &table->grant.privilege, 0, 1, - test(table->schema_table)) && - !check_grant(thd, access, table, 0, 1, 1)) + &table->grant.privilege, + &table->grant.m_internal, + 0, 1) && + !check_grant(thd, access, table, FALSE, 1, TRUE)) DBUG_RETURN(0); } } @@ -5673,7 +5102,6 @@ bool check_global_access(THD *thd, ulong want_access) Check stack size; Send error if there isn't enough stack to continue ****************************************************************************/ -#ifndef EMBEDDED_LIBRARY #if STACK_DIRECTION < 0 #define used_stack(A,B) (long) (A - B) @@ -5700,11 +5128,17 @@ bool check_stack_overrun(THD *thd, long margin, if ((stack_used=used_stack(thd->thread_stack,(char*) &stack_used)) >= (long) (my_thread_stack_size - margin)) { - char ebuff[MYSQL_ERRMSG_SIZE]; - my_snprintf(ebuff, sizeof(ebuff), ER(ER_STACK_OVERRUN_NEED_MORE), - stack_used, my_thread_stack_size, margin); - my_message(ER_STACK_OVERRUN_NEED_MORE, ebuff, MYF(ME_FATALERROR)); - thd->fatal_error(); + /* + Do not use stack for the message buffer to ensure correct + behaviour in cases we have close to no stack left. + */ + char* ebuff= new char[MYSQL_ERRMSG_SIZE]; + if (ebuff) { + my_snprintf(ebuff, MYSQL_ERRMSG_SIZE, ER(ER_STACK_OVERRUN_NEED_MORE), + stack_used, my_thread_stack_size, margin); + my_message(ER_STACK_OVERRUN_NEED_MORE, ebuff, MYF(ME_FATALERROR)); + delete [] ebuff; + } return 1; } #ifndef DBUG_OFF @@ -5712,7 +5146,7 @@ bool check_stack_overrun(THD *thd, long margin, #endif return 0; } -#endif /* EMBEDDED_LIBRARY */ + #define MY_YACC_INIT 1000 // Start with big alloc #define MY_YACC_MAX 32000 // Because of 'short' @@ -5753,21 +5187,26 @@ bool my_yyoverflow(short **yyss, YYSTYPE **yyvs, ulong *yystacksize) /** - Reset THD part responsible for command processing state. + Reset the part of THD responsible for the state of command + processing. - This needs to be called before execution of every statement - (prepared or conventional). - It is not called by substatements of routines. + This needs to be called before execution of every statement + (prepared or conventional). It is not called by substatements of + routines. - @todo - Make it a method of THD and align its name with the rest of - reset/end/start/init methods. - @todo - Call it after we use THD for queries, not before. -*/ + @todo Remove mysql_reset_thd_for_next_command and only use the + member function. + @todo Call it after we use THD for queries, not before. +*/ void mysql_reset_thd_for_next_command(THD *thd) { + thd->reset_for_next_command(); +} + +void THD::reset_for_next_command() +{ + THD *thd= this; DBUG_ENTER("mysql_reset_thd_for_next_command"); DBUG_ASSERT(!thd->spcont); /* not for substatements of routines */ DBUG_ASSERT(! thd->in_sub_stmt); @@ -5792,9 +5231,9 @@ void mysql_reset_thd_for_next_command(THD *thd) OPTION_STATUS_NO_TRANS_UPDATE | OPTION_KEEP_LOG to not get warnings in ha_rollback_trans() about some tables couldn't be rolled back. */ - if (!(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) + if (!thd->in_multi_stmt_transaction_mode()) { - thd->options&= ~OPTION_KEEP_LOG; + thd->variables.option_bits&= ~OPTION_KEEP_LOG; thd->transaction.all.modified_non_trans_table= FALSE; } DBUG_ASSERT(thd->security_ctx== &thd->main_security_ctx); @@ -5806,20 +5245,17 @@ void mysql_reset_thd_for_next_command(THD *thd) thd->user_var_events_alloc= thd->mem_root; } thd->clear_error(); - thd->main_da.reset_diagnostics_area(); - thd->total_warn_count=0; // Warnings for this query + thd->stmt_da->reset_diagnostics_area(); + thd->warning_info->reset_for_next_command(); thd->rand_used= 0; thd->sent_row_count= thd->examined_row_count= 0; - /* - Because we come here only for start of top-statements, binlog format is - constant inside a complex statement (using stored functions) etc. - */ - thd->reset_current_stmt_binlog_row_based(); + thd->reset_current_stmt_binlog_format_row(); + thd->binlog_unsafe_warning_flags= 0; DBUG_PRINT("debug", - ("current_stmt_binlog_row_based: %d", - thd->current_stmt_binlog_row_based)); + ("is_current_stmt_binlog_format_row(): %d", + thd->is_current_stmt_binlog_format_row())); DBUG_VOID_RETURN; } @@ -5974,7 +5410,6 @@ void mysql_init_multi_delete(LEX *lex) lex->select_lex.select_limit= 0; lex->unit.select_limit_cnt= HA_POS_ERROR; lex->select_lex.table_list.save_and_clear(&lex->auxiliary_table_list); - lex->lock_option= TL_READ_DEFAULT; lex->query_tables= 0; lex->query_tables_last= &lex->query_tables; } @@ -5996,8 +5431,9 @@ void mysql_init_multi_delete(LEX *lex) */ void mysql_parse(THD *thd, char *rawbuf, uint length, - const char ** found_semicolon) + Parser_state *parser_state) { + int error __attribute__((unused)); DBUG_ENTER("mysql_parse"); DBUG_EXECUTE_IF("parser_debug", turn_parser_debug_on();); @@ -6025,18 +5461,7 @@ void mysql_parse(THD *thd, char *rawbuf, uint length, { LEX *lex= thd->lex; - sp_cache_flush_obsolete(&thd->sp_proc_cache); - sp_cache_flush_obsolete(&thd->sp_func_cache); - - Parser_state parser_state; - bool err; - if (!(err= parser_state.init(thd, rawbuf, length))) - { - err= parse_sql(thd, & parser_state, NULL); - *found_semicolon= parser_state.m_lip.found_semicolon; - } - else - *found_semicolon= NULL; + bool err= parse_sql(thd, parser_state, NULL); if (!err) { @@ -6051,6 +5476,7 @@ void mysql_parse(THD *thd, char *rawbuf, uint length, { if (! thd->is_error()) { + const char *found_semicolon= parser_state->m_lip.found_semicolon; /* Binlog logs a string starting from thd->query and having length thd->query_length; so we set thd->query_length correctly (to not @@ -6061,18 +5487,27 @@ void mysql_parse(THD *thd, char *rawbuf, uint length, PROCESSLIST. Note that we don't need LOCK_thread_count to modify query_length. */ - if (*found_semicolon && (ulong) (*found_semicolon - thd->query())) + if (found_semicolon && (ulong) (found_semicolon - thd->query())) thd->set_query_inner(thd->query(), - (uint32) (*found_semicolon - - thd->query() - 1)); + (uint32) (found_semicolon - + thd->query() - 1), + thd->charset()); /* Actually execute the query */ - if (*found_semicolon) + if (found_semicolon) { lex->safe_to_cache_query= 0; thd->server_status|= SERVER_MORE_RESULTS_EXISTS; } lex->set_trg_event_type_for_tables(); - mysql_execute_command(thd); + MYSQL_QUERY_EXEC_START(thd->query(), + thd->thread_id, + (char *) (thd->db ? thd->db : ""), + &thd->security_ctx->priv_user[0], + (char *) thd->security_ctx->host_or_ip, + 0); + + error= mysql_execute_command(thd); + MYSQL_QUERY_EXEC_DONE(error); } } } @@ -6082,24 +5517,13 @@ void mysql_parse(THD *thd, char *rawbuf, uint length, DBUG_PRINT("info",("Command aborted. Fatal_error: %d", thd->is_fatal_error)); - query_cache_abort(&thd->net); + query_cache_abort(&thd->query_cache_tls); } - if (thd->lex->sphead) - { - delete thd->lex->sphead; - thd->lex->sphead= 0; - } - lex->unit.cleanup(); thd_proc_info(thd, "freeing items"); thd->end_statement(); thd->cleanup_after_query(); DBUG_ASSERT(thd->change_list.is_empty()); } - else - { - /* There are no multi queries in the cache. */ - *found_semicolon= NULL; - } DBUG_VOID_RETURN; } @@ -6169,8 +5593,8 @@ bool add_field_to_list(THD *thd, LEX_STRING *field_name, enum_field_types type, if (type_modifier & PRI_KEY_FLAG) { Key *key; - lex->col_list.push_back(new Key_part_spec(field_name->str, 0)); - key= new Key(Key::PRIMARY, NullS, + 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); lex->alter_info.key_list.push_back(key); @@ -6179,8 +5603,8 @@ bool add_field_to_list(THD *thd, LEX_STRING *field_name, enum_field_types type, if (type_modifier & (UNIQUE_FLAG | UNIQUE_KEY_FLAG)) { Key *key; - lex->col_list.push_back(new Key_part_spec(field_name->str, 0)); - key= new Key(Key::UNIQUE, NullS, + 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); lex->alter_info.key_list.push_back(key); @@ -6226,17 +5650,6 @@ bool add_field_to_list(THD *thd, LEX_STRING *field_name, enum_field_types type, DBUG_RETURN(1); } - if (type == MYSQL_TYPE_TIMESTAMP && length) - { - /* Display widths are no longer supported for TIMSTAMP as of MySQL 4.1. - In other words, for declarations such as TIMESTAMP(2), TIMESTAMP(4), - and so on, the display width is ignored. - */ - char buf[32]; - my_snprintf(buf, sizeof(buf), "TIMESTAMP(%s)", length); - WARN_DEPRECATED(thd, "6.0", buf, "'TIMESTAMP'"); - } - if (!(new_field= new Create_field()) || new_field->init(thd, field_name->str, type, length, decimals, type_modifier, default_value, on_update_value, comment, change, @@ -6253,7 +5666,7 @@ bool add_field_to_list(THD *thd, LEX_STRING *field_name, enum_field_types type, void store_position_for_column(const char *name) { - current_thd->lex->last_field->after=my_const_cast(char*) (name); + current_thd->lex->last_field->after=(char*) (name); } bool @@ -6304,6 +5717,7 @@ bool add_to_list(THD *thd, SQL_I_List<ORDER> &list, Item *item,bool asc) - TL_OPTION_FORCE_INDEX : Force usage of index - TL_OPTION_ALIAS : an alias in multi table DELETE @param lock_type How table should be locked + @param mdl_type Type of metadata lock to acquire on the table. @param use_index List of indexed used in USE INDEX @param ignore_index List of indexed used in IGNORE INDEX @@ -6318,6 +5732,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, LEX_STRING *alias, ulong table_options, thr_lock_type lock_type, + enum_mdl_type mdl_type, List<Index_hint> *index_hints_arg, LEX_STRING *option) { @@ -6360,13 +5775,17 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, DBUG_RETURN(0); /* purecov: inspected */ if (table->db.str) { + ptr->is_fqtn= TRUE; ptr->db= table->db.str; ptr->db_length= table->db.length; } else if (lex->copy_db_to(&ptr->db, &ptr->db_length)) DBUG_RETURN(0); + else + ptr->is_fqtn= FALSE; 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); ptr->table_name=table->table.str; @@ -6377,9 +5796,21 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->force_index= test(table_options & TL_OPTION_FORCE_INDEX); ptr->ignore_leaves= test(table_options & TL_OPTION_IGNORE_LEAVES); ptr->derived= table->sel; - if (!ptr->derived && is_schema_db(ptr->db, ptr->db_length)) + if (!ptr->derived && is_infoschema_db(ptr->db, ptr->db_length)) { - ST_SCHEMA_TABLE *schema_table= find_schema_table(thd, ptr->table_name); + ST_SCHEMA_TABLE *schema_table; + if (ptr->updating && + /* Special cases which are processed by commands itself */ + lex->sql_command != SQLCOM_CHECK && + lex->sql_command != SQLCOM_CHECKSUM) + { + my_error(ER_DBACCESS_DENIED_ERROR, MYF(0), + thd->security_ctx->priv_user, + thd->security_ctx->priv_host, + INFORMATION_SCHEMA_NAME.str); + DBUG_RETURN(0); + } + schema_table= find_schema_table(thd, ptr->table_name); if (!schema_table || (schema_table->hidden && ((sql_command_flags[lex->sql_command] & CF_STATUS_COMMAND) == 0 || @@ -6449,6 +5880,8 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->next_name_resolution_table= NULL; /* Link table in global list (all used tables) */ lex->add_to_query_tables(ptr); + ptr->mdl_request.init(MDL_key::TABLE, ptr->db, ptr->table_name, mdl_type, + MDL_TRANSACTION); DBUG_RETURN(ptr); } @@ -6684,6 +6117,8 @@ void st_select_lex::set_lock_for_tables(thr_lock_type lock_type) { tables->lock_type= lock_type; tables->updating= for_update; + tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? + MDL_SHARED_WRITE : MDL_SHARED_READ); } DBUG_VOID_RETURN; } @@ -6864,225 +6299,6 @@ void add_join_natural(TABLE_LIST *a, TABLE_LIST *b, List<String> *using_fields, /** - Reload/resets privileges and the different caches. - - @param thd Thread handler (can be NULL!) - @param options What should be reset/reloaded (tables, privileges, slave...) - @param tables Tables to flush (if any) - @param write_to_binlog < 0 if there was an error while interacting with the binary log inside - reload_acl_and_cache, - 0 if we should not write to the binary log, - > 0 if we can write to the binlog. - - @note Depending on 'options', it may be very bad to write the - query to the binlog (e.g. FLUSH SLAVE); this is a - pointer where reload_acl_and_cache() will put 0 if - it thinks we really should not write to the binlog. - Otherwise it will put 1. - - @return Error status code - @retval 0 Ok - @retval !=0 Error; thd->killed is set or thd->is_error() is true -*/ - -bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, - int *write_to_binlog) -{ - bool result=0; - select_errors=0; /* Write if more errors */ - int tmp_write_to_binlog= *write_to_binlog= 1; - - DBUG_ASSERT(!thd || !thd->in_sub_stmt); - -#ifndef NO_EMBEDDED_ACCESS_CHECKS - if (options & REFRESH_GRANT) - { - THD *tmp_thd= 0; - /* - If reload_acl_and_cache() is called from SIGHUP handler we have to - allocate temporary THD for execution of acl_reload()/grant_reload(). - */ - if (!thd && (thd= (tmp_thd= new THD))) - { - thd->thread_stack= (char*) &tmp_thd; - thd->store_globals(); - lex_start(thd); - } - - if (thd) - { - bool reload_acl_failed= acl_reload(thd); - bool reload_grants_failed= grant_reload(thd); - bool reload_servers_failed= servers_reload(thd); - - if (reload_acl_failed || reload_grants_failed || reload_servers_failed) - { - result= 1; - /* - When an error is returned, my_message may have not been called and - the client will hang waiting for a response. - */ - my_error(ER_UNKNOWN_ERROR, MYF(0), "FLUSH PRIVILEGES failed"); - } - } - - if (tmp_thd) - { - delete tmp_thd; - /* Remember that we don't have a THD */ - my_pthread_setspecific_ptr(THR_THD, 0); - thd= 0; - } - reset_mqh((LEX_USER *)NULL, TRUE); - } -#endif - if (options & REFRESH_LOG) - { - /* - Flush the normal query log, the update log, the binary log, - the slow query log, the relay log (if it exists) and the log - tables. - */ - - /* - Writing this command to the binlog may result in infinite loops - when doing mysqlbinlog|mysql, and anyway it does not really make - sense to log it automatically (would cause more trouble to users - than it would help them) - */ - tmp_write_to_binlog= 0; - if( mysql_bin_log.is_open() ) - { - if (mysql_bin_log.rotate_and_purge(RP_FORCE_ROTATE)) - *write_to_binlog= -1; - } -#ifdef HAVE_REPLICATION - int rotate_error= 0; - pthread_mutex_lock(&LOCK_active_mi); - rotate_error= rotate_relay_log(active_mi); - pthread_mutex_unlock(&LOCK_active_mi); - if (rotate_error) - *write_to_binlog= -1; -#endif - - /* flush slow and general logs */ - logger.flush_logs(thd); - - if (ha_flush_logs(NULL)) - result=1; - if (flush_error_log()) - result=1; - } -#ifdef HAVE_QUERY_CACHE - if (options & REFRESH_QUERY_CACHE_FREE) - { - query_cache.pack(); // FLUSH QUERY CACHE - options &= ~REFRESH_QUERY_CACHE; // Don't flush cache, just free memory - } - if (options & (REFRESH_TABLES | REFRESH_QUERY_CACHE)) - { - query_cache.flush(); // RESET QUERY CACHE - } -#endif /*HAVE_QUERY_CACHE*/ - /* - Note that if REFRESH_READ_LOCK bit is set then REFRESH_TABLES is set too - (see sql_yacc.yy) - */ - if (options & (REFRESH_TABLES | REFRESH_READ_LOCK)) - { - if ((options & REFRESH_READ_LOCK) && thd) - { - /* - We must not try to aspire a global read lock if we have a write - locked table. This would lead to a deadlock when trying to - reopen (and re-lock) the table after the flush. - */ - if (thd->locked_tables) - { - THR_LOCK_DATA **lock_p= thd->locked_tables->locks; - THR_LOCK_DATA **end_p= lock_p + thd->locked_tables->lock_count; - - for (; lock_p < end_p; lock_p++) - { - if ((*lock_p)->type >= TL_WRITE_ALLOW_WRITE) - { - my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); - return 1; - } - } - } - /* - Writing to the binlog could cause deadlocks, as we don't log - UNLOCK TABLES - */ - tmp_write_to_binlog= 0; - if (lock_global_read_lock(thd)) - return 1; // Killed - if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ? - FALSE : TRUE, TRUE)) - result= 1; - - if (make_global_read_lock_block_commit(thd)) // Killed - { - /* Don't leave things in a half-locked state */ - unlock_global_read_lock(thd); - return 1; - } - } - else - { - if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ? - FALSE : TRUE, FALSE)) - result= 1; - } - my_dbopt_cleanup(); - } - if (options & REFRESH_HOSTS) - hostname_cache_refresh(); - if (thd && (options & REFRESH_STATUS)) - refresh_status(thd); - if (options & REFRESH_THREADS) - flush_thread_cache(); -#ifdef HAVE_REPLICATION - if (options & REFRESH_MASTER) - { - DBUG_ASSERT(thd); - tmp_write_to_binlog= 0; - if (reset_master(thd)) - { - result=1; - } - } -#endif -#ifdef OPENSSL - if (options & REFRESH_DES_KEY_FILE) - { - if (des_key_file && load_des_key_file(des_key_file)) - result= 1; - } -#endif -#ifdef HAVE_REPLICATION - if (options & REFRESH_SLAVE) - { - tmp_write_to_binlog= 0; - pthread_mutex_lock(&LOCK_active_mi); - if (reset_slave(thd, active_mi)) - result=1; - pthread_mutex_unlock(&LOCK_active_mi); - } -#endif - if (options & REFRESH_USER_RESOURCES) - reset_mqh((LEX_USER *) NULL, 0); /* purecov: inspected */ - if (*write_to_binlog != -1) - *write_to_binlog= tmp_write_to_binlog; - /* - If the query was killed then this function must fail. - */ - return result || (thd ? thd->killed : 0); -} - - -/** kill on thread. @param thd Thread class @@ -7099,7 +6315,7 @@ uint kill_one_thread(THD *thd, ulong id, bool only_kill_query) uint error=ER_NO_SUCH_THREAD; DBUG_ENTER("kill_one_thread"); DBUG_PRINT("enter", ("id=%lu only_kill=%d", id, only_kill_query)); - VOID(pthread_mutex_lock(&LOCK_thread_count)); // For unlink from list + mysql_mutex_lock(&LOCK_thread_count); // For unlink from list I_List_iterator<THD> it(threads); while ((tmp=it++)) { @@ -7107,11 +6323,11 @@ uint kill_one_thread(THD *thd, ulong id, bool only_kill_query) continue; if (tmp->thread_id == id) { - pthread_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete + mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete break; } } - VOID(pthread_mutex_unlock(&LOCK_thread_count)); + mysql_mutex_unlock(&LOCK_thread_count); if (tmp) { @@ -7140,7 +6356,7 @@ uint kill_one_thread(THD *thd, ulong id, bool only_kill_query) } else error=ER_KILL_DENIED_ERROR; - pthread_mutex_unlock(&tmp->LOCK_thd_data); + mysql_mutex_unlock(&tmp->LOCK_thd_data); } DBUG_PRINT("exit", ("%d", error)); DBUG_RETURN(error); @@ -7157,11 +6373,15 @@ uint kill_one_thread(THD *thd, ulong id, bool only_kill_query) only_kill_query Should it kill the query or the connection */ +static void sql_kill(THD *thd, ulong id, bool only_kill_query) { uint error; if (!(error= kill_one_thread(thd, id, only_kill_query))) - my_ok(thd); + { + if (! thd->killed) + my_ok(thd); + } else my_error(error, MYF(0), id); } @@ -7321,13 +6541,15 @@ bool multi_update_precheck(THD *thd, TABLE_LIST *tables) if (table->derived) table->grant.privilege= SELECT_ACL; else if ((check_access(thd, UPDATE_ACL, table->db, - &table->grant.privilege, 0, 1, - test(table->schema_table)) || - check_grant(thd, UPDATE_ACL, table, 0, 1, 1)) && + &table->grant.privilege, + &table->grant.m_internal, + 0, 1) || + check_grant(thd, UPDATE_ACL, table, FALSE, 1, TRUE)) && (check_access(thd, SELECT_ACL, table->db, - &table->grant.privilege, 0, 0, - test(table->schema_table)) || - check_grant(thd, SELECT_ACL, table, 0, 1, 0))) + &table->grant.privilege, + &table->grant.m_internal, + 0, 0) || + check_grant(thd, SELECT_ACL, table, FALSE, 1, FALSE))) DBUG_RETURN(TRUE); table->table_in_first_from_clause= 1; @@ -7343,9 +6565,10 @@ bool multi_update_precheck(THD *thd, TABLE_LIST *tables) if (!table->table_in_first_from_clause) { if (check_access(thd, SELECT_ACL, table->db, - &table->grant.privilege, 0, 0, - test(table->schema_table)) || - check_grant(thd, SELECT_ACL, table, 0, 1, 0)) + &table->grant.privilege, + &table->grant.m_internal, + 0, 0) || + check_grant(thd, SELECT_ACL, table, FALSE, 1, FALSE)) DBUG_RETURN(TRUE); } } @@ -7384,7 +6607,7 @@ bool multi_delete_precheck(THD *thd, TABLE_LIST *tables) /* sql_yacc guarantees that tables and aux_tables are not zero */ DBUG_ASSERT(aux_tables != 0); - if (check_table_access(thd, SELECT_ACL, tables, UINT_MAX, FALSE)) + if (check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE)) DBUG_RETURN(TRUE); /* @@ -7393,14 +6616,14 @@ bool multi_delete_precheck(THD *thd, TABLE_LIST *tables) call check_table_access() safely. */ thd->lex->query_tables_own_last= 0; - if (check_table_access(thd, DELETE_ACL, aux_tables, UINT_MAX, FALSE)) + if (check_table_access(thd, DELETE_ACL, aux_tables, FALSE, UINT_MAX, FALSE)) { thd->lex->query_tables_own_last= save_query_tables_own_last; DBUG_RETURN(TRUE); } thd->lex->query_tables_own_last= save_query_tables_own_last; - if ((thd->options & OPTION_SAFE_UPDATES) && !select_lex->where) + if ((thd->variables.option_bits & OPTION_SAFE_UPDATES) && !select_lex->where) { my_message(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE, ER(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE), MYF(0)); @@ -7410,6 +6633,63 @@ bool multi_delete_precheck(THD *thd, TABLE_LIST *tables) } +/* + Given a table in the source list, find a correspondent table in the + table references list. + + @param lex Pointer to LEX representing multi-delete. + @param src Source table to match. + @param ref Table references list. + + @remark The source table list (tables listed before the FROM clause + or tables listed in the FROM clause before the USING clause) may + contain table names or aliases that must match unambiguously one, + and only one, table in the target table list (table references list, + after FROM/USING clause). + + @return Matching table, NULL otherwise. +*/ + +static TABLE_LIST *multi_delete_table_match(LEX *lex, TABLE_LIST *tbl, + TABLE_LIST *tables) +{ + TABLE_LIST *match= NULL; + DBUG_ENTER("multi_delete_table_match"); + + for (TABLE_LIST *elem= tables; elem; elem= elem->next_local) + { + int cmp; + + if (tbl->is_fqtn && elem->is_alias) + continue; /* no match */ + if (tbl->is_fqtn && elem->is_fqtn) + cmp= my_strcasecmp(table_alias_charset, tbl->table_name, elem->table_name) || + strcmp(tbl->db, elem->db); + else if (elem->is_alias) + cmp= my_strcasecmp(table_alias_charset, tbl->alias, elem->alias); + else + cmp= my_strcasecmp(table_alias_charset, tbl->table_name, elem->table_name) || + strcmp(tbl->db, elem->db); + + if (cmp) + continue; + + if (match) + { + my_error(ER_NONUNIQ_TABLE, MYF(0), elem->alias); + DBUG_RETURN(NULL); + } + + match= elem; + } + + if (!match) + my_error(ER_UNKNOWN_TABLE, MYF(0), tbl->table_name, "MULTI DELETE"); + + DBUG_RETURN(match); +} + + /** Link tables in auxilary table list of multi-delete with corresponding elements in main table list, and set proper locks for them. @@ -7435,20 +6715,9 @@ bool multi_delete_set_locks_and_link_aux_tables(LEX *lex) { lex->table_count++; /* All tables in aux_tables must be found in FROM PART */ - TABLE_LIST *walk; - for (walk= tables; walk; walk= walk->next_local) - { - if (!my_strcasecmp(table_alias_charset, - target_tbl->alias, walk->alias) && - !strcmp(walk->db, target_tbl->db)) - break; - } + TABLE_LIST *walk= multi_delete_table_match(lex, target_tbl, tables); if (!walk) - { - my_error(ER_UNKNOWN_TABLE, MYF(0), - target_tbl->table_name, "MULTI DELETE"); DBUG_RETURN(TRUE); - } if (!walk->derived) { target_tbl->table_name= walk->table_name; @@ -7456,6 +6725,9 @@ bool multi_delete_set_locks_and_link_aux_tables(LEX *lex) } walk->updating= target_tbl->updating; walk->lock_type= target_tbl->lock_type; + /* We can assume that tables to be deleted from are locked for write. */ + DBUG_ASSERT(walk->lock_type >= TL_WRITE_ALLOW_WRITE); + walk->mdl_request.set_type(MDL_SHARED_WRITE); target_tbl->correspondent_table= walk; // Remember corresponding table } DBUG_RETURN(FALSE); @@ -7547,21 +6819,30 @@ bool insert_precheck(THD *thd, TABLE_LIST *tables) /** - @brief Check privileges for SHOW CREATE TABLE statement. - - @param thd Thread context - @param table Target table - - @retval TRUE Failure - @retval FALSE Success + Set proper open mode and table type for element representing target table + of CREATE TABLE statement, also adjust statement table list if necessary. */ -static bool check_show_create_table_access(THD *thd, TABLE_LIST *table) +void create_table_set_open_action_and_adjust_tables(LEX *lex) { - return check_access(thd, SELECT_ACL | EXTRA_ACL, table->db, - &table->grant.privilege, 0, 0, - test(table->schema_table)) || - check_grant(thd, SELECT_ACL, table, 2, UINT_MAX, 0); + TABLE_LIST *create_table= lex->query_tables; + + if (lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) + create_table->open_type= OT_TEMPORARY_ONLY; + else + create_table->open_type= OT_BASE_ONLY; + + if (!lex->select_lex.item_list.elements) + { + /* + Avoid opening and locking target table for ordinary CREATE TABLE + or CREATE TABLE LIKE for write (unlike in CREATE ... SELECT we + won't do any insertions in it anyway). Not doing this causes + problems when running CREATE TABLE IF NOT EXISTS for already + existing log table. + */ + create_table->lock_type= TL_READ; + } } @@ -7597,46 +6878,32 @@ bool create_table_precheck(THD *thd, TABLE_LIST *tables, (select_lex->item_list.elements ? INSERT_ACL : 0); if (check_access(thd, want_priv, create_table->db, - &create_table->grant.privilege, 0, 0, - test(create_table->schema_table)) || - check_merge_table_access(thd, create_table->db, - lex->create_info.merge_list.first)) + &create_table->grant.privilege, + &create_table->grant.m_internal, + 0, 0)) + 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 (want_priv != CREATE_TMP_ACL && - check_grant(thd, want_priv, create_table, 0, 1, 0)) + check_grant(thd, want_priv, create_table, FALSE, 1, FALSE)) goto err; if (select_lex->item_list.elements) { /* Check permissions for used tables in CREATE TABLE ... SELECT */ - -#ifdef NOT_NECESSARY_TO_CHECK_CREATE_TABLE_EXIST_WHEN_PREPARING_STATEMENT - /* This code throws an ill error for CREATE TABLE t1 SELECT * FROM t1 */ - /* - Only do the check for PS, because we on execute we have to check that - against the opened tables to ensure we don't use a table that is part - of the view (which can only be done after the table has been opened). - */ - if (thd->stmt_arena->is_stmt_prepare_or_first_sp_execute()) - { - /* - For temporary tables we don't have to check if the created table exists - */ - if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) && - find_table_in_global_list(tables, create_table->db, - create_table->table_name)) - { - error= FALSE; - goto err; - } - } -#endif - if (tables && check_table_access(thd, SELECT_ACL, tables, UINT_MAX, FALSE)) + if (tables && check_table_access(thd, SELECT_ACL, tables, FALSE, + UINT_MAX, FALSE)) goto err; } else if (lex->create_info.options & HA_LEX_CREATE_TABLE_LIKE) { - if (check_show_create_table_access(thd, tables)) + if (check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE)) goto err; } error= FALSE; @@ -7697,8 +6964,9 @@ void get_default_definer(THD *thd, LEX_USER *definer) definer->host.str= (char *) sctx->priv_host; definer->host.length= strlen(definer->host.str); - definer->password.str= NULL; - definer->password.length= 0; + definer->password= null_lex_str; + definer->plugin= empty_lex_str; + definer->auth= empty_lex_str; } @@ -7834,7 +7102,10 @@ bool check_string_char_length(LEX_STRING *str, const char *err_msg, return FALSE; if (!no_error) - my_error(ER_WRONG_STRING_LENGTH, MYF(0), str->str, err_msg, max_char_length); + { + ErrConvString err(str->str, str->length, cs); + my_error(ER_WRONG_STRING_LENGTH, MYF(0), err.ptr(), err_msg, max_char_length); + } return TRUE; } @@ -7942,8 +7213,11 @@ bool parse_sql(THD *thd, Parser_state *parser_state, Object_creation_ctx *creation_ctx) { + bool ret_value; DBUG_ASSERT(thd->m_parser_state == NULL); + DBUG_ASSERT(thd->lex->m_stmt == NULL); + MYSQL_QUERY_PARSE_START(thd->query()); /* Backup creation context. */ Object_creation_ctx *backup_ctx= NULL; @@ -7975,9 +7249,45 @@ bool parse_sql(THD *thd, /* That's it. */ - return mysql_parse_status || thd->is_fatal_error; + ret_value= mysql_parse_status || thd->is_fatal_error; + MYSQL_QUERY_PARSE_DONE(ret_value); + return ret_value; } /** @} (end of group Runtime_Environment) */ + + + +/** + Check and merge "CHARACTER SET cs [ COLLATE cl ]" clause + + @param cs character set pointer. + @param cl collation pointer. + + Check if collation "cl" is applicable to character set "cs". + + If "cl" is NULL (e.g. when COLLATE clause is not specified), + then simply "cs" is returned. + + @return Error status. + @retval NULL, if "cl" is not applicable to "cs". + @retval pointer to merged CHARSET_INFO on success. +*/ + + +CHARSET_INFO* +merge_charset_and_collation(CHARSET_INFO *cs, CHARSET_INFO *cl) +{ + if (cl) + { + if (!my_charset_same(cs, cl)) + { + my_error(ER_COLLATION_CHARSET_MISMATCH, MYF(0), cl->name, cs->csname); + return NULL; + } + return cl; + } + return cs; +} |