diff options
Diffstat (limited to 'storage/innobase/handler/handler0alter.cc')
-rw-r--r-- | storage/innobase/handler/handler0alter.cc | 1431 |
1 files changed, 1026 insertions, 405 deletions
diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index 0f0534853a8..5519d8304b9 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -43,10 +43,14 @@ Smart ALTER TABLE #include "rem0types.h" #include "row0log.h" #include "row0merge.h" +#include "row0ins.h" +#include "row0row.h" +#include "row0upd.h" #include "trx0trx.h" #include "trx0roll.h" #include "handler0alter.h" #include "srv0mon.h" +#include "srv0srv.h" #include "fts0priv.h" #include "fts0plugin.h" #include "pars0pars.h" @@ -73,7 +77,7 @@ static const Alter_inplace_info::HA_ALTER_FLAGS INNOBASE_ALTER_REBUILD = Alter_inplace_info::ADD_PK_INDEX | Alter_inplace_info::DROP_PK_INDEX | Alter_inplace_info::CHANGE_CREATE_OPTION - /* CHANGE_CREATE_OPTION needs to check innobase_need_rebuild() */ + /* CHANGE_CREATE_OPTION needs to check create_option_need_rebuild() */ | Alter_inplace_info::ALTER_COLUMN_NULLABLE | Alter_inplace_info::ALTER_COLUMN_NOT_NULLABLE | Alter_inplace_info::ALTER_STORED_COLUMN_ORDER @@ -83,6 +87,9 @@ static const Alter_inplace_info::HA_ALTER_FLAGS INNOBASE_ALTER_REBUILD /* | Alter_inplace_info::ALTER_STORED_COLUMN_TYPE */ + | Alter_inplace_info::ALTER_COLUMN_UNVERSIONED + | Alter_inplace_info::ALTER_ADD_SYSTEM_VERSIONING + | Alter_inplace_info::ALTER_DROP_SYSTEM_VERSIONING ; /** Operations that require changes to data */ @@ -157,6 +164,8 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx dict_table_t* old_table; /** table where the indexes are being created or dropped */ dict_table_t* new_table; + /** table definition for instant ADD COLUMN */ + dict_table_t* instant_table; /** mapping of old column numbers to new ones, or NULL */ const ulint* col_map; /** new column names, or NULL if nothing was renamed */ @@ -183,6 +192,12 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx const char** drop_vcol_name; /** ALTER TABLE stage progress recorder */ ut_stage_alter_t* m_stage; + /** original number of user columns in the table */ + const unsigned old_n_cols; + /** original columns of the table */ + dict_col_t* const old_cols; + /** original column names of the table */ + const char* const old_col_names; ha_innobase_inplace_ctx(row_prebuilt_t*& prebuilt_arg, dict_index_t** drop_arg, @@ -210,7 +225,7 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx add_fk (add_fk_arg), num_to_add_fk (num_to_add_fk_arg), online (online_arg), heap (heap_arg), trx (0), old_table (prebuilt_arg->table), - new_table (new_table_arg), + new_table (new_table_arg), instant_table (0), col_map (0), col_names (col_names_arg), add_autoinc (add_autoinc_arg), add_cols (0), @@ -224,8 +239,12 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx num_to_drop_vcol(0), drop_vcol(0), drop_vcol_name(0), - m_stage(NULL) + m_stage(NULL), + old_n_cols(prebuilt_arg->table->n_cols), + old_cols(prebuilt_arg->table->cols), + old_col_names(prebuilt_arg->table->col_names) { + ut_ad(old_n_cols >= DATA_N_SYS_COLS); #ifdef UNIV_DEBUG for (ulint i = 0; i < num_to_add_index; i++) { ut_ad(!add_index[i]->to_be_dropped); @@ -242,6 +261,15 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx ~ha_innobase_inplace_ctx() { UT_DELETE(m_stage); + if (instant_table) { + while (dict_index_t* index + = UT_LIST_GET_LAST(instant_table->indexes)) { + UT_LIST_REMOVE(instant_table->indexes, index); + rw_lock_free(&index->lock); + dict_mem_index_free(index); + } + dict_mem_table_free(instant_table); + } mem_heap_free(heap); } @@ -249,6 +277,36 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx @return whether the table will be rebuilt */ bool need_rebuild () const { return(old_table != new_table); } + /** Convert table-rebuilding ALTER to instant ALTER. */ + void prepare_instant() + { + DBUG_ASSERT(need_rebuild()); + DBUG_ASSERT(!is_instant()); + DBUG_ASSERT(old_table->n_cols == old_table->n_def); + DBUG_ASSERT(new_table->n_cols == new_table->n_def); + DBUG_ASSERT(old_table->n_cols == old_n_cols); + DBUG_ASSERT(new_table->n_cols > old_table->n_cols); + instant_table = new_table; + + new_table = old_table; + export_vars.innodb_instant_alter_column++; + } + + /** Revert prepare_instant() if the transaction is rolled back. */ + void rollback_instant() + { + if (!is_instant()) return; + old_table->rollback_instant(old_n_cols, + old_cols, old_col_names); + } + + /** @return whether this is instant ALTER TABLE */ + bool is_instant() const + { + DBUG_ASSERT(!instant_table || !instant_table->can_be_evicted); + return instant_table; + } + private: // Disable copying ha_innobase_inplace_ctx(const ha_innobase_inplace_ctx&); @@ -410,44 +468,61 @@ innobase_spatial_exist( return(false); } -/** Determine if ALTER TABLE needs to rebuild the table. -@param ha_alter_info the DDL operation -@param table metadata before ALTER TABLE -@return whether it is necessary to rebuild the table */ +/** Determine if CHANGE_CREATE_OPTION requires rebuilding the table. +@param[in] ha_alter_info the ALTER TABLE operation +@param[in] table metadata before ALTER TABLE +@return whether it is mandatory to rebuild the table */ +static bool create_option_need_rebuild( + const Alter_inplace_info* ha_alter_info, + const TABLE* table) +{ + DBUG_ASSERT(ha_alter_info->handler_flags + & Alter_inplace_info::CHANGE_CREATE_OPTION); + + if (ha_alter_info->create_info->used_fields + & (HA_CREATE_USED_ROW_FORMAT + | HA_CREATE_USED_KEY_BLOCK_SIZE)) { + /* Specifying ROW_FORMAT or KEY_BLOCK_SIZE requires + rebuilding the table. (These attributes in the .frm + file may disagree with the InnoDB data dictionary, and + the interpretation of thse attributes depends on + InnoDB parameters. That is why we for now always + require a rebuild when these attributes are specified.) */ + return true; + } + + const ha_table_option_struct& alt_opt= + *ha_alter_info->create_info->option_struct; + const ha_table_option_struct& opt= *table->s->option_struct; + + if (alt_opt.page_compressed != opt.page_compressed + || alt_opt.page_compression_level + != opt.page_compression_level + || alt_opt.encryption != opt.encryption + || alt_opt.encryption_key_id != opt.encryption_key_id) { + return(true); + } + + return false; +} + +/** Determine if ALTER TABLE needs to rebuild the table +(or perform instant operation). +@param[in] ha_alter_info the ALTER TABLE operation +@param[in] table metadata before ALTER TABLE +@return whether it is necessary to rebuild the table or to alter columns */ static MY_ATTRIBUTE((nonnull, warn_unused_result)) bool innobase_need_rebuild( const Alter_inplace_info* ha_alter_info, const TABLE* table) { - Alter_inplace_info::HA_ALTER_FLAGS alter_inplace_flags = - ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE; - - if (alter_inplace_flags & Alter_inplace_info::CHANGE_CREATE_OPTION) { - const ha_table_option_struct& alt_opt= - *ha_alter_info->create_info->option_struct; - const ha_table_option_struct& opt= *table->s->option_struct; - - if (alt_opt.page_compressed != opt.page_compressed - || alt_opt.page_compression_level - != opt.page_compression_level - || alt_opt.encryption != opt.encryption - || alt_opt.encryption_key_id != opt.encryption_key_id) { - return(true); - } - } - - if (alter_inplace_flags == Alter_inplace_info::CHANGE_CREATE_OPTION - && !(ha_alter_info->create_info->used_fields - & (HA_CREATE_USED_ROW_FORMAT - | HA_CREATE_USED_KEY_BLOCK_SIZE))) { - /* Any other CHANGE_CREATE_OPTION than changing - ROW_FORMAT or KEY_BLOCK_SIZE can be done without - rebuilding the table. */ - return(false); + if ((ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE) + == Alter_inplace_info::CHANGE_CREATE_OPTION) { + return create_option_need_rebuild(ha_alter_info, table); } - return(!!(alter_inplace_flags & INNOBASE_ALTER_REBUILD)); + return !!(ha_alter_info->handler_flags & INNOBASE_ALTER_REBUILD); } /** Check if virtual column in old and new table are in order, excluding @@ -550,6 +625,54 @@ check_v_col_in_order( return(true); } +/** Determine if an instant operation is possible for altering columns. +@param[in] ha_alter_info the ALTER TABLE operation +@param[in] table table definition before ALTER TABLE */ +static +bool +instant_alter_column_possible( + const Alter_inplace_info* ha_alter_info, + const TABLE* table) +{ + // Making table system-versioned instantly is not implemented yet. + if (ha_alter_info->handler_flags & Alter_inplace_info::ALTER_ADD_SYSTEM_VERSIONING) { + return false; + } + + if (~ha_alter_info->handler_flags + & Alter_inplace_info::ADD_STORED_BASE_COLUMN) { + return false; + } + + /* At the moment, we disallow ADD [UNIQUE] INDEX together with + instant ADD COLUMN. + + The main reason is that the work of instant ADD must be done + in commit_inplace_alter_table(). For the rollback_instant() + to work, we must add the columns to dict_table_t beforehand, + and roll back those changes in case the transaction is rolled + back. + + If we added the columns to the dictionary cache already in the + prepare_inplace_alter_table(), we would have to deal with + column number mismatch in ha_innobase::open(), write_row() and + other functions. */ + + /* FIXME: allow instant ADD COLUMN together with + INNOBASE_ONLINE_CREATE (ADD [UNIQUE] INDEX) on pre-existing + columns. */ + if (ha_alter_info->handler_flags + & ((INNOBASE_ALTER_REBUILD | INNOBASE_ONLINE_CREATE) + & ~Alter_inplace_info::ADD_STORED_BASE_COLUMN + & ~Alter_inplace_info::CHANGE_CREATE_OPTION)) { + return false; + } + + return !(ha_alter_info->handler_flags + & Alter_inplace_info::CHANGE_CREATE_OPTION) + || !create_option_need_rebuild(ha_alter_info, table); +} + /** Check if InnoDB supports a particular alter table in-place @param altered_table TABLE object for new version of table. @param ha_alter_info Structure describing changes to be done @@ -573,6 +696,13 @@ ha_innobase::check_if_supported_inplace_alter( { DBUG_ENTER("check_if_supported_inplace_alter"); + if ((table->versioned(VERS_TIMESTAMP) || altered_table->versioned(VERS_TIMESTAMP)) + && innobase_need_rebuild(ha_alter_info, table)) { + ha_alter_info->unsupported_reason = + innobase_get_err_msg(ER_VERS_INPLACE_NOT_IMPLEMENTED); + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + /* Before 10.2.2 information about virtual columns was not stored in system tables. We need to do a full alter to rebuild proper 10.2.2+ metadata with the information about virtual columns */ @@ -885,7 +1015,7 @@ ha_innobase::check_if_supported_inplace_alter( for (uint i = 0; i < ha_alter_info->index_drop_count; i++) { if (!my_strcasecmp( system_charset_info, - ha_alter_info->index_drop_buffer[i]->name, + ha_alter_info->index_drop_buffer[i]->name.str, FTS_DOC_ID_INDEX_NAME)) { ha_alter_info->unsupported_reason = innobase_get_err_msg( ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS); @@ -916,82 +1046,14 @@ ha_innobase::check_if_supported_inplace_alter( m_prebuilt->trx->will_lock++; - if (!online) { - /* We already determined that only a non-locking - operation is possible. */ - } else if (((ha_alter_info->handler_flags - & Alter_inplace_info::ADD_PK_INDEX) - || innobase_need_rebuild(ha_alter_info, table)) - && (innobase_fulltext_exist(altered_table) - || innobase_spatial_exist(altered_table) - || innobase_indexed_virtual_exist(altered_table))) { - /* Refuse to rebuild the table online, if - FULLTEXT OR SPATIAL indexes or indexed virtual columns - are to survive the rebuild. */ - online = false; - /* If the table already contains fulltext indexes, - refuse to rebuild the table natively altogether. */ - if (m_prebuilt->table->fts) { - ha_alter_info->unsupported_reason = innobase_get_err_msg( - ER_INNODB_FT_LIMIT); - DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); - } - - if (innobase_spatial_exist(altered_table)) { - ha_alter_info->unsupported_reason = - innobase_get_err_msg( - ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS); - } else if (!innobase_fulltext_exist(altered_table)) { - /* MDEV-14341 FIXME: Remove this limitation. */ - ha_alter_info->unsupported_reason = - "online rebuild with indexed virtual columns"; - } else { - ha_alter_info->unsupported_reason = - innobase_get_err_msg( - ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS); - } - } else if ((ha_alter_info->handler_flags - & Alter_inplace_info::ADD_INDEX)) { - /* ADD FULLTEXT|SPATIAL INDEX requires a lock. - - We could do ADD FULLTEXT INDEX without a lock if the - table already contains an FTS_DOC_ID column, but in - that case we would have to apply the modification log - to the full-text indexes. - - We could also do ADD SPATIAL INDEX by implementing - row_log_apply() for it. */ - - for (uint i = 0; i < ha_alter_info->index_add_count; i++) { - const KEY* key = - &ha_alter_info->key_info_buffer[ - ha_alter_info->index_add_buffer[i]]; - if (key->flags & HA_FULLTEXT) { - DBUG_ASSERT(!(key->flags & HA_KEYFLAG_MASK - & ~(HA_FULLTEXT - | HA_PACK_KEY - | HA_GENERATED_KEY - | HA_BINARY_PACK_KEY))); - ha_alter_info->unsupported_reason = innobase_get_err_msg( - ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS); - online = false; - break; - } - if (key->flags & HA_SPATIAL) { - ha_alter_info->unsupported_reason = innobase_get_err_msg( - ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS); - online = false; - break; - } - } - } - /* When changing a NULL column to NOT NULL and specifying a DEFAULT value, ensure that the DEFAULT expression is a constant. Also, in ADD COLUMN, for now we only support a constant DEFAULT expression. */ cf_it.rewind(); Field **af = altered_table->field; + bool add_column_not_last = false; + uint n_stored_cols = 0, n_add_cols = 0; while (Create_field* cf = cf_it++) { DBUG_ASSERT(cf->field @@ -1053,6 +1115,11 @@ ha_innobase::check_if_supported_inplace_alter( } else if (!(*af)->default_value || !((*af)->default_value->flags & ~(VCOL_SESSION_FUNC | VCOL_TIME_FUNC))) { + n_add_cols++; + + if (af < &altered_table->field[table_share->fields]) { + add_column_not_last = true; + } /* The added NOT NULL column lacks a DEFAULT value, or the DEFAULT is the same for all rows. (Time functions, such as CURRENT_TIMESTAMP(), @@ -1077,7 +1144,103 @@ ha_innobase::check_if_supported_inplace_alter( DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); next_column: - af++; + n_stored_cols += (*af++)->stored_in_db(); + } + + if (!add_column_not_last + && uint(m_prebuilt->table->n_cols) - DATA_N_SYS_COLS + n_add_cols + == n_stored_cols + && m_prebuilt->table->supports_instant() + && instant_alter_column_possible(ha_alter_info, table)) { + /* We can perform instant ADD COLUMN, because all + columns are going to be added after existing ones + (and not after hidden InnoDB columns, such as FTS_DOC_ID). */ + + /* MDEV-14246 FIXME: return HA_ALTER_INPLACE_NO_LOCK and + perform all work in ha_innobase::commit_inplace_alter_table(), + to avoid an unnecessary MDL upgrade/downgrade cycle. */ + DBUG_RETURN(HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE); + } + + if (!online) { + /* We already determined that only a non-locking + operation is possible. */ + } else if (((ha_alter_info->handler_flags + & Alter_inplace_info::ADD_PK_INDEX) + || innobase_need_rebuild(ha_alter_info, table)) + && (innobase_fulltext_exist(altered_table) + || innobase_spatial_exist(altered_table) + || innobase_indexed_virtual_exist(altered_table))) { + /* Refuse to rebuild the table online, if + FULLTEXT OR SPATIAL indexes are to survive the rebuild. */ + online = false; + /* If the table already contains fulltext indexes, + refuse to rebuild the table natively altogether. */ + if (m_prebuilt->table->fts) { +cannot_create_many_fulltext_index: + ha_alter_info->unsupported_reason = innobase_get_err_msg( + ER_INNODB_FT_LIMIT); + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + } + + if (innobase_spatial_exist(altered_table)) { + ha_alter_info->unsupported_reason = + innobase_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS); + } else if (!innobase_fulltext_exist(altered_table)) { + /* MDEV-14341 FIXME: Remove this limitation. */ + ha_alter_info->unsupported_reason = + "online rebuild with indexed virtual columns"; + } else { + ha_alter_info->unsupported_reason = + innobase_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS); + } + } else if ((ha_alter_info->handler_flags + & Alter_inplace_info::ADD_INDEX)) { + /* ADD FULLTEXT|SPATIAL INDEX requires a lock. + + We could do ADD FULLTEXT INDEX without a lock if the + table already contains an FTS_DOC_ID column, but in + that case we would have to apply the modification log + to the full-text indexes. + + We could also do ADD SPATIAL INDEX by implementing + row_log_apply() for it. */ + bool add_fulltext = false; + + for (uint i = 0; i < ha_alter_info->index_add_count; i++) { + const KEY* key = + &ha_alter_info->key_info_buffer[ + ha_alter_info->index_add_buffer[i]]; + if (key->flags & HA_FULLTEXT) { + DBUG_ASSERT(!(key->flags & HA_KEYFLAG_MASK + & ~(HA_FULLTEXT + | HA_PACK_KEY + | HA_GENERATED_KEY + | HA_BINARY_PACK_KEY))); + if (add_fulltext) { + goto cannot_create_many_fulltext_index; + } + add_fulltext = true; + ha_alter_info->unsupported_reason = innobase_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS); + online = false; + } + if (online && (key->flags & HA_SPATIAL)) { + ha_alter_info->unsupported_reason = innobase_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS); + online = false; + } + } + } + + // FIXME: implement Online DDL for system-versioned tables + if ((table->versioned(VERS_TRX_ID) || altered_table->versioned(VERS_TRX_ID)) + && innobase_need_rebuild(ha_alter_info, table)) { + ha_alter_info->unsupported_reason = + innobase_get_err_msg(ER_VERS_INPLACE_NOT_IMPLEMENTED); + online = false; } DBUG_RETURN(online @@ -1636,7 +1799,7 @@ innobase_get_foreign_key_info( /* Not possible to add a foreign key without a referenced column */ mutex_exit(&dict_sys->mutex); - my_error(ER_CANNOT_ADD_FOREIGN, MYF(0)); + my_error(ER_CANNOT_ADD_FOREIGN, MYF(0), tbl_namep); goto err_exit; } @@ -1820,7 +1983,7 @@ null_field: continue; } - ifield = rec_get_nth_field(rec, offsets, ipos, &ilen); + ifield = rec_get_nth_cfield(rec, index, offsets, ipos, &ilen); /* Assign the NULL flag */ if (ilen == UNIV_SQL_NULL) { @@ -1946,21 +2109,6 @@ innobase_row_to_mysql( } } -/*************************************************************//** -Resets table->record[0]. */ -void -innobase_rec_reset( -/*===============*/ - TABLE* table) /*!< in/out: MySQL table */ -{ - uint n_fields = table->s->fields; - uint i; - - for (i = 0; i < n_fields; i++) { - table->field[i]->set_default(); - } -} - /*******************************************************************//** This function checks that index keys are sensible. @return 0 or error number */ @@ -1985,9 +2133,9 @@ innobase_check_index_keys( const KEY& key2 = info->key_info_buffer[ info->index_add_buffer[i]]; - if (0 == strcmp(key.name, key2.name)) { + if (0 == strcmp(key.name.str, key2.name.str)) { my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), - key.name); + key.name.str); return(ER_WRONG_NAME_FOR_INDEX); } @@ -2001,7 +2149,7 @@ innobase_check_index_keys( index; index = dict_table_get_next_index(index)) { if (index->is_committed() - && !strcmp(key.name, index->name)) { + && !strcmp(key.name.str, index->name)) { break; } } @@ -2026,7 +2174,8 @@ innobase_check_index_keys( const KEY* drop_key = info->index_drop_buffer[i]; - if (0 == strcmp(key.name, drop_key->name)) { + if (0 == strcmp(key.name.str, + drop_key->name.str)) { goto name_ok; } } @@ -2050,8 +2199,7 @@ innobase_check_index_keys( #endif /* MYSQL_RENAME_INDEX */ my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), - key.name); - + key.name.str); return(ER_WRONG_NAME_FOR_INDEX); } @@ -2211,7 +2359,7 @@ innobase_create_index_def( index->parser = NULL; index->key_number = key_number; index->n_fields = n_fields; - index->name = mem_heap_strdup(heap, key->name); + index->name = mem_heap_strdup(heap, key->name.str); index->rebuild = new_clustered; if (key_clustered) { @@ -2231,8 +2379,8 @@ innobase_create_index_def( if (key->flags & HA_USES_PARSER) { for (ulint j = 0; j < altered_table->s->keys; j++) { - if (ut_strcmp(altered_table->key_info[j].name, - key->name) == 0) { + if (ut_strcmp(altered_table->key_info[j].name.str, + key->name.str) == 0) { ut_ad(altered_table->key_info[j].flags & HA_USES_PARSER); @@ -2408,13 +2556,13 @@ innobase_fts_check_doc_id_index( const KEY& key = altered_table->key_info[i]; if (innobase_strcasecmp( - key.name, FTS_DOC_ID_INDEX_NAME)) { + key.name.str, FTS_DOC_ID_INDEX_NAME)) { continue; } if ((key.flags & HA_NOSAME) && key.user_defined_key_parts == 1 - && !strcmp(key.name, FTS_DOC_ID_INDEX_NAME) + && !strcmp(key.name.str, FTS_DOC_ID_INDEX_NAME) && !strcmp(key.key_part[0].field->field_name.str, FTS_DOC_ID_COL_NAME)) { if (fts_doc_col_no) { @@ -2485,7 +2633,7 @@ innobase_fts_check_doc_id_index_in_def( for (ulint j = 0; j < n_key; j++) { const KEY* key = &key_info[j]; - if (innobase_strcasecmp(key->name, FTS_DOC_ID_INDEX_NAME)) { + if (innobase_strcasecmp(key->name.str, FTS_DOC_ID_INDEX_NAME)) { continue; } @@ -2493,7 +2641,7 @@ innobase_fts_check_doc_id_index_in_def( named as "FTS_DOC_ID_INDEX" and on column "FTS_DOC_ID" */ if (!(key->flags & HA_NOSAME) || key->user_defined_key_parts != 1 - || strcmp(key->name, FTS_DOC_ID_INDEX_NAME) + || strcmp(key->name.str, FTS_DOC_ID_INDEX_NAME) || strcmp(key->key_part[0].field->field_name.str, FTS_DOC_ID_COL_NAME)) { return(FTS_INCORRECT_DOC_ID_INDEX); @@ -2565,7 +2713,7 @@ innobase_create_key_defs( new_primary = n_add > 0 && !my_strcasecmp(system_charset_info, - key_info[*add].name, "PRIMARY"); + key_info[*add].name.str, "PRIMARY"); n_fts_add = 0; /* If there is a UNIQUE INDEX consisting entirely of NOT NULL @@ -3083,11 +3231,7 @@ innobase_build_col_map( } while (const Create_field* new_field = cf_it++) { - bool is_v = false; - - if (innobase_is_v_fld(new_field)) { - is_v = true; - } + bool is_v = innobase_is_v_fld(new_field); ulint num_old_v = 0; @@ -3908,34 +4052,34 @@ innobase_add_one_virtual( return(error); } -/** Update INNODB SYS_TABLES on number of virtual columns +/** Update SYS_TABLES.N_COLS in the data dictionary. @param[in] user_table InnoDB table -@param[in] n_col number of columns +@param[in] n_cols the new value of SYS_TABLES.N_COLS @param[in] trx transaction -@return DB_SUCCESS if successful, otherwise error code */ +@return whether the operation failed */ static -dberr_t -innobase_update_n_virtual( - const dict_table_t* table, - ulint n_col, - trx_t* trx) +bool +innodb_update_n_cols(const dict_table_t* table, ulint n_cols, trx_t* trx) { - dberr_t err = DB_SUCCESS; pars_info_t* info = pars_info_create(); - pars_info_add_int4_literal(info, "num_col", n_col); + pars_info_add_int4_literal(info, "n", n_cols); pars_info_add_ull_literal(info, "id", table->id); - err = que_eval_sql( - info, - "PROCEDURE RENUMBER_TABLE_ID_PROC () IS\n" - "BEGIN\n" - "UPDATE SYS_TABLES" - " SET N_COLS = :num_col\n" - " WHERE ID = :id;\n" - "END;\n", FALSE, trx); + dberr_t err = que_eval_sql(info, + "PROCEDURE UPDATE_N_COLS () IS\n" + "BEGIN\n" + "UPDATE SYS_TABLES SET N_COLS = :n" + " WHERE ID = :id;\n" + "END;\n", FALSE, trx); + + if (err != DB_SUCCESS) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "InnoDB: Updating SYS_TABLES.N_COLS failed"); + return true; + } - return(err); + return false; } /** Update system table for adding virtual column(s) @@ -3975,27 +4119,262 @@ innobase_add_virtual_try( } - ulint n_col = user_table->n_cols; - ulint n_v_col = user_table->n_v_cols; + ulint n_col = user_table->n_cols - DATA_N_SYS_COLS; + ulint n_v_col = user_table->n_v_cols + + ctx->num_to_add_vcol - ctx->num_to_drop_vcol; + ulint new_n = dict_table_encode_n_col(n_col, n_v_col) + + ((user_table->flags & DICT_TF_COMPACT) << 31); + + return innodb_update_n_cols(user_table, new_n, trx); +} + +/** Insert into SYS_COLUMNS and insert/update the 'default row' +for instant ADD COLUMN. +@param[in,out] ha_alter_info Data used during in-place alter +@param[in,out] ctx ALTER TABLE context for the current partition +@param[in] altered_table MySQL table that is being altered +@param[in] table MySQL table as it is before the ALTER operation +@param[in,out] trx dictionary transaction +@retval true failure +@retval false success */ +static +bool +innobase_add_instant_try( + Alter_inplace_info* ha_alter_info, + ha_innobase_inplace_ctx*ctx, + const TABLE* altered_table, + const TABLE* table, + trx_t* trx) +{ + DBUG_ASSERT(!ctx->need_rebuild()); - n_v_col += ctx->num_to_add_vcol; + if (!ctx->is_instant()) return false; - n_col -= dict_table_get_n_sys_cols(user_table); + DBUG_ASSERT(altered_table->s->fields > table->s->fields); + DBUG_ASSERT(ctx->old_table->n_cols == ctx->old_n_cols); - n_v_col -= ctx->num_to_drop_vcol; + dict_table_t* user_table = ctx->old_table; + user_table->instant_add_column(*ctx->instant_table); + dict_index_t* index = dict_table_get_first_index(user_table); + /* The table may have been emptied and may have lost its + 'instant-add-ness' during this instant ADD COLUMN. */ - ulint new_n = dict_table_encode_n_col(n_col, n_v_col) - + ((user_table->flags & DICT_TF_COMPACT) << 31); + /* Construct a table row of default values for the stored columns. */ + dtuple_t* row = dtuple_create(ctx->heap, user_table->n_cols); + dict_table_copy_types(row, user_table); + Field** af = altered_table->field; + Field** const end = altered_table->field + altered_table->s->fields; - err = innobase_update_n_virtual(user_table, new_n, trx); + for (uint i = 0; af < end; af++) { + if (!(*af)->stored_in_db()) { + continue; + } - if (err != DB_SUCCESS) { - my_error(ER_INTERNAL_ERROR, MYF(0), - "InnoDB: ADD COLUMN...VIRTUAL"); - return(true); + dict_col_t* col = dict_table_get_nth_col(user_table, i); + DBUG_ASSERT(!strcmp((*af)->field_name.str, + dict_table_get_col_name(user_table, i))); + + dfield_t* d = dtuple_get_nth_field(row, i); + + if (col->is_instant()) { + dfield_set_data(d, col->def_val.data, + col->def_val.len); + } else if ((*af)->real_maybe_null()) { + /* Store NULL for nullable 'core' columns. */ + dfield_set_null(d); + } else { + switch ((*af)->type()) { + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_LONG_BLOB: + /* Store the empty string for 'core' + variable-length NOT NULL columns. */ + dfield_set_data(d, field_ref_zero, 0); + break; + default: + /* For fixed-length NOT NULL 'core' columns, + get a dummy default value from SQL. Note that + we will preserve the old values of these + columns when updating the 'default row' + record, to avoid unnecessary updates. */ + ulint len = (*af)->pack_length(); + DBUG_ASSERT(d->type.mtype != DATA_INT + || len <= 8); + row_mysql_store_col_in_innobase_format( + d, d->type.mtype == DATA_INT + ? static_cast<byte*>( + mem_heap_alloc(ctx->heap, len)) + : NULL, true, (*af)->ptr, len, + dict_table_is_comp(user_table)); + } + } + + if (i + DATA_N_SYS_COLS < ctx->old_n_cols) { + i++; + continue; + } + + pars_info_t* info = pars_info_create(); + pars_info_add_ull_literal(info, "id", user_table->id); + pars_info_add_int4_literal(info, "pos", i); + pars_info_add_str_literal(info, "name", (*af)->field_name.str); + pars_info_add_int4_literal(info, "mtype", d->type.mtype); + pars_info_add_int4_literal(info, "prtype", d->type.prtype); + pars_info_add_int4_literal(info, "len", d->type.len); + + dberr_t err = que_eval_sql( + info, + "PROCEDURE ADD_COL () IS\n" + "BEGIN\n" + "INSERT INTO SYS_COLUMNS VALUES" + "(:id,:pos,:name,:mtype,:prtype,:len,0);\n" + "END;\n", FALSE, trx); + if (err != DB_SUCCESS) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "InnoDB: Insert into SYS_COLUMNS failed"); + return(true); + } + + i++; } - return(false); + if (innodb_update_n_cols(user_table, dict_table_encode_n_col( + user_table->n_cols - DATA_N_SYS_COLS, + user_table->n_v_cols) + | (user_table->flags & DICT_TF_COMPACT) << 31, + trx)) { + return true; + } + + unsigned i = user_table->n_cols - DATA_N_SYS_COLS; + byte trx_id[DATA_TRX_ID_LEN], roll_ptr[DATA_ROLL_PTR_LEN]; + dfield_set_data(dtuple_get_nth_field(row, i++), field_ref_zero, + DATA_ROW_ID_LEN); + dfield_set_data(dtuple_get_nth_field(row, i++), trx_id, sizeof trx_id); + dfield_set_data(dtuple_get_nth_field(row, i),roll_ptr,sizeof roll_ptr); + DBUG_ASSERT(i + 1 == user_table->n_cols); + + trx_write_trx_id(trx_id, trx->id); + /* The DB_ROLL_PTR will be assigned later, when allocating undo log. + Silence a Valgrind warning in dtuple_validate() when + row_ins_clust_index_entry_low() searches for the insert position. */ + memset(roll_ptr, 0, sizeof roll_ptr); + + dtuple_t* entry = row_build_index_entry(row, NULL, index, ctx->heap); + entry->info_bits = REC_INFO_DEFAULT_ROW; + + mtr_t mtr; + mtr.start(); + mtr.set_named_space(index->space); + btr_pcur_t pcur; + btr_pcur_open_at_index_side(true, index, BTR_MODIFY_TREE, &pcur, true, + 0, &mtr); + ut_ad(btr_pcur_is_before_first_on_page(&pcur)); + btr_pcur_move_to_next_on_page(&pcur); + + buf_block_t* block = btr_pcur_get_block(&pcur); + ut_ad(page_is_leaf(block->frame)); + ut_ad(!page_has_prev(block->frame)); + ut_ad(!buf_block_get_page_zip(block)); + const rec_t* rec = btr_pcur_get_rec(&pcur); + que_thr_t* thr = pars_complete_graph_for_exec( + NULL, trx, ctx->heap, NULL); + + if (rec_is_default_row(rec, index)) { + ut_ad(page_rec_is_user_rec(rec)); + if (!page_has_next(block->frame) + && page_rec_is_last(rec, block->frame)) { + goto empty_table; + } + /* Extend the record with the instantly added columns. */ + const unsigned n = user_table->n_cols - ctx->old_n_cols; + /* Reserve room for DB_TRX_ID,DB_ROLL_PTR and any + non-updated off-page columns in case they are moved off + page as a result of the update. */ + upd_t* update = upd_create(index->n_fields, ctx->heap); + update->n_fields = n; + update->info_bits = REC_INFO_DEFAULT_ROW; + /* Add the default values for instantly added columns */ + for (unsigned i = 0; i < n; i++) { + upd_field_t* uf = upd_get_nth_field(update, i); + unsigned f = index->n_fields - n + i; + uf->field_no = f; + uf->new_val = entry->fields[f]; + } + ulint* offsets = NULL; + mem_heap_t* offsets_heap = NULL; + big_rec_t* big_rec; + dberr_t error = btr_cur_pessimistic_update( + BTR_NO_LOCKING_FLAG, btr_pcur_get_btr_cur(&pcur), + &offsets, &offsets_heap, ctx->heap, + &big_rec, update, UPD_NODE_NO_ORD_CHANGE, + thr, trx->id, &mtr); + if (big_rec) { + if (error == DB_SUCCESS) { + error = btr_store_big_rec_extern_fields( + &pcur, offsets, big_rec, &mtr, + BTR_STORE_UPDATE); + } + + dtuple_big_rec_free(big_rec); + } + if (offsets_heap) { + mem_heap_free(offsets_heap); + } + btr_pcur_close(&pcur); + mtr.commit(); + return error != DB_SUCCESS; + } else if (page_rec_is_supremum(rec)) { +empty_table: + /* The table is empty. */ + ut_ad(page_is_root(block->frame)); + btr_page_empty(block, NULL, index, 0, &mtr); + index->remove_instant(); + mtr.commit(); + return false; + } + + /* Convert the table to the instant ADD COLUMN format. */ + ut_ad(user_table->is_instant()); + mtr.commit(); + mtr.start(); + mtr.set_named_space(index->space); + dberr_t err; + if (page_t* root = btr_root_get(index, &mtr)) { + switch (fil_page_get_type(root)) { + case FIL_PAGE_TYPE_INSTANT: + DBUG_ASSERT(page_get_instant(root) + == index->n_core_fields); + break; + case FIL_PAGE_INDEX: + DBUG_ASSERT(!page_is_comp(root) + || !page_get_instant(root)); + break; + default: + DBUG_ASSERT(!"wrong page type"); + mtr.commit(); + return true; + } + + mlog_write_ulint(root + FIL_PAGE_TYPE, + FIL_PAGE_TYPE_INSTANT, MLOG_2BYTES, + &mtr); + page_set_instant(root, index->n_core_fields, &mtr); + mtr.commit(); + mtr.start(); + mtr.set_named_space(index->space); + err = row_ins_clust_index_entry_low( + BTR_NO_LOCKING_FLAG, BTR_MODIFY_TREE, index, + index->n_uniq, entry, 0, thr, false); + } else { + err = DB_CORRUPTION; + } + + mtr.commit(); + return err != DB_SUCCESS; } /** Update INNODB SYS_COLUMNS on new virtual column's position @@ -4209,24 +4588,12 @@ innobase_drop_virtual_try( } - ulint n_col = user_table->n_cols; - ulint n_v_col = user_table->n_v_cols; - - n_v_col -= ctx->num_to_drop_vcol; - - n_col -= dict_table_get_n_sys_cols(user_table); - + ulint n_col = user_table->n_cols - DATA_N_SYS_COLS; + ulint n_v_col = user_table->n_v_cols - ctx->num_to_drop_vcol; ulint new_n = dict_table_encode_n_col(n_col, n_v_col) - + ((user_table->flags & DICT_TF_COMPACT) << 31); - - err = innobase_update_n_virtual(user_table, new_n, trx); + + ((user_table->flags & DICT_TF_COMPACT) << 31); - if (err != DB_SUCCESS) { - my_error(ER_INTERNAL_ERROR, MYF(0), - "InnoDB: DROP COLUMN...VIRTUAL"); - } - - return(false); + return innodb_update_n_cols(user_table, new_n, trx); } /** Adjust the create index column number from "New table" to @@ -4312,6 +4679,35 @@ innodb_v_adjust_idx_col( } } +/** Create index metadata in the data dictionary. +@param[in,out] trx dictionary transaction +@param[in,out] index index being created +@param[in] add_v virtual columns that are being added, or NULL +@return DB_SUCCESS or error code */ +MY_ATTRIBUTE((nonnull(1,2), warn_unused_result)) +static +dberr_t +create_index_dict( + trx_t* trx, + dict_index_t* index, + const dict_add_v_col_t* add_v) +{ + DBUG_ENTER("create_index_dict"); + + mem_heap_t* heap = mem_heap_create(512); + ind_node_t* node = ind_create_graph_create(index, heap, add_v); + que_thr_t* thr = pars_complete_graph_for_exec(node, trx, heap, NULL); + + que_fork_start_command( + static_cast<que_fork_t*>(que_node_get_parent(thr))); + + que_run_threads(thr); + + que_graph_free((que_t*) que_node_get_parent(thr)); + + DBUG_RETURN(trx->error_state); +} + /** Update internal structures with concurrent writes blocked, while preparing ALTER TABLE. @@ -4347,7 +4743,7 @@ prepare_inplace_alter_table_dict( index_def_t* index_defs; /* index definitions */ dict_table_t* user_table; dict_index_t* fts_index = NULL; - ulint new_clustered = 0; + bool new_clustered = false; dberr_t error; ulint num_fts_index; dict_add_v_col_t* add_v = NULL; @@ -4406,12 +4802,6 @@ prepare_inplace_alter_table_dict( here */ ut_ad(check_v_col_in_order(old_table, altered_table, ha_alter_info)); - /* Create a background transaction for the operations on - the data dictionary tables. */ - ctx->trx = innobase_trx_allocate(ctx->prebuilt->trx->mysql_thd); - - trx_start_for_ddl(ctx->trx, TRX_DICT_OP_INDEX); - /* Create table containing all indexes to be built in this ALTER TABLE ADD INDEX so that they are in the correct order in the table. */ @@ -4430,30 +4820,7 @@ prepare_inplace_alter_table_dict( fts_doc_id_col, add_fts_doc_id, add_fts_doc_id_idx, old_table); - new_clustered = DICT_CLUSTERED & index_defs[0].ind_type; - - if (num_fts_index > 1) { - my_error(ER_INNODB_FT_LIMIT, MYF(0)); - goto error_handled; - } - - if (!ctx->online) { - /* This is not an online operation (LOCK=NONE). */ - } else if (ctx->add_autoinc == ULINT_UNDEFINED - && num_fts_index == 0 - && (!innobase_need_rebuild(ha_alter_info, old_table) - || !innobase_fulltext_exist(altered_table))) { - /* InnoDB can perform an online operation (LOCK=NONE). */ - } else { - size_t query_length; - /* This should have been blocked in - check_if_supported_inplace_alter(). */ - ut_ad(0); - my_error(ER_NOT_SUPPORTED_YET, MYF(0), - innobase_get_stmt_unsafe(ctx->prebuilt->trx->mysql_thd, - &query_length)); - goto error_handled; - } + new_clustered = (DICT_CLUSTERED & index_defs[0].ind_type) != 0; /* The primary index would be rebuilt if a FTS Doc ID column is to be added, and the primary index definition @@ -4466,17 +4833,12 @@ prepare_inplace_alter_table_dict( /* Allocate memory for dictionary index definitions */ ctx->add_index = static_cast<dict_index_t**>( - mem_heap_alloc(ctx->heap, ctx->num_to_add_index + mem_heap_zalloc(ctx->heap, ctx->num_to_add_index * sizeof *ctx->add_index)); ctx->add_key_numbers = add_key_nums = static_cast<ulint*>( mem_heap_alloc(ctx->heap, ctx->num_to_add_index * sizeof *ctx->add_key_numbers)); - /* This transaction should be dictionary operation, so that - the data dictionary will be locked during crash recovery. */ - - ut_ad(ctx->trx->dict_operation == TRX_DICT_OP_INDEX); - /* Acquire a lock on the table before creating any indexes. */ if (ctx->online) { @@ -4491,6 +4853,12 @@ prepare_inplace_alter_table_dict( } } + /* Create a background transaction for the operations on + the data dictionary tables. */ + ctx->trx = innobase_trx_allocate(ctx->prebuilt->trx->mysql_thd); + + trx_start_for_ddl(ctx->trx, TRX_DICT_OP_INDEX); + /* Latch the InnoDB data dictionary exclusively so that no deadlocks or lock waits can happen in it during an index create operation. */ @@ -4510,10 +4878,43 @@ prepare_inplace_alter_table_dict( ut_d(dict_table_check_for_dup_indexes( ctx->new_table, CHECK_ABORTED_OK)); + DBUG_EXECUTE_IF("innodb_OOM_prepare_inplace_alter", + error = DB_OUT_OF_MEMORY; + goto error_handling;); + /* If a new clustered index is defined for the table we need to rebuild the table with a temporary name. */ if (new_clustered) { + if (innobase_check_foreigns( + ha_alter_info, altered_table, old_table, + user_table, ctx->drop_fk, ctx->num_to_drop_fk)) { +new_clustered_failed: + DBUG_ASSERT(ctx->trx != ctx->prebuilt->trx); + trx_rollback_to_savepoint(ctx->trx, NULL); + + ut_ad(user_table->get_ref_count() == 1); + + online_retry_drop_indexes_with_trx( + user_table, ctx->trx); + + if (ctx->need_rebuild()) { + ut_ad(!ctx->new_table->cached); + dict_mem_table_free(ctx->new_table); + ctx->new_table = ctx->old_table; + } + + while (ctx->num_to_add_index--) { + if (dict_index_t*& i = ctx->add_index[ + ctx->num_to_add_index]) { + dict_mem_index_free(i); + i = NULL; + } + } + + goto err_exit; + } + size_t dblen = ctx->old_table->name.dblen() + 1; size_t tablen = altered_table->s->table_name.length; const char* part = ctx->old_table->name.part(); @@ -4531,39 +4932,6 @@ prepare_inplace_alter_table_dict( dtuple_t* add_cols; ulint space_id = 0; ulint z = 0; - uint32_t key_id = FIL_DEFAULT_ENCRYPTION_KEY; - fil_encryption_t mode = FIL_ENCRYPTION_DEFAULT; - - if (fil_space_t* space - = fil_space_acquire(ctx->prebuilt->table->space)) { - if (const fil_space_crypt_t* crypt_data - = space->crypt_data) { - key_id = crypt_data->key_id; - mode = crypt_data->encryption; - } - - fil_space_release(space); - } - - if (ha_alter_info->handler_flags - & Alter_inplace_info::CHANGE_CREATE_OPTION) { - const ha_table_option_struct& alt_opt= - *ha_alter_info->create_info->option_struct; - const ha_table_option_struct& opt= - *old_table->s->option_struct; - if (alt_opt.encryption != opt.encryption - || alt_opt.encryption_key_id - != opt.encryption_key_id) { - key_id = uint32_t(alt_opt.encryption_key_id); - mode = fil_encryption_t(alt_opt.encryption); - } - } - - if (innobase_check_foreigns( - ha_alter_info, altered_table, old_table, - user_table, ctx->drop_fk, ctx->num_to_drop_fk)) { - goto new_clustered_failed; - } for (uint i = 0; i < altered_table->s->fields; i++) { const Field* field = altered_table->field[i]; @@ -4588,15 +4956,6 @@ prepare_inplace_alter_table_dict( DBUG_ASSERT(!add_fts_doc_id_idx || (flags2 & DICT_TF2_FTS)); - /* Create the table. */ - trx_set_dict_operation(ctx->trx, TRX_DICT_OP_TABLE); - - if (dict_table_get_low(new_table_name)) { - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), - new_table_name); - goto new_clustered_failed; - } - /* The initial space id 0 may be overridden later if this table is going to be a file_per_table tablespace. */ ctx->new_table = dict_mem_table_create( @@ -4641,12 +5000,22 @@ prepare_inplace_alter_table_dict( field_type |= DATA_UNSIGNED; } + if (altered_table->versioned()) { + if (i == altered_table->s->row_start_field) { + field_type |= DATA_VERS_START; + } else if (i == + altered_table->s->row_end_field) { + field_type |= DATA_VERS_END; + } else if (!(field->flags + & VERS_UPDATE_UNVERSIONED_FLAG)) { + field_type |= DATA_VERSIONED; + } + } + if (dtype_is_string_type(col_type)) { charset_no = (ulint) field->charset()->number; if (charset_no > MAX_CHAR_COLL_NUM) { - dict_mem_table_free( - ctx->new_table); my_error(ER_WRONG_KEY_COLUMN, MYF(0), "InnoDB", field->field_name.str); goto new_clustered_failed; @@ -4678,6 +5047,7 @@ prepare_inplace_alter_table_dict( if (dict_col_name_is_reserved(field->field_name.str)) { dict_mem_table_free(ctx->new_table); + ctx->new_table = ctx->old_table; my_error(ER_WRONG_COLUMN_NAME, MYF(0), field->field_name.str); goto new_clustered_failed; @@ -4728,51 +5098,7 @@ prepare_inplace_alter_table_dict( ctx->new_table->fts->doc_col = fts_doc_id_col; } - error = row_create_table_for_mysql( - ctx->new_table, ctx->trx, mode, key_id); - - switch (error) { - dict_table_t* temp_table; - case DB_SUCCESS: - /* We need to bump up the table ref count and - before we can use it we need to open the - table. The new_table must be in the data - dictionary cache, because we are still holding - the dict_sys->mutex. */ - ut_ad(mutex_own(&dict_sys->mutex)); - temp_table = dict_table_open_on_name( - ctx->new_table->name.m_name, TRUE, FALSE, - DICT_ERR_IGNORE_NONE); - ut_a(ctx->new_table == temp_table); - /* n_ref_count must be 1, because purge cannot - be executing on this very table as we are - holding dict_operation_lock X-latch. */ - DBUG_ASSERT(ctx->new_table->get_ref_count() == 1); - break; - case DB_TABLESPACE_EXISTS: - my_error(ER_TABLESPACE_EXISTS, MYF(0), - new_table_name); - goto new_clustered_failed; - case DB_DUPLICATE_KEY: - my_error(HA_ERR_TABLE_EXIST, MYF(0), - altered_table->s->table_name.str); - goto new_clustered_failed; - case DB_UNSUPPORTED: - my_error(ER_UNSUPPORTED_EXTENSION, MYF(0), - ctx->new_table->name.m_name); - goto new_clustered_failed; - default: - my_error_innodb(error, table_name, flags); -new_clustered_failed: - DBUG_ASSERT(ctx->trx != ctx->prebuilt->trx); - trx_rollback_to_savepoint(ctx->trx, NULL); - - ut_ad(user_table->get_ref_count() == 1); - - online_retry_drop_indexes_with_trx( - user_table, ctx->trx); - goto err_exit; - } + dict_table_add_system_columns(ctx->new_table, ctx->heap); if (ha_alter_info->handler_flags & Alter_inplace_info::ADD_COLUMN) { @@ -4838,15 +5164,10 @@ new_clustered_failed: } } - /* Assign table_id, so that no table id of - fts_create_index_tables() will be written to the undo logs. */ - DBUG_ASSERT(ctx->new_table->id != 0); - ctx->trx->table_id = ctx->new_table->id; - - /* Create the indexes in SYS_INDEXES and load into dictionary. */ + ut_ad(new_clustered == ctx->need_rebuild()); + /* Create the index metadata. */ for (ulint a = 0; a < ctx->num_to_add_index; a++) { - if (index_defs[a].ind_type & DICT_VIRTUAL && ctx->num_to_drop_vcol > 0 && !new_clustered) { innodb_v_adjust_idx_col(ha_alter_info, old_table, @@ -4855,66 +5176,281 @@ new_clustered_failed: } ctx->add_index[a] = row_merge_create_index( - ctx->trx, ctx->new_table, &index_defs[a], add_v); + ctx->new_table, &index_defs[a], add_v); add_key_nums[a] = index_defs[a].key_number; - if (!ctx->add_index[a]) { - error = ctx->trx->error_state; - DBUG_ASSERT(error != DB_SUCCESS); - goto error_handling; - } - DBUG_ASSERT(ctx->add_index[a]->is_committed() == !!new_clustered); + } - if (ctx->add_index[a]->type & DICT_FTS) { - DBUG_ASSERT(num_fts_index); - DBUG_ASSERT(!fts_index); - DBUG_ASSERT(ctx->add_index[a]->type == DICT_FTS); - fts_index = ctx->add_index[a]; - } - - /* If only online ALTER TABLE operations have been - requested, allocate a modification log. If the table - will be locked anyway, the modification - log is unnecessary. When rebuilding the table - (new_clustered), we will allocate the log for the - clustered index of the old table, later. */ - if (new_clustered - || !ctx->online - || !user_table->is_readable() - || dict_table_is_discarded(user_table)) { - /* No need to allocate a modification log. */ - ut_ad(!ctx->add_index[a]->online_log); - } else if (ctx->add_index[a]->type & DICT_FTS) { - /* Fulltext indexes are not covered - by a modification log. */ - } else { - DBUG_EXECUTE_IF("innodb_OOM_prepare_inplace_alter", - error = DB_OUT_OF_MEMORY; - goto error_handling;); - rw_lock_x_lock(&ctx->add_index[a]->lock); + if (ctx->need_rebuild() && user_table->supports_instant()) { + if (!instant_alter_column_possible(ha_alter_info, old_table)) { + goto not_instant_add_column; + } + + for (uint i = ctx->old_table->n_cols - DATA_N_SYS_COLS; + i--; ) { + if (ctx->col_map[i] != i) { + goto not_instant_add_column; + } + } - bool ok = row_log_allocate(ctx->add_index[a], - NULL, true, NULL, NULL, - path); - rw_lock_x_unlock(&ctx->add_index[a]->lock); + DBUG_ASSERT(ctx->new_table->n_cols > ctx->old_table->n_cols); - if (!ok) { - error = DB_OUT_OF_MEMORY; - goto error_handling; + for (uint a = 0; a < ctx->num_to_add_index; a++) { + error = dict_index_add_to_cache_w_vcol( + ctx->new_table, ctx->add_index[a], add_v, + FIL_NULL, false); + ut_a(error == DB_SUCCESS); + } + DBUG_ASSERT(ha_alter_info->key_count + /* hidden GEN_CLUST_INDEX in InnoDB */ + + dict_index_is_auto_gen_clust( + dict_table_get_first_index(ctx->new_table)) + /* hidden FTS_DOC_ID_INDEX in InnoDB */ + + (ctx->old_table->fts_doc_id_index + && innobase_fts_check_doc_id_index_in_def( + altered_table->s->keys, + altered_table->key_info) + != FTS_EXIST_DOC_ID_INDEX) + == ctx->num_to_add_index); + ctx->num_to_add_index = 0; + ctx->add_index = NULL; + + uint i = 0; // index of stored columns ctx->new_table->cols[] + Field **af = altered_table->field; + + List_iterator_fast<Create_field> cf_it( + ha_alter_info->alter_info->create_list); + + while (const Create_field* new_field = cf_it++) { + DBUG_ASSERT(!new_field->field + || std::find(old_table->field, + old_table->field + + old_table->s->fields, + new_field->field) != + old_table->field + old_table->s->fields); + DBUG_ASSERT(new_field->field + || !strcmp(new_field->field_name.str, + (*af)->field_name.str)); + + if (!(*af)->stored_in_db()) { + af++; + continue; } + + dict_col_t* col = dict_table_get_nth_col( + ctx->new_table, i); + DBUG_ASSERT(!strcmp((*af)->field_name.str, + dict_table_get_col_name(ctx->new_table, + i))); + DBUG_ASSERT(!col->is_instant()); + + if (new_field->field) { + ut_d(const dict_col_t* old_col + = dict_table_get_nth_col(user_table, i)); + ut_d(const dict_index_t* index + = user_table->indexes.start); + DBUG_ASSERT(col->mtype == old_col->mtype); + DBUG_ASSERT(col->prtype == old_col->prtype); + DBUG_ASSERT(col->mbminlen + == old_col->mbminlen); + DBUG_ASSERT(col->mbmaxlen + == old_col->mbmaxlen); + DBUG_ASSERT(col->len >= old_col->len); + DBUG_ASSERT(old_col->is_instant() + == (dict_col_get_clust_pos( + old_col, index) + >= index->n_core_fields)); + } else if ((*af)->is_real_null()) { + /* DEFAULT NULL */ + col->def_val.len = UNIV_SQL_NULL; + } else { + switch ((*af)->type()) { + case MYSQL_TYPE_VARCHAR: + col->def_val.len = reinterpret_cast + <const Field_varstring*> + ((*af))->get_length(); + col->def_val.data = reinterpret_cast + <const Field_varstring*> + ((*af))->get_data(); + break; + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_LONG_BLOB: + col->def_val.len = reinterpret_cast + <const Field_blob*> + ((*af))->get_length(); + col->def_val.data = reinterpret_cast + <const Field_blob*> + ((*af))->get_ptr(); + break; + default: + dfield_t d; + dict_col_copy_type(col, &d.type); + ulint len = (*af)->pack_length(); + DBUG_ASSERT(len <= 8 + || d.type.mtype + != DATA_INT); + row_mysql_store_col_in_innobase_format( + &d, + d.type.mtype == DATA_INT + ? static_cast<byte*>( + mem_heap_alloc( + ctx->heap, + len)) + : NULL, + true, (*af)->ptr, len, + dict_table_is_comp( + user_table)); + col->def_val.len = d.len; + col->def_val.data = d.data; + } + } + + i++; + af++; } + + DBUG_ASSERT(af == altered_table->field + + altered_table->s->fields); + /* There might exist a hidden FTS_DOC_ID column for + FULLTEXT INDEX. If it exists, the columns should have + been implicitly added by ADD FULLTEXT INDEX together + with instant ADD COLUMN. (If a hidden FTS_DOC_ID pre-existed, + then the ctx->col_map[] check should have prevented + adding visible user columns after that.) */ + DBUG_ASSERT(DATA_N_SYS_COLS + i == ctx->new_table->n_cols + || (1 + DATA_N_SYS_COLS + i + == ctx->new_table->n_cols + && !strcmp(dict_table_get_col_name( + ctx->new_table, i), + FTS_DOC_ID_COL_NAME))); + + ctx->prepare_instant(); } - ut_ad(new_clustered == ctx->need_rebuild()); + if (ctx->need_rebuild()) { +not_instant_add_column: + DBUG_ASSERT(ctx->need_rebuild()); + DBUG_ASSERT(!ctx->is_instant()); + DBUG_ASSERT(num_fts_index <= 1); + DBUG_ASSERT(!ctx->online || num_fts_index == 0); + DBUG_ASSERT(!ctx->online + || ctx->add_autoinc == ULINT_UNDEFINED); + DBUG_ASSERT(!ctx->online + || !innobase_need_rebuild(ha_alter_info, old_table) + || !innobase_fulltext_exist(altered_table)); - DBUG_EXECUTE_IF("innodb_OOM_prepare_inplace_alter", - error = DB_OUT_OF_MEMORY; - goto error_handling;); + uint32_t key_id = FIL_DEFAULT_ENCRYPTION_KEY; + fil_encryption_t mode = FIL_ENCRYPTION_DEFAULT; + + if (fil_space_t* s = fil_space_acquire(user_table->space)) { + if (const fil_space_crypt_t* c = s->crypt_data) { + key_id = c->key_id; + mode = c->encryption; + } + fil_space_release(s); + } + + if (ha_alter_info->handler_flags + & Alter_inplace_info::CHANGE_CREATE_OPTION) { + const ha_table_option_struct& alt_opt= + *ha_alter_info->create_info->option_struct; + const ha_table_option_struct& opt= + *old_table->s->option_struct; + if (alt_opt.encryption != opt.encryption + || alt_opt.encryption_key_id + != opt.encryption_key_id) { + key_id = uint32_t(alt_opt.encryption_key_id); + mode = fil_encryption_t(alt_opt.encryption); + } + } + + if (dict_table_get_low(ctx->new_table->name.m_name)) { + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), + ctx->new_table->name.m_name); + goto new_clustered_failed; + } + + /* Create the table. */ + trx_set_dict_operation(ctx->trx, TRX_DICT_OP_TABLE); + + error = row_create_table_for_mysql( + ctx->new_table, ctx->trx, mode, key_id); + + switch (error) { + dict_table_t* temp_table; + case DB_SUCCESS: + /* We need to bump up the table ref count and + before we can use it we need to open the + table. The new_table must be in the data + dictionary cache, because we are still holding + the dict_sys->mutex. */ + ut_ad(mutex_own(&dict_sys->mutex)); + temp_table = dict_table_open_on_name( + ctx->new_table->name.m_name, TRUE, FALSE, + DICT_ERR_IGNORE_NONE); + ut_a(ctx->new_table == temp_table); + /* n_ref_count must be 1, because purge cannot + be executing on this very table as we are + holding dict_operation_lock X-latch. */ + DBUG_ASSERT(ctx->new_table->get_ref_count() == 1); + DBUG_ASSERT(ctx->new_table->id != 0); + DBUG_ASSERT(ctx->new_table->id == ctx->trx->table_id); + break; + case DB_TABLESPACE_EXISTS: + my_error(ER_TABLESPACE_EXISTS, MYF(0), + ctx->new_table->name.m_name); + goto new_table_failed; + case DB_DUPLICATE_KEY: + my_error(HA_ERR_TABLE_EXIST, MYF(0), + altered_table->s->table_name.str); + goto new_table_failed; + case DB_UNSUPPORTED: + my_error(ER_UNSUPPORTED_EXTENSION, MYF(0), + ctx->new_table->name.m_name); + goto new_table_failed; + default: + my_error_innodb(error, table_name, flags); +new_table_failed: + DBUG_ASSERT(ctx->trx != ctx->prebuilt->trx); + goto new_clustered_failed; + } + + for (ulint a = 0; a < ctx->num_to_add_index; a++) { + dict_index_t*& index = ctx->add_index[a]; + const bool has_new_v_col = index->has_new_v_col; + error = create_index_dict(ctx->trx, index, add_v); + if (error != DB_SUCCESS) { + while (++a < ctx->num_to_add_index) { + dict_mem_index_free(ctx->add_index[a]); + } + goto error_handling; + } + + index = dict_table_get_index_on_name( + ctx->new_table, index_defs[a].name, true); + ut_a(index); + + index->parser = index_defs[a].parser; + index->has_new_v_col = has_new_v_col; + /* Note the id of the transaction that created this + index, we use it to restrict readers from accessing + this index, to ensure read consistency. */ + ut_ad(index->trx_id == ctx->trx->id); + + if (index->type & DICT_FTS) { + DBUG_ASSERT(num_fts_index == 1); + DBUG_ASSERT(!fts_index); + DBUG_ASSERT(index->type == DICT_FTS); + fts_index = ctx->add_index[a]; + } + } - if (new_clustered) { dict_index_t* clust_index = dict_table_get_first_index( user_table); dict_index_t* new_clust_index = dict_table_get_first_index( @@ -4925,6 +5461,11 @@ new_clustered_failed: DBUG_EXECUTE_IF("innodb_alter_table_pk_assert_no_sort", DBUG_ASSERT(ctx->skip_pk_sort);); + ut_ad(!new_clust_index->is_instant()); + /* row_merge_build_index() depends on the correct value */ + ut_ad(new_clust_index->n_core_null_bytes + == UT_BITS_IN_BYTES(new_clust_index->n_nullable)); + DBUG_ASSERT(!ctx->new_table->persistent_autoinc); if (const Field* ai = altered_table->found_next_number_field) { const unsigned col_no = innodb_col_no(ai); @@ -4946,6 +5487,7 @@ new_clustered_failed: /* Allocate a log for online table rebuild. */ rw_lock_x_lock(&clust_index->lock); bool ok = row_log_allocate( + ctx->prebuilt->trx, clust_index, ctx->new_table, !(ha_alter_info->handler_flags & Alter_inplace_info::ADD_PK_INDEX), @@ -4957,12 +5499,73 @@ new_clustered_failed: goto error_handling; } } + } else if (ctx->num_to_add_index) { + ut_ad(!ctx->is_instant()); + ctx->trx->table_id = user_table->id; + + for (ulint a = 0; a < ctx->num_to_add_index; a++) { + dict_index_t*& index = ctx->add_index[a]; + const bool has_new_v_col = index->has_new_v_col; + error = create_index_dict(ctx->trx, index, add_v); + if (error != DB_SUCCESS) { +error_handling_drop_uncached: + while (++a < ctx->num_to_add_index) { + dict_mem_index_free(ctx->add_index[a]); + } + goto error_handling; + } + + index = dict_table_get_index_on_name( + ctx->new_table, index_defs[a].name, false); + ut_a(index); + + index->parser = index_defs[a].parser; + index->has_new_v_col = has_new_v_col; + /* Note the id of the transaction that created this + index, we use it to restrict readers from accessing + this index, to ensure read consistency. */ + ut_ad(index->trx_id == ctx->trx->id); + + /* If ADD INDEX with LOCK=NONE has been + requested, allocate a modification log. */ + if (index->type & DICT_FTS) { + DBUG_ASSERT(num_fts_index == 1); + DBUG_ASSERT(!fts_index); + DBUG_ASSERT(index->type == DICT_FTS); + fts_index = ctx->add_index[a]; + /* Fulltext indexes are not covered + by a modification log. */ + } else if (!ctx->online + || !user_table->is_readable() + || dict_table_is_discarded(user_table)) { + /* No need to allocate a modification log. */ + DBUG_ASSERT(!index->online_log); + } else { + DBUG_EXECUTE_IF( + "innodb_OOM_prepare_inplace_alter", + error = DB_OUT_OF_MEMORY; + goto error_handling_drop_uncached;); + rw_lock_x_lock(&ctx->add_index[a]->lock); + + bool ok = row_log_allocate( + ctx->prebuilt->trx, + index, + NULL, true, NULL, NULL, + path); + rw_lock_x_unlock(&index->lock); + + if (!ok) { + error = DB_OUT_OF_MEMORY; + goto error_handling_drop_uncached; + } + } + } } - if (ctx->online) { + if (ctx->online && ctx->num_to_add_index) { /* Assign a consistent read view for row_merge_read_clustered_index(). */ - trx_assign_read_view(ctx->prebuilt->trx); + ctx->prebuilt->trx->read_view.open(ctx->prebuilt->trx); } if (fts_index) { @@ -4986,8 +5589,8 @@ op_ok: ut_ad(rw_lock_own(dict_operation_lock, RW_LOCK_X)); DICT_TF2_FLAG_SET(ctx->new_table, DICT_TF2_FTS); - if (new_clustered) { - /* For !new_clustered, this will be set at + if (ctx->need_rebuild()) { + /* For !ctx->need_rebuild(), this will be set at commit_cache_norebuild(). */ ctx->new_table->fts_doc_id_index = dict_table_get_index_on_name( @@ -5086,6 +5689,11 @@ error_handling: error_handled: ctx->prebuilt->trx->error_info = NULL; + + if (!ctx->trx) { + goto err_exit; + } + ctx->trx->error_state = DB_SUCCESS; if (!dict_locked) { @@ -5147,9 +5755,11 @@ err_exit: } #endif /* UNIV_DEBUG */ - row_mysql_unlock_data_dictionary(ctx->trx); + if (ctx->trx) { + row_mysql_unlock_data_dictionary(ctx->trx); - trx_free_for_mysql(ctx->trx); + trx_free_for_mysql(ctx->trx); + } trx_commit_for_mysql(ctx->prebuilt->trx); delete ctx; @@ -5890,7 +6500,7 @@ found_fk: = ha_alter_info->index_drop_buffer[i]; dict_index_t* index = dict_table_get_index_on_name( - indexed_table, key->name); + indexed_table, key->name.str); if (!index) { push_warning_printf( @@ -5932,7 +6542,7 @@ found_fk: if (!my_strcasecmp( system_charset_info, FTS_DOC_ID_INDEX_NAME, - table->key_info[i].name)) { + table->key_info[i].name.str)) { /* The index exists in the MySQL data dictionary. Do not drop it, even though it is no longer needed @@ -6105,7 +6715,7 @@ err_exit: if (!(ha_alter_info->handler_flags & INNOBASE_ALTER_DATA) || ((ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE) == Alter_inplace_info::CHANGE_CREATE_OPTION - && !innobase_need_rebuild(ha_alter_info, table))) { + && !create_option_need_rebuild(ha_alter_info, table))) { if (heap) { ha_alter_info->handler_ctx @@ -6344,7 +6954,7 @@ get_error_key_name( } else if (ha_alter_info->key_count == 0) { return(dict_table_get_first_index(table)->name); } else { - return(ha_alter_info->key_info_buffer[error_key_num].name); + return(ha_alter_info->key_info_buffer[error_key_num].name.str); } } @@ -6390,7 +7000,7 @@ ok_exit: if ((ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE) == Alter_inplace_info::CHANGE_CREATE_OPTION - && !innobase_need_rebuild(ha_alter_info, table)) { + && !create_option_need_rebuild(ha_alter_info, table)) { goto ok_exit; } @@ -6402,6 +7012,8 @@ ok_exit: DBUG_ASSERT(ctx->trx); DBUG_ASSERT(ctx->prebuilt == m_prebuilt); + if (ctx->is_instant()) goto ok_exit; + dict_index_t* pk = dict_table_get_first_index(m_prebuilt->table); ut_ad(pk != NULL); @@ -6483,7 +7095,8 @@ ok_exit: ctx->add_index, ctx->add_key_numbers, ctx->num_to_add_index, altered_table, ctx->add_cols, ctx->col_map, ctx->add_autoinc, ctx->sequence, ctx->skip_pk_sort, - ctx->m_stage, add_v, eval_table); + ctx->m_stage, add_v, eval_table, + ha_alter_info->handler_flags & Alter_inplace_info::ALTER_DROP_HISTORICAL); #ifndef DBUG_OFF oom: @@ -8097,28 +8710,31 @@ commit_try_norebuild( DBUG_RETURN(true); } + if (innobase_add_instant_try(ha_alter_info, ctx, altered_table, + old_table, trx)) { + DBUG_RETURN(true); + } + DBUG_RETURN(false); } /** Commit the changes to the data dictionary cache after a successful commit_try_norebuild() call. -@param ctx In-place ALTER TABLE context +@param ha_alter_info algorithm=inplace context +@param ctx In-place ALTER TABLE context for the current partition @param table the TABLE before the ALTER -@param trx Data dictionary transaction object -(will be started and committed) -@return whether all replacements were found for dropped indexes */ -inline MY_ATTRIBUTE((nonnull, warn_unused_result)) -bool +@param trx Data dictionary transaction +(will be started and committed, for DROP INDEX) */ +inline MY_ATTRIBUTE((nonnull)) +void commit_cache_norebuild( /*===================*/ + Alter_inplace_info* ha_alter_info, ha_innobase_inplace_ctx*ctx, const TABLE* table, trx_t* trx) { DBUG_ENTER("commit_cache_norebuild"); - - bool found = true; - DBUG_ASSERT(!ctx->need_rebuild()); col_set drop_list; @@ -8174,7 +8790,7 @@ commit_cache_norebuild( if (!dict_foreign_replace_index( index->table, ctx->col_names, index)) { - found = false; + ut_a(!ctx->prebuilt->trx->check_foreigns); } /* Mark the index dropped @@ -8206,6 +8822,15 @@ commit_cache_norebuild( trx_commit_for_mysql(trx); } + if (!ctx->is_instant()) { + innobase_rename_or_enlarge_columns_cache( + ha_alter_info, table, ctx->new_table); + } + +#ifdef MYSQL_RENAME_INDEX + rename_indexes_in_cache(ctx, ha_alter_info); +#endif + ctx->new_table->fts_doc_id_index = ctx->new_table->fts ? dict_table_get_index_on_name( @@ -8213,8 +8838,7 @@ commit_cache_norebuild( : NULL; DBUG_ASSERT((ctx->new_table->fts == NULL) == (ctx->new_table->fts_doc_id_index == NULL)); - - DBUG_RETURN(found); + DBUG_VOID_RETURN; } /** Adjust the persistent statistics after non-rebuilding ALTER TABLE. @@ -8272,7 +8896,7 @@ alter_stats_norebuild( char errstr[1024]; if (dict_stats_drop_index( - ctx->new_table->name.m_name, key->name, + ctx->new_table->name.m_name, key->name.str, errstr, sizeof errstr) != DB_SUCCESS) { push_warning(thd, Sql_condition::WARN_LEVEL_WARN, @@ -8670,9 +9294,16 @@ ha_innobase::commit_inplace_alter_table( } /* Commit or roll back the changes to the data dictionary. */ + DEBUG_SYNC(m_user_thd, "innodb_alter_inplace_before_commit"); if (fail) { trx_rollback_for_mysql(trx); + for (inplace_alter_handler_ctx** pctx = ctx_array; + *pctx; pctx++) { + ha_innobase_inplace_ctx* ctx + = static_cast<ha_innobase_inplace_ctx*>(*pctx); + ctx->rollback_instant(); + } } else if (!new_clustered) { trx_commit_for_mysql(trx); } else { @@ -8849,19 +9480,9 @@ foreign_fail: "InnoDB: Could not add foreign" " key constraints."); } else { - if (!commit_cache_norebuild( - ctx, table, trx)) { - ut_a(!m_prebuilt->trx->check_foreigns); - } - - innobase_rename_or_enlarge_columns_cache( - ha_alter_info, table, - ctx->new_table); -#ifdef MYSQL_RENAME_INDEX - rename_indexes_in_cache(ctx, ha_alter_info); -#endif + commit_cache_norebuild(ha_alter_info, ctx, + table, trx); } - } dict_mem_table_free_foreign_vcol_set(ctx->new_table); |