diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index a030f547d59..813f3fb3d57 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -133,6 +133,10 @@ static void sql_kill_user(THD *thd, LEX_USER *user, killed_state state); static bool lock_tables_precheck(THD *thd, TABLE_LIST *tables); static bool execute_show_status(THD *, TABLE_LIST *); static bool check_rename_table(THD *, TABLE_LIST *, TABLE_LIST *); +static bool generate_incident_event(THD *thd); +static int show_create_db(THD *thd, LEX *lex); +static bool alter_routine(THD *thd, LEX *lex); +static bool drop_routine(THD *thd, LEX *lex); const char *any_db="*any*"; // Special symbol for check_access @@ -2873,7 +2877,8 @@ bool sp_process_definer(THD *thd) @return FALSE in case of success, TRUE in case of error. */ -static bool lock_tables_open_and_lock_tables(THD *thd, TABLE_LIST *tables) +static bool __attribute__ ((noinline)) +lock_tables_open_and_lock_tables(THD *thd, TABLE_LIST *tables) { Lock_tables_prelocking_strategy lock_tables_prelocking_strategy; MDL_deadlock_and_lock_abort_error_handler deadlock_handler; @@ -3034,7 +3039,8 @@ static bool do_execute_sp(THD *thd, sp_head *sp) } -static int mysql_create_routine(THD *thd, LEX *lex) +static int __attribute__ ((noinline)) +mysql_create_routine(THD *thd, LEX *lex) { DBUG_ASSERT(lex->sphead != 0); DBUG_ASSERT(lex->sphead->m_db.str); /* Must be initialized in the parser */ @@ -4443,40 +4449,8 @@ mysql_execute_command(THD *thd) break; } case SQLCOM_REPLACE: -#ifndef DBUG_OFF - if (mysql_bin_log.is_open()) - { - /* - Generate an incident log event before writing the real event - to the binary log. We put this event is before the statement - since that makes it simpler to check that the statement was - not executed on the slave (since incidents usually stop the - slave). - - Observe that any row events that are generated will be - generated before. - - This is only for testing purposes and will not be present in a - release build. - */ - - Incident incident= INCIDENT_NONE; - DBUG_PRINT("debug", ("Just before generate_incident()")); - DBUG_EXECUTE_IF("incident_database_resync_on_replace", - incident= INCIDENT_LOST_EVENTS;); - if (incident) - { - Incident_log_event ev(thd, incident); - (void) mysql_bin_log.write(&ev); /* error is ignored */ - if (mysql_bin_log.rotate_and_purge(true)) - { - res= 1; - break; - } - } - DBUG_PRINT("debug", ("Just after generate_incident()")); - } -#endif + if ((res= generate_incident_event(thd))) + break; /* fall through */ case SQLCOM_INSERT: { @@ -5090,26 +5064,9 @@ mysql_execute_command(THD *thd) break; } case SQLCOM_SHOW_CREATE_DB: - { - char db_name_buff[NAME_LEN+1]; - LEX_CSTRING db_name; - DBUG_EXECUTE_IF("4x_server_emul", - my_error(ER_UNKNOWN_ERROR, MYF(0)); goto error;); - - db_name.str= db_name_buff; - db_name.length= lex->name.length; - strmov(db_name_buff, lex->name.str); - WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_SHOW); - - if (check_db_name((LEX_STRING*) &db_name)) - { - my_error(ER_WRONG_DB_NAME, MYF(0), db_name.str); - break; - } - res= mysqld_show_create_db(thd, &db_name, &lex->name, lex->create_info); + res= show_create_db(thd, lex); break; - } case SQLCOM_CREATE_EVENT: case SQLCOM_ALTER_EVENT: #ifdef HAVE_EVENT_SCHEDULER @@ -5689,153 +5646,16 @@ mysql_execute_command(THD *thd) case SQLCOM_ALTER_PROCEDURE: case SQLCOM_ALTER_FUNCTION: - { - int sp_result; - const Sp_handler *sph= Sp_handler::handler(lex->sql_command); - if (check_routine_access(thd, ALTER_PROC_ACL, &lex->spname->m_db, - &lex->spname->m_name, sph, 0)) - goto error; - - /* - 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= sph->sp_update_routine(thd, lex->spname, &lex->sp_chistics); - switch (sp_result) - { - case SP_OK: - my_ok(thd); - break; - case SP_KEY_NOT_FOUND: - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), - sph->type_str(), ErrConvDQName(lex->spname).ptr()); - goto error; - default: - my_error(ER_SP_CANT_ALTER, MYF(0), - sph->type_str(), ErrConvDQName(lex->spname).ptr()); - goto error; - } - break; - } + if (alter_routine(thd, lex)) + goto error; + break; case SQLCOM_DROP_PROCEDURE: case SQLCOM_DROP_FUNCTION: case SQLCOM_DROP_PACKAGE: case SQLCOM_DROP_PACKAGE_BODY: - { -#ifdef HAVE_DLOPEN - if (lex->sql_command == SQLCOM_DROP_FUNCTION && - ! lex->spname->m_explicit_name) - { - /* 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", NULL, NULL, 1, 0)) - goto error; - - 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 (lex->spname->m_db.str == NULL) - { - if (lex->if_exists()) - { - push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, - ER_SP_DOES_NOT_EXIST, ER_THD(thd, 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; - } - /* Fall thought to test for a stored function */ - } -#endif - - int sp_result; - const Sp_handler *sph= Sp_handler::handler(lex->sql_command); - - if (check_routine_access(thd, ALTER_PROC_ACL, &lex->spname->m_db, &lex->spname->m_name, - Sp_handler::handler(lex->sql_command), 0)) - goto error; - WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL); - - /* Conditionally writes to binlog */ - sp_result= sph->sp_drop_routine(thd, 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, lex->spname->m_db.str, lex->spname->m_name.str, - Sp_handler::handler(lex->sql_command))) - { - push_warning(thd, Sql_condition::WARN_LEVEL_WARN, - ER_PROC_AUTO_REVOKE_FAIL, - ER_THD(thd, 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: - my_ok(thd); - break; - case SP_KEY_NOT_FOUND: - if (lex->if_exists()) - { - res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); - push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, - ER_SP_DOES_NOT_EXIST, ER_THD(thd, ER_SP_DOES_NOT_EXIST), - sph->type_str(), - ErrConvDQName(lex->spname).ptr()); - if (!res) - my_ok(thd); - break; - } - my_error(ER_SP_DOES_NOT_EXIST, MYF(0), - sph->type_str(), ErrConvDQName(lex->spname).ptr()); - goto error; - default: - my_error(ER_SP_DROP_FAILED, MYF(0), - sph->type_str(), ErrConvDQName(lex->spname).ptr()); - goto error; - } - break; - } + if (drop_routine(thd, lex)) + goto error; + break; case SQLCOM_SHOW_CREATE_PROC: case SQLCOM_SHOW_CREATE_FUNC: case SQLCOM_SHOW_CREATE_PACKAGE: @@ -6358,7 +6178,15 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) } -static bool execute_show_status(THD *thd, TABLE_LIST *all_tables) +/** + SHOW STATUS + + Notes: This is noinline as we don't want to have system_status_var (> 3K) + to be on the stack of mysql_execute_command() +*/ + +static bool __attribute__ ((noinline)) +execute_show_status(THD *thd, TABLE_LIST *all_tables) { bool res; system_status_var old_status_var= thd->status_var; @@ -6443,8 +6271,9 @@ static TABLE *find_temporary_table_for_rename(THD *thd, } -static bool check_rename_table(THD *thd, TABLE_LIST *first_table, - TABLE_LIST *all_tables) +static bool __attribute__ ((noinline)) +check_rename_table(THD *thd, TABLE_LIST *first_table, + TABLE_LIST *all_tables) { DBUG_ASSERT(first_table == all_tables && first_table != 0); TABLE_LIST *table; @@ -6483,6 +6312,227 @@ static bool check_rename_table(THD *thd, TABLE_LIST *first_table, return 0; } +/* + Generate an incident log event before writing the real event + to the binary log. We put this event is before the statement + since that makes it simpler to check that the statement was + not executed on the slave (since incidents usually stop the + slave). + + Observe that any row events that are generated will be generated before. + + This is only for testing purposes and will not be present in a release build. +*/ + +#ifndef DBUG_OFF +static bool __attribute__ ((noinline)) generate_incident_event(THD *thd) +{ + if (mysql_bin_log.is_open()) + { + + Incident incident= INCIDENT_NONE; + DBUG_PRINT("debug", ("Just before generate_incident()")); + DBUG_EXECUTE_IF("incident_database_resync_on_replace", + incident= INCIDENT_LOST_EVENTS;); + if (incident) + { + Incident_log_event ev(thd, incident); + (void) mysql_bin_log.write(&ev); /* error is ignored */ + if (mysql_bin_log.rotate_and_purge(true)) + return 1; + } + DBUG_PRINT("debug", ("Just after generate_incident()")); + } + return 0; +} +#else +static bool generate_incident_event(THD *thd) +{ + return 0; +} +#endif + + +static int __attribute__ ((noinline)) +show_create_db(THD *thd, LEX *lex) +{ + char db_name_buff[NAME_LEN+1]; + LEX_CSTRING db_name; + DBUG_EXECUTE_IF("4x_server_emul", + my_error(ER_UNKNOWN_ERROR, MYF(0)); return 1;); + + db_name.str= db_name_buff; + db_name.length= lex->name.length; + strmov(db_name_buff, lex->name.str); + + if (check_db_name((LEX_STRING*) &db_name)) + { + my_error(ER_WRONG_DB_NAME, MYF(0), db_name.str); + return 1; + } + return mysqld_show_create_db(thd, &db_name, &lex->name, lex->create_info); +} + + +/** + Called on SQLCOM_ALTER_PROCEDURE and SQLCOM_ALTER_FUNCTION +*/ + +static bool __attribute__ ((noinline)) +alter_routine(THD *thd, LEX *lex) +{ + int sp_result; + const Sp_handler *sph= Sp_handler::handler(lex->sql_command); + if (check_routine_access(thd, ALTER_PROC_ACL, &lex->spname->m_db, + &lex->spname->m_name, sph, 0)) + return 1; + /* + 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= sph->sp_update_routine(thd, lex->spname, &lex->sp_chistics); + switch (sp_result) { + case SP_OK: + my_ok(thd); + return 0; + case SP_KEY_NOT_FOUND: + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), + sph->type_str(), ErrConvDQName(lex->spname).ptr()); + return 1; + default: + my_error(ER_SP_CANT_ALTER, MYF(0), + sph->type_str(), ErrConvDQName(lex->spname).ptr()); + return 1; + } + return 0; /* purecov: deadcode */ +} + + +static bool __attribute__ ((noinline)) +drop_routine(THD *thd, LEX *lex) +{ + int sp_result; +#ifdef HAVE_DLOPEN + if (lex->sql_command == SQLCOM_DROP_FUNCTION && + ! lex->spname->m_explicit_name) + { + /* 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", NULL, NULL, 1, 0)) + return 1; + + if (!mysql_drop_function(thd, &lex->spname->m_name)) + { + my_ok(thd); + return 0; + } + my_error(ER_SP_DROP_FAILED, MYF(0), + "FUNCTION (UDF)", lex->spname->m_name.str); + return 1; + } + + if (lex->spname->m_db.str == NULL) + { + if (lex->if_exists()) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_SP_DOES_NOT_EXIST, + ER_THD(thd, ER_SP_DOES_NOT_EXIST), + "FUNCTION (UDF)", lex->spname->m_name.str); + my_ok(thd); + return 0; + } + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), + "FUNCTION (UDF)", lex->spname->m_name.str); + return 1; + } + /* Fall trough to test for a stored function */ + } +#endif /* HAVE_DLOPEN */ + + const Sp_handler *sph= Sp_handler::handler(lex->sql_command); + + if (check_routine_access(thd, ALTER_PROC_ACL, &lex->spname->m_db, + &lex->spname->m_name, + Sp_handler::handler(lex->sql_command), 0)) + return 1; + + WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL); + + /* Conditionally writes to binlog */ + sp_result= sph->sp_drop_routine(thd, 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, lex->spname->m_db.str, lex->spname->m_name.str, + Sp_handler::handler(lex->sql_command))) + { + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + ER_PROC_AUTO_REVOKE_FAIL, + ER_THD(thd, ER_PROC_AUTO_REVOKE_FAIL)); + /* If this happens, an error should have been reported. */ + return 1; + } +#endif /* NO_EMBEDDED_ACCESS_CHECKS */ + + switch (sp_result) { + case SP_OK: + my_ok(thd); + return 0; + case SP_KEY_NOT_FOUND: + int res; + if (lex->if_exists()) + { + res= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_SP_DOES_NOT_EXIST, + ER_THD(thd, ER_SP_DOES_NOT_EXIST), + sph->type_str(), + ErrConvDQName(lex->spname).ptr()); + if (res) + return 1; + my_ok(thd); + return 0; + } + my_error(ER_SP_DOES_NOT_EXIST, MYF(0), + sph->type_str(), ErrConvDQName(lex->spname).ptr()); + return 1; + default: + my_error(ER_SP_DROP_FAILED, MYF(0), + sph->type_str(), ErrConvDQName(lex->spname).ptr()); + return 1; + } + +#ifdef WITH_WSREP +wsrep_error_label: + return 1; +#endif +} /** @brief Compare requested privileges with the privileges acquired from the @@ -9150,8 +9200,8 @@ void sql_kill(THD *thd, longlong id, killed_state state, killed_type type) } -static -void sql_kill_user(THD *thd, LEX_USER *user, killed_state state) +static void __attribute__ ((noinline)) +sql_kill_user(THD *thd, LEX_USER *user, killed_state state) { uint error; ha_rows rows; diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index 128d80ad226..6b64915ada2 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -2782,3 +2782,54 @@ bool wsrep_consistency_check(THD *thd) { return thd->wsrep_consistency_check == CONSISTENCY_CHECK_RUNNING; } + + +/* + Commit an empty transaction. + + If the transaction is real and the wsrep transaction is still active, + the transaction did not generate any rows or keys and is committed + as empty. Here the wsrep transaction is rolled back and after statement + step is performed to leave the wsrep transaction in the state as it + never existed. + + This should not be an inline functions as it requires a lot of stack space + because of WSREP_DBUG() usage. It's also not a function that is + frequently called. +*/ + +void wsrep_commit_empty(THD* thd, bool all) +{ + DBUG_ENTER("wsrep_commit_empty"); + WSREP_DEBUG("wsrep_commit_empty(%llu)", thd->thread_id); + if (wsrep_is_real(thd, all) && + wsrep_thd_is_local(thd) && + thd->wsrep_trx().active() && + thd->wsrep_trx().state() != wsrep::transaction::s_committed) + { + /* @todo CTAS with STATEMENT binlog format and empty result set + seems to be committing empty. Figure out why and try to fix + elsewhere. */ + DBUG_ASSERT(!wsrep_has_changes(thd) || + (thd->lex->sql_command == SQLCOM_CREATE_TABLE && + !thd->is_current_stmt_binlog_format_row())); + bool have_error= wsrep_current_error(thd); + int ret= wsrep_before_rollback(thd, all) || + wsrep_after_rollback(thd, all) || + wsrep_after_statement(thd); + /* The committing transaction was empty but it held some locks and + got BF aborted. As there were no certified changes in the + data, we ignore the deadlock error and rely on error reporting + by storage engine/server. */ + if (!ret && !have_error && wsrep_current_error(thd)) + { + DBUG_ASSERT(wsrep_current_error(thd) == wsrep::e_deadlock_error); + thd->wsrep_cs().reset_error(); + } + if (ret) + { + WSREP_DEBUG("wsrep_commit_empty failed: %d", wsrep_current_error(thd)); + } + } + DBUG_VOID_RETURN; +} diff --git a/sql/wsrep_trans_observer.h b/sql/wsrep_trans_observer.h index 64ac80783c9..69756786dd2 100644 --- a/sql/wsrep_trans_observer.h +++ b/sql/wsrep_trans_observer.h @@ -26,6 +26,8 @@ class THD; +void wsrep_commit_empty(THD* thd, bool all); + /* Return true if THD has active wsrep transaction. */ @@ -466,50 +468,4 @@ wsrep_current_error_status(THD* thd) return thd->wsrep_cs().current_error_status(); } - -/* - Commit an empty transaction. - - If the transaction is real and the wsrep transaction is still active, - the transaction did not generate any rows or keys and is committed - as empty. Here the wsrep transaction is rolled back and after statement - step is performed to leave the wsrep transaction in the state as it - never existed. -*/ -static inline void wsrep_commit_empty(THD* thd, bool all) -{ - DBUG_ENTER("wsrep_commit_empty"); - WSREP_DEBUG("wsrep_commit_empty(%llu)", thd->thread_id); - if (wsrep_is_real(thd, all) && - wsrep_thd_is_local(thd) && - thd->wsrep_trx().active() && - thd->wsrep_trx().state() != wsrep::transaction::s_committed) - { - /* @todo CTAS with STATEMENT binlog format and empty result set - seems to be committing empty. Figure out why and try to fix - elsewhere. */ - DBUG_ASSERT(!wsrep_has_changes(thd) || - (thd->lex->sql_command == SQLCOM_CREATE_TABLE && - !thd->is_current_stmt_binlog_format_row())); - bool have_error= wsrep_current_error(thd); - int ret= wsrep_before_rollback(thd, all) || - wsrep_after_rollback(thd, all) || - wsrep_after_statement(thd); - /* The committing transaction was empty but it held some locks and - got BF aborted. As there were no certified changes in the - data, we ignore the deadlock error and rely on error reporting - by storage engine/server. */ - if (!ret && !have_error && wsrep_current_error(thd)) - { - DBUG_ASSERT(wsrep_current_error(thd) == wsrep::e_deadlock_error); - thd->wsrep_cs().reset_error(); - } - if (ret) - { - WSREP_DEBUG("wsrep_commit_empty failed: %d", wsrep_current_error(thd)); - } - } - DBUG_VOID_RETURN; -} - #endif /* WSREP_TRANS_OBSERVER */