diff options
Diffstat (limited to 'sql/sql_table.cc')
-rw-r--r-- | sql/sql_table.cc | 281 |
1 files changed, 269 insertions, 12 deletions
diff --git a/sql/sql_table.cc b/sql/sql_table.cc index b07efb29bba..d891b739591 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -19,6 +19,7 @@ /* drop and alter of tables */ #include "mariadb.h" +#include "sql_class.h" #include "sql_priv.h" #include "unireg.h" #include "debug_sync.h" @@ -58,6 +59,10 @@ #include "ddl_log.h" #include "debug.h" // debug_crash_here() #include <algorithm> +#include "rpl_mi.h" +#include "rpl_rli.h" +#include "log.h" + #ifdef _WIN32 #include <io.h> @@ -89,6 +94,12 @@ static int mysql_prepare_create_table(THD *, HA_CREATE_INFO *, Alter_info *, static uint blob_length_by_type(enum_field_types type); static bool fix_constraints_names(THD *, List<Virtual_column_info> *, const HA_CREATE_INFO *); +bool write_bin_log_start_alter(THD *thd, bool& partial_alter, + uint64 start_alter_id, bool log_if_exists, + MEM_ROOT *mem); +static bool wait_for_master(THD *thd); +static int process_master_state(THD *thd, int alter_result, + uint64 &start_alter_id); /** @brief Helper function for explain_filename @@ -1011,14 +1022,23 @@ int write_bin_log(THD *thd, bool clear_error, */ int write_bin_log_with_if_exists(THD *thd, bool clear_error, - bool is_trans, bool add_if_exists) + bool is_trans, bool add_if_exists, + bool commit_alter) { int result; ulonglong save_option_bits= thd->variables.option_bits; if (add_if_exists) thd->variables.option_bits|= OPTION_IF_EXISTS; + if (commit_alter) + thd->set_binlog_flags_for_alter(Log_event::FL_COMMIT_ALTER_E1); + result= write_bin_log(thd, clear_error, thd->query(), thd->query_length(), is_trans); + if (commit_alter) + { + thd->set_binlog_flags_for_alter(0); + thd->set_binlog_start_alter_seq_no(0); + } thd->variables.option_bits= save_option_bits; return result; } @@ -7283,6 +7303,51 @@ static bool notify_tabledef_changed(TABLE_LIST *table_list) DBUG_RETURN(0); } +/* + The function is invoked in error branches of ALTER processing. + Write Rollback alter in case of partial_alter is true else + call process_master_state. + + Returns + false on Success + true otherwise +*/ +bool write_bin_log_start_alter_rollback(THD *thd, uint64 &start_alter_id, + bool &partial_alter, bool if_exists) +{ + if (start_alter_id) + { + start_alter_info *info= thd->rgi_slave->sa_info; + if (info->sa_seq_no == 0) + { + /* + Error occurred before SA got to processing incl its binlogging. + So it's a failure to apply and thus no need to wait for master's + completion result. + */ + return true; + } + /* + We have to call wait for master here because in main calculation + we can error out before calling wait for master + (for example if copy_data_between_tables fails) + */ + if (info->state == start_alter_state::REGISTERED) + wait_for_master(thd); + if(process_master_state(thd, 1, start_alter_id)) + return true; + } + else if (partial_alter) // Write only if SA written + { + // Send the rollback message + Write_log_with_flags wlwf(thd, Log_event::FL_ROLLBACK_ALTER_E1); + if(write_bin_log_with_if_exists(thd, false, false, if_exists, false)) + return true; + partial_alter= false; + } + return false; +} + /** Perform in-place alter table. @@ -7297,8 +7362,9 @@ static bool notify_tabledef_changed(TABLE_LIST *table_list) @param target_mdl_request Metadata request/lock on the target table name. @param alter_ctx ALTER TABLE runtime context. - @retval true Error - @retval false Success + @retval >=1 Error{ 1= ROLLBACK recieved from master , 2= error + in alter so no ROLLBACK in binlog } + @retval 0 Success @note If mysql_alter_table does not need to copy the table, it is @@ -7321,13 +7387,16 @@ static bool mysql_inplace_alter_table(THD *thd, MDL_request *target_mdl_request, DDL_LOG_STATE *ddl_log_state, TRIGGER_RENAME_PARAM *trigger_param, - Alter_table_ctx *alter_ctx) + Alter_table_ctx *alter_ctx, + bool &partial_alter, + uint64 &start_alter_id, bool if_exists) { Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN | MYSQL_OPEN_IGNORE_KILLED); handlerton *db_type= table->s->db_type(); Alter_info *alter_info= ha_alter_info->alter_info; bool reopen_tables= false; bool res, commit_succeded_with_error= 0; + const enum_alter_inplace_result inplace_supported= ha_alter_info->inplace_supported; DBUG_ENTER("mysql_inplace_alter_table"); @@ -7407,11 +7476,22 @@ static bool mysql_inplace_alter_table(THD *thd, MDL_SHARED_NO_WRITE, thd->variables.lock_wait_timeout)) goto cleanup; + // If alter_algorithm == Instant. + if ((table->s->tmp_table == NO_TMP_TABLE || table->s->table_creation_was_logged + || start_alter_id) + && alter_info->algorithm(thd) != Alter_info::ALTER_TABLE_ALGORITHM_INSTANT) + if (write_bin_log_start_alter(thd, partial_alter, start_alter_id, + if_exists, &table->s->mem_root)) + goto cleanup; + + DBUG_EXECUTE_IF("start_alter_kill_after_binlog", { + DBUG_SUICIDE(); + }); + // It's now safe to take the table level lock. if (lock_tables(thd, table_list, alter_ctx->tables_opened, 0)) goto cleanup; - DEBUG_SYNC(thd, "alter_table_inplace_after_lock_upgrade"); THD_STAGE_INFO(thd, stage_alter_inplace_prepare); @@ -7490,14 +7570,23 @@ static bool mysql_inplace_alter_table(THD *thd, DEBUG_SYNC(thd, "alter_table_inplace_after_lock_downgrade"); THD_STAGE_INFO(thd, stage_alter_inplace); + DBUG_EXECUTE_IF("start_alter_delay_master", { + debug_sync_set_action(thd, + STRING_WITH_LEN("now wait_for alter_cont")); + }); /* We can abort alter table for any table type */ thd->abort_on_warning= !ha_alter_info->ignore && thd->is_strict_mode(); res= table->file->ha_inplace_alter_table(altered_table, ha_alter_info); thd->abort_on_warning= false; + + if (start_alter_id && wait_for_master(thd)) + goto rollback; + if (res) goto rollback; + DEBUG_SYNC(thd, "alter_table_inplace_before_lock_upgrade"); // Upgrade to EXCLUSIVE before commit. if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME)) @@ -7606,7 +7695,7 @@ static bool mysql_inplace_alter_table(THD *thd, thd->is_error()) { // Since changes were done in-place, we can't revert them. - DBUG_RETURN(true); + goto err; } debug_crash_here("ddl_log_alter_after_rename_frm"); @@ -7623,7 +7712,7 @@ static bool mysql_inplace_alter_table(THD *thd, If the rename fails we will still have a working table with the old name, but with other changes applied. */ - DBUG_RETURN(true); + goto err; } debug_crash_here("ddl_log_alter_before_rename_triggers"); if (Table_triggers_list::change_table_name(thd, trigger_param, @@ -7668,6 +7757,12 @@ static bool mysql_inplace_alter_table(THD *thd, if (thd->locked_tables_list.reopen_tables(thd, false)) thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); } + +err: + if (partial_alter || start_alter_id) + write_bin_log_start_alter_rollback(thd, start_alter_id, partial_alter, + if_exists); + DBUG_RETURN(true); } @@ -9368,6 +9463,128 @@ static bool log_and_ok(THD *thd) return(0); } +/* + Wait for master to send result of Alter table. + Returns + true when Rollback is decided + false otherwise +*/ +static bool wait_for_master(THD *thd) +{ +#ifdef HAVE_REPLICATION + start_alter_info* info= thd->rgi_slave->sa_info; + Master_info *mi= thd->rgi_slave->rli->mi; + + DBUG_ASSERT(info); + DBUG_ASSERT(info->state != start_alter_state::INVALID); + DBUG_ASSERT(mi); + + mysql_mutex_lock(&mi->start_alter_lock); + while (info->state == start_alter_state::REGISTERED) + { + mysql_cond_wait(&info->start_alter_cond, &mi->start_alter_lock); + } + if (info->state == start_alter_state::ROLLBACK_ALTER) + { + /* + SA thread will not give error , We will set the error in info->error + and then RA worker will output the error + We can modify the info->error without taking mutex, because CA worker + will be waiting on ::COMPLETED wait condition + */ + if(thd->is_error()) + { + info->error= thd->get_stmt_da()->sql_errno(); + thd->clear_error(); + thd->reset_killed(); + } + } + mysql_mutex_unlock(&mi->start_alter_lock); + + return info->state == start_alter_state::ROLLBACK_ALTER; +#else + return 0; +#endif +} + +#ifdef HAVE_REPLICATION +/* + In this function, we are going to change info->state to ::COMPLETED. + This means we are messaging CA/RA worker that we have binlogged, so our + here is finished. +*/ +static void alter_committed(THD *thd, start_alter_info* info, Master_info *mi) +{ + start_alter_state tmp= info->state; + mysql_mutex_lock(&mi->start_alter_lock); + info->state= start_alter_state::COMPLETED; + mysql_cond_broadcast(&info->start_alter_cond); + mysql_mutex_unlock(&mi->start_alter_lock); + if (tmp == start_alter_state::ROLLBACK_ALTER) + { + thd->clear_error(); + thd->reset_killed(); + } +} +#endif + +/* + process_master_state:- process the info->state recieved from master + We will comapre master state with alter_result + In the case of ROLLBACK_ALTER alter_result > 0 + In the case of COMMIT_ALTER alter_result == 0 + if the condition is not satisfied we will report error and + Return 1. Make sure wait_for_master is called before calling this function + This function should be called only at the write binlog time of commit/ + rollback alter. We will alter_committed if everything is fine. + + @retval 1 error + @retval 0 Ok +*/ +static int process_master_state(THD *thd, int alter_result, + uint64 &start_alter_id) +{ +#ifdef HAVE_REPLICATION + start_alter_info *info= thd->rgi_slave->sa_info; + + DBUG_ASSERT(info->state > start_alter_state::REGISTERED); + /* this function shouldn't be called twice */ + DBUG_ASSERT(start_alter_id); + + start_alter_id= 0; + if ((info->state == start_alter_state::ROLLBACK_ALTER && alter_result >= 0) + || (info->state == start_alter_state::COMMIT_ALTER && !alter_result)) + { + alter_committed(thd, info, thd->rgi_slave->rli->mi); + return 0; + } + else + { + thd->is_slave_error= 1; + return 1; + } +#else + return 0; +#endif +} + +/* + Returns a (global unique) identifier of START Alter when slave applier + executes mysql_alter_table(). + In non-slave context it is zero. +*/ +static uint64 get_start_alter_id(THD *thd) +{ + DBUG_ASSERT(!(thd->rgi_slave && + (thd->rgi_slave->gtid_ev_flags_extra & + Log_event::FL_START_ALTER_E1)) || + !thd->rgi_slave->direct_commit_alter); + return + thd->rgi_slave && + (thd->rgi_slave->gtid_ev_flags_extra & Log_event::FL_START_ALTER_E1) ? + thd->variables.gtid_seq_no : 0; +} + /** Alter table @@ -9425,6 +9642,14 @@ bool mysql_alter_table(THD *thd, const LEX_CSTRING *new_db, bool partition_changed= false; bool fast_alter_partition= false; #endif + bool partial_alter= false; + /* + start_alter_id is the gtid seq no of the START Alter - the 1st part + of two-phase loggable ALTER. The variable is meaningful only + on slave execution. + */ + uint64 start_alter_id= get_start_alter_id(thd); + /* Create .FRM for new version of table with a temporary name. We don't log the statement, it will be logged later. @@ -10343,7 +10568,8 @@ do_continue:; &ha_alter_info, &target_mdl_request, &ddl_log_state, &trigger_param, - &alter_ctx); + &alter_ctx, partial_alter, + start_alter_id, if_exists); thd->count_cuted_fields= org_count_cuted_fields; inplace_alter_table_committed= ha_alter_info.inplace_alter_table_committed; inplace_alter_table_committed_argument= @@ -10354,7 +10580,6 @@ do_continue:; goto err_cleanup; } cleanup_table_after_inplace_alter_keep_files(&altered_table); - goto end_inplace; } else @@ -10398,6 +10623,15 @@ do_continue:; else thd->close_unused_temporary_table_instances(table_list); + if (table->s->tmp_table == NO_TMP_TABLE) + if (write_bin_log_start_alter(thd, partial_alter, start_alter_id, + if_exists, &table->s->mem_root)) + goto err_new_table_cleanup; + + DBUG_EXECUTE_IF("start_alter_delay_master", { + debug_sync_set_action(thd, + STRING_WITH_LEN("now wait_for alter_cont")); + }); // It's now safe to take the table level lock. if (lock_tables(thd, table_list, alter_ctx.tables_opened, MYSQL_LOCK_USE_MALLOC)) @@ -10510,6 +10744,13 @@ do_continue:; } thd->count_cuted_fields= CHECK_FIELD_IGNORE; + if (start_alter_id) + { + DBUG_ASSERT(thd->slave_thread); + + if (wait_for_master(thd)) + goto err_new_table_cleanup; + } if (table->s->tmp_table != NO_TMP_TABLE) { /* Release lock if this is a transactional temporary table */ @@ -10552,6 +10793,7 @@ do_continue:; thd->variables.option_bits&= ~OPTION_BIN_COMMIT_OFF; binlog_commit(thd, true); } + DBUG_ASSERT(!start_alter_id); /* We don't replicate alter table statement on temporary tables */ if (!thd->is_current_stmt_binlog_format_row() && @@ -10561,7 +10803,8 @@ do_continue:; int tmp_error; thd->binlog_xid= thd->query_id; ddl_log_update_xid(&ddl_log_state, thd->binlog_xid); - tmp_error= write_bin_log_with_if_exists(thd, true, false, log_if_exists); + tmp_error= write_bin_log_with_if_exists(thd, true, false, log_if_exists, + partial_alter); thd->binlog_xid= 0; if (tmp_error) goto err_cleanup; @@ -10791,12 +11034,19 @@ end_inplace: DBUG_ASSERT(!(mysql_bin_log.is_open() && thd->is_current_stmt_binlog_format_row() && (create_info->tmp_table()))); - if (!binlog_as_create_select) + + if(start_alter_id) + { + if (process_master_state(thd, 0, start_alter_id)) + DBUG_RETURN(true); + } + else if (!binlog_as_create_select) { int tmp_error; thd->binlog_xid= thd->query_id; ddl_log_update_xid(&ddl_log_state, thd->binlog_xid); - tmp_error= write_bin_log_with_if_exists(thd, true, false, log_if_exists); + tmp_error= write_bin_log_with_if_exists(thd, true, false, log_if_exists, + partial_alter); thd->binlog_xid= 0; if (tmp_error) goto err_cleanup; @@ -10893,6 +11143,10 @@ err_cleanup: /* Signal to storage engine that ddl log is committed */ (*inplace_alter_table_committed)(inplace_alter_table_committed_argument); } + DEBUG_SYNC(thd, "alter_table_after_temp_table_drop"); + if (partial_alter || start_alter_id) + write_bin_log_start_alter_rollback(thd, start_alter_id, partial_alter, + if_exists); DBUG_RETURN(true); err_with_mdl: @@ -10903,6 +11157,9 @@ err_with_mdl: remove all references to the altered table from the list of locked tables and release the exclusive metadata lock. */ + if (partial_alter || start_alter_id) + write_bin_log_start_alter_rollback(thd, start_alter_id, partial_alter, + if_exists); thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); if (!table_list->table) thd->mdl_context.release_all_locks_for_name(mdl_ticket); |