diff options
Diffstat (limited to 'sql/sql_table.cc')
-rw-r--r-- | sql/sql_table.cc | 752 |
1 files changed, 556 insertions, 196 deletions
diff --git a/sql/sql_table.cc b/sql/sql_table.cc index c2306c53..c46ed3c8 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -55,6 +55,7 @@ #include "sql_audit.h" #include "sql_sequence.h" #include "tztime.h" +#include "rpl_rli.h" #include "sql_insert.h" // binlog_drop_table #include "ddl_log.h" #include "debug.h" // debug_crash_here() @@ -94,6 +95,7 @@ class Enable_wsrep_ctas_guard #endif /* WITH_WSREP */ #include "sql_debug.h" +#include "scope.h" #ifdef _WIN32 #include <io.h> @@ -111,9 +113,11 @@ static bool make_unique_constraint_name(THD *, LEX_CSTRING *, const char *, List<Virtual_column_info> *, uint *); static const char *make_unique_invisible_field_name(THD *, const char *, List<Create_field> *); -static int copy_data_between_tables(THD *, TABLE *,TABLE *, bool, uint, - ORDER *, ha_rows *, ha_rows *, - Alter_info *, Alter_table_ctx *); +static int copy_data_between_tables(THD *, TABLE *,TABLE *, + bool, uint, ORDER *, + ha_rows *, ha_rows *, + Alter_info *, + Alter_table_ctx *, bool, uint64); static int append_system_key_parts(THD *, HA_CREATE_INFO *, Key *); static int mysql_prepare_create_table(THD *, HA_CREATE_INFO *, Alter_info *, uint *, handler *, KEY **, uint *, int); @@ -670,20 +674,11 @@ void build_lower_case_table_filename(char *buff, size_t bufflen, const LEX_CSTRING *table, uint flags) { - char table_name[SAFE_NAME_LEN+1], db_name[SAFE_NAME_LEN+1]; - DBUG_ASSERT(db->length <= SAFE_NAME_LEN && table->length <= SAFE_NAME_LEN); - - memcpy(db_name, db->str, db->length); - db_name[db->length]= 0; - my_casedn_str(files_charset_info, db_name); - - memcpy(table_name, table->str, table->length); - table_name[table->length]= 0; - my_casedn_str(files_charset_info, table_name); - - build_table_filename(buff, bufflen, db_name, table_name, "", - flags & FN_IS_TMP); + build_table_filename(buff, bufflen, + IdentBufferCasedn<SAFE_NAME_LEN>(*db).to_lex_cstring().str, + IdentBufferCasedn<SAFE_NAME_LEN>(*table).to_lex_cstring().str, "", + flags & FN_IS_TMP); } @@ -2135,7 +2130,8 @@ static int sort_keys(KEY *a, KEY *b) return -1; /* Long Unique keys should always be last unique key. - Before this patch they used to change order wrt to partial keys (MDEV-19049) + Before this patch they used to change order wrt to partial keys + (MDEV-19049) */ if (a->algorithm == HA_KEY_ALG_LONG_HASH) return 1; @@ -2230,10 +2226,12 @@ bool check_duplicates_in_interval(const char *set_or_name, Generates an error to the diagnostics area in case of a failure. */ bool Column_definition:: - prepare_charset_for_string(const Column_derived_attributes *dattr) + prepare_charset_for_string(Sql_used *used, + const Charset_collation_map_st &map, + const Column_derived_attributes *dattr) { CHARSET_INFO *tmp= charset_collation_attrs(). - resolved_to_character_set(dattr->charset()); + resolved_to_character_set(used, map, dattr->charset()); if (!tmp) return true; charset= tmp; @@ -2632,7 +2630,7 @@ static Create_field * add_hash_field(THD * thd, List<Create_field> *create_list, cf->invisible= INVISIBLE_FULL; cf->pack_flag|= FIELDFLAG_MAYBE_NULL; cf->vcol_info= new (thd->mem_root) Virtual_column_info(); - cf->vcol_info->stored_in_db= false; + cf->vcol_info->set_vcol_type(VCOL_GENERATED_VIRTUAL); uint num= 1; LEX_CSTRING field_name; field_name.str= (char *)thd->alloc(LONG_HASH_FIELD_NAME_LENGTH); @@ -3745,7 +3743,7 @@ without_overlaps_err: { if (sql_field->vcol_info && sql_field->vcol_info->expr && check_expression(sql_field->vcol_info, &sql_field->field_name, - sql_field->vcol_info->stored_in_db + sql_field->vcol_info->is_stored() ? VCOL_GENERATED_STORED : VCOL_GENERATED_VIRTUAL, alter_info)) DBUG_RETURN(TRUE); @@ -4303,7 +4301,7 @@ handler *mysql_create_frm_image(THD *thd, HA_CREATE_INFO *create_info, { if (key->type == Key::FOREIGN_KEY) { - my_error(ER_FEATURE_NOT_SUPPORTED_WITH_PARTITIONING, MYF(0), + my_error(ER_FEATURE_NOT_SUPPORTED_WITH_PARTITIONING, MYF(0), "FOREIGN KEY"); goto err; } @@ -4435,8 +4433,8 @@ int create_table_impl(THD *thd, If a table exists, it must have been pre-opened. Try looking for one in-use in THD::all_temp_tables list of TABLE_SHAREs. */ - TABLE *tmp_table= thd->find_temporary_table(db.str, table_name.str, - THD::TMP_TABLE_ANY); + TABLE *tmp_table= internal_tmp_table ? NULL : + thd->find_temporary_table(db.str, table_name.str, THD::TMP_TABLE_ANY); if (tmp_table) { @@ -9600,25 +9598,19 @@ static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table, continue; Foreign_key *fk= static_cast<Foreign_key*>(key); - char dbuf[NAME_LEN]; - char tbuf[NAME_LEN]; - const char *ref_db= (fk->ref_db.str ? - fk->ref_db.str : - alter_ctx->new_db.str); - const char *ref_table= fk->ref_table.str; + IdentBuffer<NAME_LEN> dbuf, tbuf; + LEX_CSTRING ref_db= fk->ref_db.str ? fk->ref_db : alter_ctx->new_db; + LEX_CSTRING ref_table= fk->ref_table; MDL_request mdl_request; if (lower_case_table_names) { - strmake_buf(dbuf, ref_db); - my_casedn_str(system_charset_info, dbuf); - strmake_buf(tbuf, ref_table); - my_casedn_str(system_charset_info, tbuf); - ref_db= dbuf; - ref_table= tbuf; + ref_db= dbuf.copy_casedn(ref_db).to_lex_cstring(); + ref_table= tbuf.copy_casedn(ref_table).to_lex_cstring(); } - MDL_REQUEST_INIT(&mdl_request, MDL_key::TABLE, ref_db, ref_table, + MDL_REQUEST_INIT(&mdl_request, MDL_key::TABLE, + ref_db.str, ref_table.str, MDL_SHARED_NO_WRITE, MDL_TRANSACTION); if (thd->mdl_context.acquire_lock(&mdl_request, thd->variables.lock_wait_timeout)) @@ -10065,6 +10057,152 @@ static uint64 get_start_alter_id(THD *thd) } +static +bool online_alter_check_autoinc(const THD *thd, const Alter_info *alter_info, + const TABLE *table) +{ + /* + We can't go online, if all of the following is presented: + * Autoinc is added to existing field + * Disabled NO_AUTO_VALUE_ON_ZERO + * No non-nullable unique key in the old table, that has all the key parts + remaining unchanged. + */ + + // Exit earlier when possible + if (thd->variables.sql_mode & MODE_NO_AUTO_VALUE_ON_ZERO) + return true; + if ((alter_info->flags | ALTER_CHANGE_COLUMN) != alter_info->flags) + return true; + + /* + Find at least one unique index (without NULLs), all columns of which + remain in the table unchanged to presume it's a safe ALTER TABLE. + */ + for (uint k= 0; k < table->s->keys; k++) + { + const KEY &key= table->key_info[k]; + if ((key.flags & HA_NOSAME) == 0 || key.flags & HA_NULL_PART_KEY) + continue; + bool key_parts_good= true; + for (uint kp= 0; kp < key.user_defined_key_parts && key_parts_good; kp++) + { + const Field *f= key.key_part[kp].field; + // tmp_set contains dropped fields after mysql_prepare_alter_table + key_parts_good= !bitmap_is_set(&table->tmp_set, f->field_index); + + if (key_parts_good) + for (const auto &c: alter_info->create_list) + if (c.field == f) + { + key_parts_good= f->is_equal(c); + break; + } + } + if (key_parts_good) + return true; + } + + for (const auto &c: alter_info->create_list) + { + if (c.flags & AUTO_INCREMENT_FLAG) + { + if (c.field && !(c.field->flags & AUTO_INCREMENT_FLAG)) + return false; + break; + } + } + return true; +} + +static +const char *online_alter_check_supported(const THD *thd, + const Alter_info *alter_info, + const TABLE *table, + const TABLE *new_table, bool *online) +{ + DBUG_ASSERT(*online); + + *online= thd->locked_tables_mode != LTM_LOCK_TABLES && !table->s->tmp_table; + if (!*online) + return NULL; + + *online= (new_table->file->ha_table_flags() & HA_NO_ONLINE_ALTER) == 0; + if (!*online) + return new_table->file->engine_name()->str; + + *online= table->s->sequence == NULL; + if (!*online) + return "SEQUENCE"; + + *online= (alter_info->flags & ALTER_DROP_SYSTEM_VERSIONING) == 0; + if (!*online) + return "DROP SYSTEM VERSIONING"; + + *online= !thd->lex->ignore; + if (!*online) + return "ALTER IGNORE TABLE"; + + *online= !table->versioned(VERS_TRX_ID); + if (!*online) + return "BIGINT GENERATED ALWAYS AS ROW_START"; + + List<FOREIGN_KEY_INFO> fk_list; + table->file->get_foreign_key_list(thd, &fk_list); + for (auto &fk: fk_list) + { + if (fk_modifies_child(fk.delete_method) || + fk_modifies_child(fk.update_method)) + { + *online= false; + // Don't fall to a common unsupported case to avoid heavy string ops. + if (alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE) + { + return fk_modifies_child(fk.delete_method) + ? thd->strcat({STRING_WITH_LEN("ON DELETE ")}, + *fk_option_name(fk.delete_method)).str + : thd->strcat({STRING_WITH_LEN("ON UPDATE ")}, + *fk_option_name(fk.update_method)).str; + } + return NULL; + } + } + + for (auto &c: alter_info->create_list) + { + *online= c.field || !(c.flags & AUTO_INCREMENT_FLAG); + if (!*online) + return "ADD COLUMN ... AUTO_INCREMENT"; + + auto *def= c.default_value; + *online= !(def && def->flags & VCOL_NEXTVAL + // either it's a new field, or a NULL -> NOT NULL change + && (!c.field || (!(c.field->flags & NOT_NULL_FLAG) + && (c.flags & NOT_NULL_FLAG)))); + if (!*online) + { + if (alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_NONE) + return NULL; // Avoid heavy string op + const char *fmt= ER_THD(thd, ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED); + + LEX_CSTRING dflt{STRING_WITH_LEN("DEFAULT")}; + LEX_CSTRING nxvl{STRING_WITH_LEN("NEXTVAL()")}; + size_t len= strlen(fmt) + nxvl.length + c.field_name.length + dflt.length; + char *resp= (char*)thd->alloc(len); + // expression %s cannot be used in the %s clause of %`s + my_snprintf(resp, len, fmt, nxvl.str, dflt.str, c.field_name.str); + return resp; + } + } + + *online= online_alter_check_autoinc(thd, alter_info, table); + if (!*online) + return "CHANGE COLUMN ... AUTO_INCREMENT"; + + return NULL; +} + + /** Alter table @@ -10152,6 +10290,11 @@ bool mysql_alter_table(THD *thd, const LEX_CSTRING *new_db, MDL_request target_mdl_request; MDL_ticket *mdl_ticket= 0; Alter_table_prelocking_strategy alter_prelocking_strategy; +#ifdef HAVE_REPLICATION + bool online= order == NULL && !opt_bootstrap; +#else + bool online= false; +#endif TRIGGER_RENAME_PARAM trigger_param; /* @@ -10235,6 +10378,24 @@ bool mysql_alter_table(THD *thd, const LEX_CSTRING *new_db, */ table_list->required_type= TABLE_TYPE_NORMAL; + if ((alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_DEFAULT + && (thd->variables.old_behavior & OLD_MODE_LOCK_ALTER_TABLE_COPY)) + || alter_info->requested_lock > Alter_info::ALTER_TABLE_LOCK_NONE + || thd->lex->sql_command == SQLCOM_OPTIMIZE + || alter_info->algorithm(thd) > Alter_info::ALTER_TABLE_ALGORITHM_COPY) + online= false; + + if (online) + { + table_list->lock_type= TL_READ; + } + + enum_tx_isolation iso_level_initial= thd->tx_isolation; + SCOPE_EXIT([thd, iso_level_initial](){ + thd->tx_isolation= iso_level_initial; + }); + thd->tx_isolation= ISO_REPEATABLE_READ; + DEBUG_SYNC(thd, "alter_table_before_open_tables"); thd->open_options|= HA_OPEN_FOR_ALTER; @@ -10265,7 +10426,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 this ALTER TABLE is actually SEQUENCE we need to check @@ -10334,8 +10495,7 @@ bool mysql_alter_table(THD *thd, const LEX_CSTRING *new_db, a new one if needed. */ table->s->tdc->flushed= 1; // Force close of all instances - if (thd->mdl_context.upgrade_shared_lock(mdl_ticket, - MDL_EXCLUSIVE, + if (thd->mdl_context.upgrade_shared_lock(mdl_ticket, MDL_EXCLUSIVE, thd->variables.lock_wait_timeout)) DBUG_RETURN(1); quick_rm_table(thd, table->file->ht, &table_list->db, @@ -10344,8 +10504,7 @@ bool mysql_alter_table(THD *thd, const LEX_CSTRING *new_db, goto end_inplace; } if (!if_exists && - (table->file->partition_ht()->flags & - HTON_TABLE_MAY_NOT_EXIST_ON_SLAVE)) + (table->file->partition_ht()->flags & HTON_TABLE_MAY_NOT_EXIST_ON_SLAVE)) { /* Table is a shared table that may not exist on the slave. @@ -10485,7 +10644,7 @@ bool mysql_alter_table(THD *thd, const LEX_CSTRING *new_db, till this point for the alter operation. */ if ((alter_info->flags & ALTER_ADD_FOREIGN_KEY) && - check_fk_parent_table_access(thd, create_info, alter_info, new_db->str)) + check_fk_parent_table_access(thd, create_info, alter_info, *new_db)) DBUG_RETURN(true); /* @@ -10784,31 +10943,25 @@ do_continue:; #endif /* WITH_WSREP */ /* - Use copy algorithm if: - - old_alter_table system variable is set without in-place requested using - the ALGORITHM clause. - - Or if in-place is impossible for given operation. + We can use only copy algorithm if one of the following is true: + - In-place is impossible for given operation. - Changes to partitioning which were not handled by fast_alter_part_table() needs to be handled using table copying algorithm unless the engine supports auto-partitioning as such engines can do some changes using in-place API. */ - if ((thd->variables.alter_algorithm == Alter_info::ALTER_TABLE_ALGORITHM_COPY && - alter_info->algorithm(thd) != - Alter_info::ALTER_TABLE_ALGORITHM_INPLACE) - || is_inplace_alter_impossible(table, create_info, alter_info) + if (is_inplace_alter_impossible(table, create_info, alter_info) || IF_PARTITIONING((partition_changed && - !(old_db_type->partition_flags() & HA_USE_AUTO_PARTITION)), 0)) + !(old_db_type->partition_flags() & HA_USE_AUTO_PARTITION)), 0)) { - if (alter_info->algorithm(thd) == - Alter_info::ALTER_TABLE_ALGORITHM_INPLACE) + if (alter_info->algorithm_is_nocopy(thd)) { my_error(ER_ALTER_OPERATION_NOT_SUPPORTED, MYF(0), - "ALGORITHM=INPLACE", "ALGORITHM=COPY"); + alter_info->algorithm_clause(thd), "ALGORITHM=COPY"); DBUG_RETURN(true); } - alter_info->set_requested_algorithm( - Alter_info::ALTER_TABLE_ALGORITHM_COPY); + + alter_info->set_requested_algorithm(Alter_info::ALTER_TABLE_ALGORITHM_COPY); } /* @@ -11059,7 +11212,7 @@ do_continue:; } if (alter_info->supports_algorithm(thd, &ha_alter_info) || - alter_info->supports_lock(thd, &ha_alter_info)) + alter_info->supports_lock(thd, online, &ha_alter_info)) { cleanup_table_after_inplace_alter(&altered_table); goto err_new_table_cleanup; @@ -11117,16 +11270,6 @@ do_continue:; if (!table->s->tmp_table) { - // COPY algorithm doesn't work with concurrent writes. - if (alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE) - { - my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0), - "LOCK=NONE", - ER_THD(thd, ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY), - "LOCK=SHARED"); - goto err_new_table_cleanup; - } - // If EXCLUSIVE lock is requested, upgrade already. if (alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE && wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) @@ -11184,11 +11327,9 @@ do_continue:; DEBUG_SYNC(thd, "alter_table_intermediate_table_created"); /* Open the table since we need to copy the data. */ - new_table= thd->create_and_open_tmp_table(&frm, - alter_ctx.get_tmp_path(), + new_table= thd->create_and_open_tmp_table(&frm, alter_ctx.get_tmp_path(), alter_ctx.new_db.str, - alter_ctx.new_name.str, - true); + alter_ctx.new_name.str, true); if (!new_table) goto err_new_table_cleanup; @@ -11198,6 +11339,21 @@ do_continue:; thd->session_tracker.state_change.mark_as_changed(thd); } + if (online) + { + const char *reason= online_alter_check_supported(thd, alter_info, table, + new_table, + &online); + if (reason && + alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE) + { + DBUG_ASSERT(!online); + my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0), + "LOCK=NONE", reason, "LOCK=SHARED"); + goto err_new_table_cleanup; + } + } + /* Note: In case of MERGE table, we do not attach children. We do not copy data for MERGE tables. Only the children have data. @@ -11265,11 +11421,22 @@ do_continue:; binlog_as_create_select= 1; DBUG_ASSERT(new_table->file->row_logging); new_table->mark_columns_needed_for_insert(); - thd->binlog_write_table_map(new_table, 1); + mysql_bin_log.write_table_map(thd, new_table, 1); } - if (copy_data_between_tables(thd, table, new_table, ignore, order_num, - order, &copied, &deleted, alter_info, - &alter_ctx)) + + /* + if ORDER BY: sorting + always: copying, building indexes. + if online: reading up the binlog (second binlog is being written) + reading up the second binlog under exclusive lock + */ + thd_progress_init(thd, MY_TEST(order) + 2 + 2 * MY_TEST(online)); + + if (copy_data_between_tables(thd, table, new_table, + ignore, + order_num, order, &copied, &deleted, + alter_info, + &alter_ctx, online, start_alter_id)) goto err_new_table_cleanup; } else @@ -11433,6 +11600,8 @@ do_continue:; NULL); table_list->table= table= NULL; /* Safety */ + thd_progress_end(thd); + DBUG_PRINT("info", ("is_table_renamed: %d engine_changed: %d", alter_ctx.is_table_renamed(), engine_changed)); @@ -11580,7 +11749,7 @@ end_inplace: thd->is_current_stmt_binlog_format_row() && (create_info->tmp_table()))); - if(start_alter_id) + if (start_alter_id) { if (!is_reg_table) { @@ -11662,6 +11831,8 @@ err_new_table_cleanup: DBUG_PRINT("error", ("err_new_table_cleanup")); thd->variables.option_bits&= ~OPTION_BIN_COMMIT_OFF; + thd_progress_end(thd); + /* No default value was provided for a DATE/DATETIME field, the current sql_mode doesn't allow the '0000-00-00' value and @@ -11766,12 +11937,74 @@ bool mysql_trans_commit_alter_copy_data(THD *thd) DBUG_RETURN(error); } +#ifdef HAVE_REPLICATION +/* + locking ALTER TABLE doesn't issue ER_NO_DEFAULT_FOR_FIELD, so online + ALTER shouldn't either +*/ +class Has_default_error_handler : public Internal_error_handler +{ +public: + bool handle_condition(THD *, uint sql_errno, const char *, + Sql_condition::enum_warning_level *, + const char *, Sql_condition **) + { + return sql_errno == ER_NO_DEFAULT_FOR_FIELD; + } +}; + + +static int online_alter_read_from_binlog(THD *thd, rpl_group_info *rgi, + Cache_flip_event_log *log, + ha_rows *found_rows) +{ + int error= 0; + + IO_CACHE *log_file= log->flip(); + + thd_progress_report(thd, 1, MY_MAX(1, my_b_write_tell(log_file))); + + Has_default_error_handler hdeh; + thd->push_internal_handler(&hdeh); + do + { + const auto *descr_event= rgi->rli->relay_log.description_event_for_exec; + auto *ev= Log_event::read_log_event(log_file, descr_event, 0, 1, ~0UL); + error= log_file->error; + if (unlikely(!ev)) + { + if (error) + my_error(ER_IO_READ_ERROR,MYF(0), (ulong)EIO, strerror(EIO), ""); + break; + } + DBUG_ASSERT(!error); + + ev->thd= thd; + error= ev->apply_event(rgi); + + error= error || thd->is_error(); + if(likely(!error)) + ev->online_alter_update_row_count(found_rows); + + if (ev != rgi->rli->relay_log.description_event_for_exec) + delete ev; + thd_progress_report(thd, my_b_tell(log_file), thd->progress.max_counter); + DEBUG_SYNC(thd, "alter_table_online_progress"); + } while(!error); + thd->pop_internal_handler(); + + return MY_TEST(error); +} +#endif static int -copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, bool ignore, - uint order_num, ORDER *order, ha_rows *copied, - ha_rows *deleted, Alter_info *alter_info, - Alter_table_ctx *alter_ctx) +copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, + bool ignore, + uint order_num, ORDER *order, + ha_rows *copied, ha_rows *deleted, + Alter_info *alter_info, + Alter_table_ctx *alter_ctx, bool online, + uint64 start_alter_id) { int error= 1; Copy_field *copy= NULL, *copy_end; @@ -11796,9 +12029,6 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, bool ignore, MYSQL_TIME query_start; DBUG_ENTER("copy_data_between_tables"); - /* Two or 3 stages; Sorting, copying data and update indexes */ - thd_progress_init(thd, 2 + MY_TEST(order)); - if (!(copy= new (thd->mem_root) Copy_field[to->s->fields])) DBUG_RETURN(-1); @@ -11840,6 +12070,7 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, bool ignore, Create_field *def; copy_end=copy; to->s->default_fields= 0; + error= 1; for (Field **ptr=to->field ; *ptr ; ptr++) { def=it++; @@ -11958,145 +12189,171 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, bool ignore, if (!ignore) /* for now, InnoDB needs the undo log for ALTER IGNORE */ to->file->extra(HA_EXTRA_BEGIN_ALTER_COPY); - while (likely(!(error= info.read_record()))) + if (!(error= info.read_record())) { - if (unlikely(thd->killed)) +#ifdef HAVE_REPLICATION + if (online) { - thd->send_kill_message(); - error= 1; - break; - } + DBUG_ASSERT(from->s->online_alter_binlog == NULL); + from->s->online_alter_binlog= new Cache_flip_event_log(); + if (!from->s->online_alter_binlog) + goto err; + from->s->online_alter_binlog->init_pthread_objects(); + error= from->s->online_alter_binlog->open(WRITE_CACHE); - if (make_unversioned) - { - if (!from_row_end->is_max()) - continue; // Drop history rows. - } + if (error) + { + from->s->online_alter_binlog->release(); + from->s->online_alter_binlog= NULL; + goto err; + } - if (unlikely(++thd->progress.counter >= time_to_report_progress)) - { - time_to_report_progress+= MY_HOW_OFTEN_TO_WRITE/10; - thd_progress_report(thd, thd->progress.counter, - thd->progress.max_counter); + from->mdl_ticket->downgrade_lock(MDL_SHARED_UPGRADABLE); + DEBUG_SYNC(thd, "alter_table_online_downgraded"); } +#else + DBUG_ASSERT(!online); +#endif // HAVE_REPLICATION - /* Return error if source table isn't empty. */ - if (unlikely(alter_ctx->error_if_not_empty)) + do { - error= 1; - break; - } + if (unlikely(thd->killed)) + { + thd->send_kill_message(); + error= 1; + break; + } - for (Copy_field *copy_ptr=copy ; copy_ptr != copy_end ; copy_ptr++) - { - copy_ptr->do_copy(copy_ptr); - } + if (make_unversioned) + { + if (!from_row_end->is_max()) + continue; // Drop history rows. + } - if (make_versioned) - { - to_row_start->set_notnull(); - to_row_start->store_time(&query_start); - to_row_end->set_max(); - } + if (unlikely(++thd->progress.counter >= time_to_report_progress)) + { + time_to_report_progress+= MY_HOW_OFTEN_TO_WRITE/10; + thd_progress_report(thd, thd->progress.counter, + thd->progress.max_counter); + } - prev_insert_id= to->file->next_insert_id; - if (to->default_field) - to->update_default_fields(ignore); - if (to->vfield) - to->update_virtual_fields(to->file, VCOL_UPDATE_FOR_WRITE); + /* Return error if source table isn't empty. */ + if (unlikely(alter_ctx->error_if_not_empty)) + { + error= 1; + break; + } - /* This will set thd->is_error() if fatal failure */ - if (to->verify_constraints(ignore) == VIEW_CHECK_SKIP) - continue; - if (unlikely(thd->is_error())) - { - error= 1; - break; - } - if (keep_versioned && to->versioned(VERS_TRX_ID)) - to->vers_write= false; + for (Copy_field *copy_ptr=copy ; copy_ptr != copy_end ; copy_ptr++) + { + copy_ptr->do_copy(copy_ptr); + } - if (to->next_number_field) - { - if (auto_increment_field_copied) - to->auto_increment_field_not_null= TRUE; - else - to->next_number_field->reset(); - } - error= to->file->ha_write_row(to->record[0]); - to->auto_increment_field_not_null= FALSE; - if (unlikely(error)) - { - if (to->file->is_fatal_error(error, HA_CHECK_DUP)) + if (make_versioned) + { + to_row_start->set_notnull(); + to_row_start->store_time(&query_start); + to_row_end->set_max(); + } + + prev_insert_id= to->file->next_insert_id; + if (to->default_field) + to->update_default_fields(ignore); + if (to->vfield) + to->update_virtual_fields(to->file, VCOL_UPDATE_FOR_WRITE); + + /* This will set thd->is_error() if fatal failure */ + if (to->verify_constraints(ignore) == VIEW_CHECK_SKIP) + continue; + if (unlikely(thd->is_error())) { - /* Not a duplicate key error. */ - to->file->print_error(error, MYF(0)); error= 1; - break; + break; } - else + if (keep_versioned && to->versioned(VERS_TRX_ID)) + to->vers_write= false; + + if (to->next_number_field) + { + if (auto_increment_field_copied) + to->auto_increment_field_not_null= TRUE; + else + to->next_number_field->reset(); + } + error= to->file->ha_write_row(to->record[0]); + to->auto_increment_field_not_null= FALSE; + if (unlikely(error)) { - /* Duplicate key error. */ - if (unlikely(alter_ctx->fk_error_if_delete_row)) + if (to->file->is_fatal_error(error, HA_CHECK_DUP)) { - /* - We are trying to omit a row from the table which serves as parent - in a foreign key. This might have broken referential integrity so - emit an error. Note that we can't ignore this error even if we are - executing ALTER IGNORE TABLE. IGNORE allows to skip rows, but - doesn't allow to break unique or foreign key constraints, - */ - my_error(ER_FK_CANNOT_DELETE_PARENT, MYF(0), - alter_ctx->fk_error_id, - alter_ctx->fk_error_table); + /* Not a duplicate key error. */ + to->file->print_error(error, MYF(0)); + error= 1; break; } - - if (ignore) - { - /* This ALTER IGNORE TABLE. Simply skip row and continue. */ - to->file->restore_auto_increment(prev_insert_id); - delete_count++; - } else { - /* Ordinary ALTER TABLE. Report duplicate key error. */ - uint key_nr= to->file->get_dup_key(error); - if ((int) key_nr >= 0) + /* Duplicate key error. */ + if (unlikely(alter_ctx->fk_error_if_delete_row)) + { + /* + We are trying to omit a row from the table which serves as parent + in a foreign key. This might have broken referential integrity so + emit an error. Note that we can't ignore this error even if we are + executing ALTER IGNORE TABLE. IGNORE allows to skip rows, but + doesn't allow to break unique or foreign key constraints, + */ + my_error(ER_FK_CANNOT_DELETE_PARENT, MYF(0), + alter_ctx->fk_error_id, + alter_ctx->fk_error_table); + break; + } + + if (ignore) { - const char *err_msg= ER_THD(thd, ER_DUP_ENTRY_WITH_KEY_NAME); - if (key_nr == 0 && to->s->keys > 0 && - (to->key_info[0].key_part[0].field->flags & - AUTO_INCREMENT_FLAG)) - err_msg= ER_THD(thd, ER_DUP_ENTRY_AUTOINCREMENT_CASE); - print_keydup_error(to, - key_nr >= to->s->keys ? NULL : - &to->key_info[key_nr], - err_msg, MYF(0)); + /* This ALTER IGNORE TABLE. Simply skip row and continue. */ + to->file->restore_auto_increment(prev_insert_id); + delete_count++; } else - to->file->print_error(error, MYF(0)); - break; + { + /* Ordinary ALTER TABLE. Report duplicate key error. */ + uint key_nr= to->file->get_dup_key(error); + if ((int) key_nr >= 0) + { + const char *err_msg= ER_THD(thd, ER_DUP_ENTRY_WITH_KEY_NAME); + if (key_nr == 0 && to->s->keys > 0 && + (to->key_info[0].key_part[0].field->flags & + AUTO_INCREMENT_FLAG)) + err_msg= ER_THD(thd, ER_DUP_ENTRY_AUTOINCREMENT_CASE); + print_keydup_error(to, + key_nr >= to->s->keys ? NULL : + &to->key_info[key_nr], + err_msg, MYF(0)); + } + else + to->file->print_error(error, MYF(0)); + break; + } } } - } - else - { - DEBUG_SYNC(thd, "copy_data_between_tables_before"); - found_count++; - mysql_stage_set_work_completed(thd->m_stage_progress_psi, found_count); - } - thd->get_stmt_da()->inc_current_row_for_warning(); + else + { + DEBUG_SYNC(thd, "copy_data_between_tables_before"); + found_count++; + mysql_stage_set_work_completed(thd->m_stage_progress_psi, found_count); + } + thd->get_stmt_da()->inc_current_row_for_warning(); + } while (!(error= info.read_record())); } + else + online= false; + + DEBUG_SYNC(thd, "alter_table_copy_end"); THD_STAGE_INFO(thd, stage_enabling_keys); thd_progress_next_stage(thd); - if (error > 0 && !from->s->tmp_table) - { - /* We are going to drop the temporary table */ - to->file->extra(HA_EXTRA_PREPARE_FOR_DROP); - } if (bulk_insert_started && to->file->ha_end_bulk_insert() && error <= 0) { /* Give error, if not already given */ @@ -12104,6 +12361,7 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, bool ignore, to->file->print_error(my_errno,MYF(0)); error= 1; } + bulk_insert_started= 0; if (!ignore) to->file->extra(HA_EXTRA_END_ALTER_COPY); @@ -12111,18 +12369,119 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, bool ignore, cleanup_done= 1; to->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); +#ifdef HAVE_REPLICATION + if (online && error < 0) + { + MEM_UNDEFINED(from->record[0], from->s->rec_buff_length * 2); + MEM_UNDEFINED(to->record[0], to->s->rec_buff_length * 2); + thd_progress_next_stage(thd); + enum_sql_command saved_sql_command= thd->lex->sql_command; + Table_map_log_event table_event(thd, from, from->s->table_map_id, + from->file->has_transactions()); + Relay_log_info rli(false); + rpl_group_info rgi(&rli); + RPL_TABLE_LIST rpl_table(to, TL_WRITE, from, table_event.get_table_def(), + copy, copy_end); + DBUG_ASSERT(to->pos_in_table_list == NULL); + to->pos_in_table_list= &rpl_table; + rgi.thd= thd; + rgi.tables_to_lock= &rpl_table; + + rgi.m_table_map.set_table(from->s->table_map_id, to); + + Cache_flip_event_log *binlog= from->s->online_alter_binlog; + DBUG_ASSERT(binlog->is_open()); + + rli.relay_log.description_event_for_exec= + new Format_description_log_event(4); + + // We'll be filling from->record[0] from row events + bitmap_set_all(from->write_set); + // We restore bitmaps, because update event is going to mess up with them. + to->default_column_bitmaps(); + + end_read_record(&info); + init_read_record_done= false; + mysql_unlock_tables(thd, thd->lock); + thd->lock= NULL; + + error= online_alter_read_from_binlog(thd, &rgi, binlog, &found_count); + if (start_alter_id) + { + DBUG_ASSERT(thd->slave_thread); + + int rpl_error= wait_for_master(thd); + if (rpl_error) + error= 1; + } + + DEBUG_SYNC(thd, "alter_table_online_before_lock"); + + int lock_error= + thd->mdl_context.upgrade_shared_lock(from->mdl_ticket, MDL_SHARED_NO_WRITE, + (double)thd->variables.lock_wait_timeout); + if (!error) + error= lock_error; + + if (!error) + { + thd_progress_next_stage(thd); + error= online_alter_read_from_binlog(thd, &rgi, binlog, &found_count); + } + + /* + We'll do it earlier than usually in mysql_alter_table to handle the + online-related errors more comfortably. + */ + lock_error= thd->mdl_context.upgrade_shared_lock(from->mdl_ticket, + MDL_EXCLUSIVE, + (double)thd->variables.lock_wait_timeout); + if (!error) + error= lock_error; + + to->pos_in_table_list= NULL; // Safety + DBUG_ASSERT(thd->lex->sql_command == saved_sql_command); + thd->lex->sql_command= saved_sql_command; // Just in case + } +#endif + + if (error > 0 && !from->s->tmp_table) + { + /* We are going to drop the temporary table */ + to->file->extra(HA_EXTRA_PREPARE_FOR_DROP); + } + DEBUG_SYNC(thd, "copy_data_between_tables_before_reset_backup_lock"); if (backup_reset_alter_copy_lock(thd)) error= 1; if (unlikely(mysql_trans_commit_alter_copy_data(thd))) error= 1; + + if (unlikely(error) && online) + { + /* + We can't free the resources properly now, as we can still be in + non-exclusive state. So this s->online_alter_binlog will be used + until all transactions will release it. + Once the transaction commits, it can release online_alter_binlog + by decreasing ref_count. + + online_alter_binlog->ref_count can be reached 0 only once. + Proof: + If share exists, we'll always have ref_count >= 1. + Once it reaches destroy(), nobody can acquire it again, + therefore, only release() is possible at this moment. + + Also, this will release the binlog. + */ + from->s->tdc->flush_unused(1); + } err: if (bulk_insert_started) (void) to->file->ha_end_bulk_insert(); -/* Free resources */ if (init_read_record_done) end_read_record(&info); delete [] copy; @@ -12147,7 +12506,6 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, bool ignore, if (error < 0 && !from->s->tmp_table && to->file->extra(HA_EXTRA_PREPARE_FOR_RENAME)) error= 1; - thd_progress_end(thd); DBUG_RETURN(error > 0 ? -1 : 0); } @@ -12815,7 +13173,8 @@ bool HA_CREATE_INFO:: thd->stmt_arena->is_stmt_execute() || thd->stmt_arena->state == Query_arena::STMT_INITIALIZED_FOR_SP); if (!(default_table_charset= - default_cscl.resolved_to_context(ctx))) + default_cscl.resolved_to_context(thd, + thd->variables.character_set_collations, ctx))) return true; } @@ -12828,7 +13187,8 @@ bool HA_CREATE_INFO:: thd->stmt_arena->is_stmt_execute() || thd->stmt_arena->state == Query_arena::STMT_INITIALIZED_FOR_SP); if (!(alter_table_convert_to_charset= - convert_cscl.resolved_to_context(ctx))) + convert_cscl.resolved_to_context(thd, + thd->variables.character_set_collations, ctx))) return true; } return false; |