diff options
Diffstat (limited to 'sql/sql_table.cc')
-rw-r--r-- | sql/sql_table.cc | 609 |
1 files changed, 541 insertions, 68 deletions
diff --git a/sql/sql_table.cc b/sql/sql_table.cc index a281bdd7bcc..88b29d2b59e 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" @@ -59,6 +60,9 @@ #include "debug.h" // debug_crash_here() #include <algorithm> #include "wsrep_mysqld.h" +#include "rpl_mi.h" +#include "rpl_rli.h" +#include "log.h" #include "sql_debug.h" #ifdef _WIN32 @@ -91,6 +95,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 *); +static bool wait_for_master(THD *thd); +static int process_master_state(THD *thd, int alter_result, + uint64 &start_alter_id, bool if_exists); +static bool +write_bin_log_start_alter_rollback(THD *thd, uint64 &start_alter_id, + bool &partial_alter, bool if_exists); /** @brief Helper function for explain_filename @@ -669,10 +679,12 @@ void build_lower_case_table_filename(char *buff, size_t bufflen, */ uint build_table_shadow_filename(char *buff, size_t bufflen, - ALTER_PARTITION_PARAM_TYPE *lpt) + ALTER_PARTITION_PARAM_TYPE *lpt, + bool backup) { char tmp_name[FN_REFLEN]; - my_snprintf(tmp_name, sizeof (tmp_name), "%s-shadow-%lx-%s", tmp_file_prefix, + my_snprintf(tmp_name, sizeof (tmp_name), "%s-%s-%lx-%s", tmp_file_prefix, + backup ? "backup" : "shadow", (ulong) current_thd->thread_id, lpt->table_name.str); return build_table_filename(buff, bufflen, lpt->db.str, tmp_name, "", FN_IS_TMP); @@ -706,6 +718,11 @@ uint build_table_shadow_filename(char *buff, size_t bufflen, tables since it only handles partitioned data if it exists. */ + +/* + TODO: Partitioning atomic DDL refactoring: WFRM_WRITE_SHADOW + should be merged with create_table_impl(frm_only == true). +*/ bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags) { /* @@ -719,8 +736,11 @@ bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags) char shadow_frm_name[FN_REFLEN+1]; char frm_name[FN_REFLEN+1]; #ifdef WITH_PARTITION_STORAGE_ENGINE + char bak_path[FN_REFLEN+1]; + char bak_frm_name[FN_REFLEN+1]; char *part_syntax_buf; uint syntax_len; + partition_info *part_info= lpt->part_info; #endif DBUG_ENTER("mysql_write_frm"); @@ -779,6 +799,94 @@ bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags) goto end; } } +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (flags & WFRM_WRITE_CONVERTED_TO) + { + THD *thd= lpt->thd; + Alter_table_ctx *alter_ctx= lpt->alter_ctx; + HA_CREATE_INFO *create_info= lpt->create_info; + + LEX_CSTRING new_path= { alter_ctx->get_new_path(), 0 }; + partition_info *work_part_info= thd->work_part_info; + handlerton *db_type= create_info->db_type; + DBUG_ASSERT(lpt->table->part_info); + DBUG_ASSERT(lpt->table->part_info == part_info); + handler *file= ((ha_partition *)(lpt->table->file))->get_child_handlers()[0]; + DBUG_ASSERT(file); + new_path.length= strlen(new_path.str); + strxnmov(frm_name, sizeof(frm_name) - 1, new_path.str, reg_ext, NullS); + create_info->alias= alter_ctx->table_name; + thd->work_part_info= NULL; + create_info->db_type= work_part_info->default_engine_type; + /* NOTE: partitioned temporary tables are not supported. */ + DBUG_ASSERT(!create_info->tmp_table()); + if (ddl_log_create_table(thd, part_info, create_info->db_type, &new_path, + &alter_ctx->new_db, &alter_ctx->new_name, true) || + ERROR_INJECT("create_before_create_frm")) + DBUG_RETURN(TRUE); + + if (mysql_prepare_create_table(thd, create_info, lpt->alter_info, + &lpt->db_options, file, + &lpt->key_info_buffer, &lpt->key_count, + C_ALTER_TABLE, alter_ctx->new_db, + alter_ctx->new_name)) + DBUG_RETURN(TRUE); + + lpt->create_info->table_options= lpt->db_options; + LEX_CUSTRING frm= build_frm_image(thd, alter_ctx->new_name, create_info, + lpt->alter_info->create_list, + lpt->key_count, lpt->key_info_buffer, + file); + if (unlikely(!frm.str)) + DBUG_RETURN(TRUE); + + thd->work_part_info= work_part_info; + create_info->db_type= db_type; + + ERROR_INJECT("alter_partition_after_create_frm"); + + error= writefile(frm_name, alter_ctx->new_db.str, alter_ctx->new_name.str, + create_info->tmp_table(), frm.str, frm.length); + my_free((void *) frm.str); + if (unlikely(error) || ERROR_INJECT("alter_partition_after_write_frm")) + { + mysql_file_delete(key_file_frm, frm_name, MYF(0)); + DBUG_RETURN(TRUE); + } + + DBUG_RETURN(false); + } + if (flags & WFRM_BACKUP_ORIGINAL) + { + build_table_filename(path, sizeof(path) - 1, lpt->db.str, + lpt->table_name.str, "", 0); + strxnmov(frm_name, sizeof(frm_name), path, reg_ext, NullS); + + build_table_shadow_filename(bak_path, sizeof(bak_path) - 1, lpt, true); + strxmov(bak_frm_name, bak_path, reg_ext, NullS); + + DDL_LOG_MEMORY_ENTRY *main_entry= part_info->main_entry; + mysql_mutex_lock(&LOCK_gdl); + if (write_log_replace_frm(lpt, part_info->list->entry_pos, + (const char*) bak_path, + (const char*) path) || + ddl_log_write_execute_entry(part_info->list->entry_pos, + &part_info->execute_entry)) + { + mysql_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(TRUE); + } + mysql_mutex_unlock(&LOCK_gdl); + part_info->main_entry= main_entry; + if (mysql_file_rename(key_file_frm, frm_name, bak_frm_name, MYF(MY_WME))) + DBUG_RETURN(TRUE); + if (lpt->table->file->ha_create_partitioning_metadata(bak_path, path, + CHF_RENAME_FLAG)) + DBUG_RETURN(TRUE); + } +#else /* !WITH_PARTITION_STORAGE_ENGINE */ + DBUG_ASSERT(!(flags & WFRM_BACKUP_ORIGINAL)); +#endif /* !WITH_PARTITION_STORAGE_ENGINE */ if (flags & WFRM_INSTALL_SHADOW) { #ifdef WITH_PARTITION_STORAGE_ENGINE @@ -800,20 +908,25 @@ bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags) completing this we write a new phase to the log entry that will deactivate it. */ - if (mysql_file_delete(key_file_frm, frm_name, MYF(MY_WME)) || + if (!(flags & WFRM_BACKUP_ORIGINAL) && ( + mysql_file_delete(key_file_frm, frm_name, MYF(MY_WME)) #ifdef WITH_PARTITION_STORAGE_ENGINE - lpt->table->file->ha_create_partitioning_metadata(path, shadow_path, + || lpt->table->file->ha_create_partitioning_metadata(path, shadow_path, CHF_DELETE_FLAG) || - ddl_log_increment_phase(part_info->frm_log_entry->entry_pos) || - (ddl_log_sync(), FALSE) || - mysql_file_rename(key_file_frm, - shadow_frm_name, frm_name, MYF(MY_WME)) || - lpt->table->file->ha_create_partitioning_metadata(path, shadow_path, - CHF_RENAME_FLAG)) -#else - mysql_file_rename(key_file_frm, - shadow_frm_name, frm_name, MYF(MY_WME))) + ddl_log_increment_phase(part_info->main_entry->entry_pos) || + (ddl_log_sync(), FALSE) +#endif + )) + { + error= 1; + goto err; + } + if (mysql_file_rename(key_file_frm, shadow_frm_name, frm_name, MYF(MY_WME)) +#ifdef WITH_PARTITION_STORAGE_ENGINE + || lpt->table->file->ha_create_partitioning_metadata(path, shadow_path, + CHF_RENAME_FLAG) #endif + ) { error= 1; goto err; @@ -852,8 +965,8 @@ bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags) err: #ifdef WITH_PARTITION_STORAGE_ENGINE - ddl_log_increment_phase(part_info->frm_log_entry->entry_pos); - part_info->frm_log_entry= NULL; + ddl_log_increment_phase(part_info->main_entry->entry_pos); + part_info->main_entry= NULL; (void) ddl_log_sync(); #endif ; @@ -891,7 +1004,16 @@ int write_bin_log(THD *thd, bool clear_error, int errcode= 0; thd_proc_info(thd, "Writing to binlog"); if (clear_error) + { + if (global_system_variables.log_warnings > 2) + { + uint err_clear= thd->is_error() ? thd->get_stmt_da()->sql_errno() : 0; + if (err_clear) + sql_print_warning("Error code %d of query '%s' is cleared at its " + "binary logging.", err_clear, query); + } thd->clear_error(); + } else errcode= query_error_code(thd, TRUE); error= thd->binlog_query(THD::STMT_QUERY_TYPE, @@ -903,21 +1025,40 @@ int write_bin_log(THD *thd, bool clear_error, } -/* - Write to binary log with optional adding "IF EXISTS" +/** + Write to binary log the query event whose text is taken from thd->query(). - The query is taken from thd->query() + @param thd Thread handle. + @param clear_error Whether to clear out any error from + execution context and binlog event. + @param is_trans Whether the query changed transactional data. + @param add_if_exists True indicates the binary logging of the query + should be done with "if exists" option. + @param commit_alter Whether the query should be binlogged as + Commit Alter. + + @return false on Success + @return true otherwise */ 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(Gtid_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; } @@ -2185,7 +2326,7 @@ void promote_first_timestamp_column(List<Create_field> *column_definitions) static bool key_cmp(const Key_part_spec &a, const Key_part_spec &b) { - return a.length == b.length && + return a.length == b.length && a.asc == b.asc && !lex_string_cmp(system_charset_info, &a.field_name, &b.field_name); } @@ -2765,8 +2906,6 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, */ if (sql_field->stored_in_db()) record_offset+= sql_field->pack_length; - if (sql_field->flags & VERS_SYSTEM_FIELD) - continue; } /* Update virtual fields' offset and give error if All fields are invisible */ @@ -3120,14 +3259,14 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, if (!sql_field || (sql_field->invisible > INVISIBLE_USER && !column->generated)) { - my_error(ER_KEY_COLUMN_DOES_NOT_EXITS, MYF(0), column->field_name.str); + my_error(ER_KEY_COLUMN_DOES_NOT_EXIST, MYF(0), column->field_name.str); DBUG_RETURN(TRUE); } if (sql_field->invisible > INVISIBLE_USER && !(sql_field->flags & VERS_SYSTEM_FIELD) && - !key->invisible && DBUG_EVALUATE_IF("test_invisible_index", 0, 1)) + !key->invisible && !DBUG_IF("test_invisible_index")) { - my_error(ER_KEY_COLUMN_DOES_NOT_EXITS, MYF(0), column->field_name.str); + my_error(ER_KEY_COLUMN_DOES_NOT_EXIST, MYF(0), column->field_name.str); DBUG_RETURN(TRUE); } while ((dup_column= cols2++) != column) @@ -3233,6 +3372,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, key_part_info->fieldnr= field; key_part_info->offset= (uint16) sql_field->offset; key_part_info->key_type=sql_field->pack_flag; + key_part_info->key_part_flag= column->asc ? 0 : HA_REVERSE_SORT; uint key_part_length= sql_field->type_handler()-> calc_key_length(*sql_field); @@ -4201,7 +4341,6 @@ err: @retval -1 table existed but IF NOT EXISTS was used */ -static int create_table_impl(THD *thd, DDL_LOG_STATE *ddl_log_state_create, DDL_LOG_STATE *ddl_log_state_rm, @@ -6066,9 +6205,8 @@ remove_key: if (!part_elem) { push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, - ER_DROP_PARTITION_NON_EXISTENT, - ER_THD(thd, ER_DROP_PARTITION_NON_EXISTENT), - "DROP"); + ER_PARTITION_DOES_NOT_EXIST, + ER_THD(thd, ER_PARTITION_DOES_NOT_EXIST)); names_it.remove(); } } @@ -6258,6 +6396,14 @@ Compare_keys compare_keys_but_name(const KEY *table_key, const KEY *new_key, return Compare_keys::NotEqual; } + /* + Check the descending flag for index field. + */ + if ((new_part->key_part_flag ^ key_part->key_part_flag) & HA_REVERSE_SORT) + { + return Compare_keys::NotEqual; + } + auto compare= table->file->compare_key_parts( *table->field[key_part->fieldnr - 1], new_field, *key_part, *new_part); result= merge(result, compare); @@ -6894,10 +7040,8 @@ static void update_altered_table(const Alter_inplace_info &ha_alter_info, @retval false success */ -bool mysql_compare_tables(TABLE *table, - Alter_info *alter_info, - HA_CREATE_INFO *create_info, - bool *metadata_equal) +bool mysql_compare_tables(TABLE *table, Alter_info *alter_info, + HA_CREATE_INFO *create_info, bool *metadata_equal) { DBUG_ENTER("mysql_compare_tables"); @@ -6922,15 +7066,14 @@ bool mysql_compare_tables(TABLE *table, Alter_info tmp_alter_info(*alter_info, thd->mem_root); uint db_options= 0; /* not used */ KEY *key_info_buffer= NULL; - LEX_CSTRING db= { table->s->db.str, table->s->db.length }; - LEX_CSTRING table_name= { table->s->db.str, table->s->table_name.length }; /* Create the prepared information. */ int create_table_mode= table->s->tmp_table == NO_TMP_TABLE ? C_ORDINARY_CREATE : C_ALTER_TABLE; if (mysql_prepare_create_table(thd, create_info, &tmp_alter_info, &db_options, table->file, &key_info_buffer, - &key_count, create_table_mode, db, table_name)) + &key_count, create_table_mode, + table->s->db, table->s->table_name)) DBUG_RETURN(1); /* Some very basic checks. */ @@ -7025,7 +7168,8 @@ bool mysql_compare_tables(TABLE *table, are equal. Comparing field numbers is sufficient. */ if ((table_part->length != new_part->length) || - (table_part->fieldnr - 1 != new_part->fieldnr)) + (table_part->fieldnr - 1 != new_part->fieldnr) || + ((table_part->key_part_flag ^ new_part->key_part_flag) & HA_REVERSE_SORT)) DBUG_RETURN(false); } } @@ -7210,6 +7354,86 @@ 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. + + @param thd Thread handle. + @param[in/out] + start_alter_id Start Alter identifier or zero, + it is reset to zero. + @param[in/out] + partial_alter When is true at the function enter + that indicates Start Alter phase completed; + it then is reset to false. + @param if_exists True indicates the binary logging of the query + should be done with "if exists" option. + + @return false on Success + @return true otherwise +*/ +static bool +write_bin_log_start_alter_rollback(THD *thd, uint64 &start_alter_id, + bool &partial_alter, bool if_exists) +{ +#if defined(HAVE_REPLICATION) + if (start_alter_id) + { + start_alter_info *info= thd->rgi_slave->sa_info; + Master_info *mi= thd->rgi_slave->rli->mi; + + 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; + } + mysql_mutex_lock(&mi->start_alter_lock); + if (info->direct_commit_alter) + { + DBUG_ASSERT(info->state == start_alter_state::ROLLBACK_ALTER); + + /* + SA may end up in the rollback state through FTWRL that breaks + SA's waiting for a master decision. + Then it completes "officially", and `direct_commit_alter` true status + will affect the future of CA to re-execute the whole query. + */ + info->state= start_alter_state::COMPLETED; + if (info->direct_commit_alter) + mysql_cond_broadcast(&info->start_alter_cond); + mysql_mutex_unlock(&mi->start_alter_lock); + + return true; // not really an error to be handled by caller specifically + } + mysql_mutex_unlock(&mi->start_alter_lock); + /* + 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, if_exists)) + return true; + } + else +#endif + if (partial_alter) // Write only if SA written + { + // Send the rollback message + Write_log_with_flags wlwf(thd, Gtid_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. @@ -7223,9 +7447,17 @@ static bool notify_tabledef_changed(TABLE_LIST *table_list) used during different phases. @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 + @param partial_alter Is set to true to return the fact of the first + "START ALTER" binlogging phase is done. + @param[in/out] + start_alter_id Gtid seq_no of START ALTER or zero otherwise; + it may get changed to return to the caller. + @param if_exists True indicates the binary logging of the query + should be done with "if exists" option. + + @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 @@ -7248,13 +7480,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"); @@ -7334,10 +7569,31 @@ static bool mysql_inplace_alter_table(THD *thd, thd->variables.lock_wait_timeout)) goto cleanup; + DBUG_ASSERT(table->s->tmp_table == NO_TMP_TABLE || start_alter_id == 0); + + if (table->s->tmp_table == NO_TMP_TABLE) + { + if (write_bin_log_start_alter(thd, partial_alter, start_alter_id, + if_exists)) + goto cleanup; + } + else if (start_alter_id) + { + DBUG_ASSERT(thd->rgi_slave); + + my_error(ER_INCONSISTENT_SLAVE_TEMP_TABLE, MYF(0), thd->query(), + table_list->db.str, table_list->table_name.str); + 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); @@ -7417,14 +7673,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 NO_CLEAR_EVENT")); + }); /* 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)) @@ -7533,7 +7798,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"); @@ -7550,7 +7815,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, @@ -7595,6 +7860,8 @@ 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: DBUG_RETURN(true); } @@ -7637,6 +7904,7 @@ void append_drop_column(THD *thd, String *str, Field *field) } +#ifdef WITH_PARTITION_STORAGE_ENGINE static inline void rename_field_in_list(Create_field *field, List<const char> *field_list) { @@ -7649,6 +7917,7 @@ void rename_field_in_list(Create_field *field, List<const char> *field_list) it.replace(field->field_name.str); } } +#endif /** @@ -7802,8 +8071,9 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, thd->calloc(sizeof(void*) * table->s->keys)) == NULL) DBUG_RETURN(1); - create_info->option_list= merge_engine_table_options(table->s->option_list, - create_info->option_list, thd->mem_root); + if (merge_engine_options(table->s->option_list, create_info->option_list, + &create_info->option_list, thd->mem_root)) + DBUG_RETURN(1); /* First collect all fields from table which isn't in drop_list @@ -8374,9 +8644,10 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, key_part_length= 0; // Use whole field } key_part_length /= kfield->charset()->mbmaxlen; - key_parts.push_back(new (thd->mem_root) Key_part_spec(&cfield->field_name, - key_part_length, true), - thd->mem_root); + Key_part_spec *kps= new (thd->mem_root) Key_part_spec(&cfield->field_name, + key_part_length, true); + kps->asc= !(key_part->key_part_flag & HA_REVERSE_SORT); + key_parts.push_back(kps, thd->mem_root); if (!(cfield->invisible == INVISIBLE_SYSTEM && cfield->vers_sys_field())) user_keyparts= true; } @@ -8435,7 +8706,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, key_type= Key::UNIQUE; if (dropped_key_part) { - my_error(ER_KEY_COLUMN_DOES_NOT_EXITS, MYF(0), dropped_key_part); + my_error(ER_KEY_COLUMN_DOES_NOT_EXIST, MYF(0), dropped_key_part); if (long_hash_key) { key_info->algorithm= HA_KEY_ALG_LONG_HASH; @@ -9361,6 +9632,153 @@ 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); + + DBUG_ASSERT(!info->direct_commit_alter || + info->state == start_alter_state::ROLLBACK_ALTER); + + 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. + + @param thd Thread handle + @param start_alter_state ALTER replicaton execution context + @param mi Master_info of the replication source +*/ +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. + + @param thd Thread handle. + @param alter_result Result of execution. + @param[in/out] + start_alter_id Start Alter identifier or zero, + it is reset to zero. + @param if_exists True indicates the binary logging of the query + should be done with "if exists" option. + @retval 1 error + @retval 0 Ok +*/ +static int process_master_state(THD *thd, int alter_result, + uint64 &start_alter_id, bool if_exists) +{ +#ifdef HAVE_REPLICATION + start_alter_info *info= thd->rgi_slave->sa_info; + bool partial_alter= false; + + if (info->state == start_alter_state::INVALID) + { + /* the caller has not yet called SA logging nor wait for master decision */ + if (!write_bin_log_start_alter(thd, partial_alter, start_alter_id, + if_exists)) + wait_for_master(thd); + + DBUG_ASSERT(!partial_alter); + } + + /* 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 & + Gtid_log_event::FL_START_ALTER_E1)) || + !thd->rgi_slave->direct_commit_alter); + return + thd->rgi_slave && + (thd->rgi_slave->gtid_ev_flags_extra & Gtid_log_event::FL_START_ALTER_E1) ? + thd->variables.gtid_seq_no : 0; +} + /** Alter table @@ -9419,6 +9837,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. @@ -9557,6 +9983,7 @@ bool mysql_alter_table(THD *thd, const LEX_CSTRING *new_db, } table= table_list->table; + bool is_reg_table= table->s->tmp_table == NO_TMP_TABLE; #ifdef WITH_WSREP if (WSREP(thd) && @@ -9713,7 +10140,8 @@ bool mysql_alter_table(THD *thd, const LEX_CSTRING *new_db, Table maybe does not exist, but we got an exclusive lock on the name, now we can safely try to find out for sure. */ - if (ha_table_exists(thd, &alter_ctx.new_db, &alter_ctx.new_name)) + if (!(alter_info->partition_flags & ALTER_PARTITION_CONVERT_IN) && + ha_table_exists(thd, &alter_ctx.new_db, &alter_ctx.new_name)) { /* Table will be closed in do_command() */ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alter_ctx.new_alias.str); @@ -9968,6 +10396,8 @@ do_continue:; { DBUG_RETURN(true); } + if (parse_engine_part_options(thd, table)) + DBUG_RETURN(true); } /* If the old table had partitions and we are doing ALTER TABLE ... @@ -10050,10 +10480,8 @@ do_continue:; } // In-place execution of ALTER TABLE for partitioning. - DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info, - create_info, table_list, - &alter_ctx.db, - &alter_ctx.table_name)); + DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info, &alter_ctx, + create_info, table_list)); } #endif @@ -10355,7 +10783,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= @@ -10410,6 +10839,25 @@ 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)) + goto err_new_table_cleanup; + } + else if (start_alter_id) + { + DBUG_ASSERT(thd->rgi_slave); + + my_error(ER_INCONSISTENT_SLAVE_TEMP_TABLE, MYF(0), thd->query(), + table_list->db.str, table_list->table_name.str); + 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 NO_CLEAR_EVENT")); + }); // It's now safe to take the table level lock. if (lock_tables(thd, table_list, alter_ctx.tables_opened, MYSQL_LOCK_USE_MALLOC)) @@ -10522,6 +10970,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 */ @@ -10565,6 +11020,9 @@ do_continue:; binlog_commit(thd, true); } + DBUG_ASSERT(!start_alter_id); // no 2 phase logging for + DBUG_ASSERT(!partial_alter); // temporary table alter + /* We don't replicate alter table statement on temporary tables */ if (!thd->is_current_stmt_binlog_format_row() && table_creation_was_logged && @@ -10803,12 +11261,26 @@ 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 (!is_reg_table) + { + my_error(ER_INCONSISTENT_SLAVE_TEMP_TABLE, MYF(0), thd->query(), + table_list->db.str, table_list->table_name.str); + DBUG_RETURN(true); + } + + if (process_master_state(thd, 0, start_alter_id, if_exists)) + 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; @@ -10904,6 +11376,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: @@ -11065,6 +11541,8 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, if (!(*ptr)->vcol_info) { bitmap_set_bit(from->read_set, def->field->field_index); + if ((*ptr)->check_assignability_from(def->field, ignore)) + goto err; (copy_end++)->set(*ptr,def->field,0); } } @@ -11152,7 +11630,7 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, if (ignore && !alter_ctx->fk_error_if_delete_row) to->file->extra(HA_EXTRA_IGNORE_DUP_KEY); - thd->get_stmt_da()->reset_current_row_for_warning(); + thd->get_stmt_da()->reset_current_row_for_warning(1); restore_record(to, s->default_values); // Create empty record to->reset_default_fields(); @@ -11620,16 +12098,11 @@ bool check_engine(THD *thd, const char *db_name, if (create_info->tmp_table() && ha_check_storage_engine_flag(*new_engine, HTON_TEMPORARY_NOT_SUPPORTED)) { - if (create_info->used_fields & HA_CREATE_USED_ENGINE) - { - my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0), - hton_name(*new_engine)->str, "TEMPORARY"); - *new_engine= 0; - DBUG_RETURN(true); - } - *new_engine= myisam_hton; + my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0), + hton_name(*new_engine)->str, "TEMPORARY"); + *new_engine= 0; + DBUG_RETURN(true); } - lex_string_set(&create_info->new_storage_engine_name, ha_resolve_storage_engine_name(*new_engine)); DBUG_RETURN(false); |