From 6402ca7870895b256ef1f25a1f0530ed97392686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Wed, 1 Nov 2017 22:26:25 +0200 Subject: [PATCH] MDEV-14016 Allow instant ADD COLUMN, ADD INDEX, LOCK=NONE Ideally, we would move some code from ha_innobase::prepare_inplace_alter_table() to ha_innobase::check_if_supported_inplace_alter(), but the API does not really allow us to return errors; it can only inform which forms of ALGORITHM and LOCK are allowed. So, we have to duplicate some logic between the "check" and "prepare" phases. We do the duplication by calling common functions. instant_alter_column_possible(): Check if instant column operation is possible. Invoked from both ha_innobase::check_if_supported_inplace_alter() and prepare_inplace_alter_table_dict(). ha_innobase::check_if_supported_inplace_alter(): Before refusing certain operations if FULLTEXT INDEX exist, check if instant ALTER TABLE is possible and return early if it is the case. prepare_inplace_alter_table_dict(): Before checking the limitations on FULLTEXT INDEX, check if instant ALTER TABLE is possible, and suppress the checks if it is the case. If instant ADD COLUMN is used when the table already contains FULLTEXT INDEX, do account for a hidden FTS_DOC_ID_INDEX in a debug assertion. --- mysql-test/suite/innodb/r/innodb-alter.result | 21 ++ mysql-test/suite/innodb/t/innodb-alter.test | 27 ++ .../innodb_gis/r/alter_spatial_index.result | 53 ++- .../innodb_gis/t/alter_spatial_index.test | 25 +- storage/innobase/handler/handler0alter.cc | 357 ++++++++++-------- 5 files changed, 303 insertions(+), 180 deletions(-) diff --git a/mysql-test/suite/innodb/r/innodb-alter.result b/mysql-test/suite/innodb/r/innodb-alter.result index 8bf66bb8914..de69845dddb 100644 --- a/mysql-test/suite/innodb/r/innodb-alter.result +++ b/mysql-test/suite/innodb/r/innodb-alter.result @@ -440,6 +440,27 @@ tt CREATE TABLE `tt` ( PRIMARY KEY (`pk`), FULLTEXT KEY `ct` (`ct`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 +ALTER TABLE tt ADD COLUMN c CHAR(1) NOT NULL FIRST, LOCK=NONE; +ERROR 0A000: LOCK=NONE is not supported. Reason: InnoDB presently supports one FULLTEXT index creation at a time. Try LOCK=SHARED +ALTER TABLE tt ADD COLUMN c CHAR(1) NOT NULL, LOCK=NONE; +ERROR 0A000: LOCK=NONE is not supported. Reason: InnoDB presently supports one FULLTEXT index creation at a time. Try LOCK=SHARED +CREATE TABLE tu ( +pk INT PRIMARY KEY, FTS_DOC_ID BIGINT UNSIGNED NOT NULL, t TEXT, +FULLTEXT INDEX(t) +) ENGINE=InnoDB; +ALTER TABLE tu ADD COLUMN c CHAR(1) NOT NULL FIRST, LOCK=NONE; +ERROR 0A000: LOCK=NONE is not supported. Reason: InnoDB presently supports one FULLTEXT index creation at a time. Try LOCK=SHARED +ALTER TABLE tu ADD COLUMN c CHAR(1) NOT NULL, LOCK=NONE; +DROP TABLE tu; +CREATE TABLE tv ( +pk INT PRIMARY KEY, FTS_DOC_ID BIGINT UNSIGNED NOT NULL, t TEXT, +UNIQUE INDEX FTS_DOC_ID_INDEX(FTS_DOC_ID), +FULLTEXT INDEX(t) +) ENGINE=InnoDB; +ALTER TABLE tv ADD COLUMN c CHAR(1) NOT NULL FIRST, LOCK=NONE; +ERROR 0A000: LOCK=NONE is not supported. Reason: InnoDB presently supports one FULLTEXT index creation at a time. Try LOCK=SHARED +ALTER TABLE tv ADD COLUMN c CHAR(1) NOT NULL, LOCK=NONE; +DROP TABLE tv; ALTER TABLE t1o CHANGE c1 dB_row_Id INT, ALGORITHM=COPY; ERROR 42000: Incorrect column name 'dB_row_Id' ALTER TABLE t1o CHANGE c1 dB_row_Id INT, ALGORITHM=INPLACE; diff --git a/mysql-test/suite/innodb/t/innodb-alter.test b/mysql-test/suite/innodb/t/innodb-alter.test index 9d84e95b88d..7a7b4f3a845 100644 --- a/mysql-test/suite/innodb/t/innodb-alter.test +++ b/mysql-test/suite/innodb/t/innodb-alter.test @@ -193,6 +193,33 @@ ALGORITHM=INPLACE, LOCK=SHARED; -- source suite/innodb/include/innodb_dict.inc SHOW CREATE TABLE tt; +# Non-instant ADD COLUMN would require the table to be rebuilt. +--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON +ALTER TABLE tt ADD COLUMN c CHAR(1) NOT NULL FIRST, LOCK=NONE; +# This is still non-instant ADD COLUMN, because FTS_DOC_ID is hidden. +--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON +ALTER TABLE tt ADD COLUMN c CHAR(1) NOT NULL, LOCK=NONE; + +CREATE TABLE tu ( + pk INT PRIMARY KEY, FTS_DOC_ID BIGINT UNSIGNED NOT NULL, t TEXT, + FULLTEXT INDEX(t) +) ENGINE=InnoDB; +--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON +ALTER TABLE tu ADD COLUMN c CHAR(1) NOT NULL FIRST, LOCK=NONE; +# Instant ADD COLUMN (adding after the visible FTS_DOC_ID) +ALTER TABLE tu ADD COLUMN c CHAR(1) NOT NULL, LOCK=NONE; +DROP TABLE tu; + +CREATE TABLE tv ( + pk INT PRIMARY KEY, FTS_DOC_ID BIGINT UNSIGNED NOT NULL, t TEXT, + UNIQUE INDEX FTS_DOC_ID_INDEX(FTS_DOC_ID), + FULLTEXT INDEX(t) +) ENGINE=InnoDB; +--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON +ALTER TABLE tv ADD COLUMN c CHAR(1) NOT NULL FIRST, LOCK=NONE; +# Instant ADD COLUMN (adding after the visible FTS_DOC_ID) +ALTER TABLE tv ADD COLUMN c CHAR(1) NOT NULL, LOCK=NONE; +DROP TABLE tv; # DB_ROW_ID, DB_TRX_ID, DB_ROLL_PTR are reserved InnoDB system column names. --error ER_WRONG_COLUMN_NAME diff --git a/mysql-test/suite/innodb_gis/r/alter_spatial_index.result b/mysql-test/suite/innodb_gis/r/alter_spatial_index.result index b4859c799d1..7caa5f6829c 100644 --- a/mysql-test/suite/innodb_gis/r/alter_spatial_index.result +++ b/mysql-test/suite/innodb_gis/r/alter_spatial_index.result @@ -483,16 +483,55 @@ info: Records: 0 Duplicates: 0 Warnings: 0 ALTER TABLE tab MODIFY COLUMN c2 GEOMETRY NOT NULL; affected rows: 0 info: Records: 0 Duplicates: 0 Warnings: 0 -ALTER TABLE tab add COLUMN c8 POINT NOT NULL AFTER c5, ALGORITHM = INPLACE, LOCK=NONE; +ALTER TABLE tab MODIFY COLUMN c3 POLYGON NOT NULL; +affected rows: 10 +info: Records: 10 Duplicates: 0 Warnings: 0 +ALTER TABLE tab add COLUMN c7 POINT NOT NULL; +affected rows: 0 +info: Records: 0 Duplicates: 0 Warnings: 0 +ALTER TABLE tab add COLUMN c8 POINT NOT NULL, ALGORITHM = INPLACE, LOCK=NONE; +SELECT HEX(c8) FROM tab; +HEX(c8) + + + + + + + + + + +BEGIN; +INSERT INTO tab SELECT 0,c2,c3,c4,c5, +ST_GeomFromText('POINT(67 89)'),ST_GeomFromText('POINT(67 89)') +FROM tab LIMIT 1; +SELECT HEX(c8) FROM tab; +HEX(c8) +0000000001010000000000000000C050400000000000405640 + + + + + + + + + + +ROLLBACK; +ALTER TABLE tab add COLUMN c9 POINT NOT NULL AFTER c5, ALGORITHM = INPLACE, LOCK=NONE; ERROR 0A000: LOCK=NONE is not supported. Reason: Do not support online operation on table with GIS index. Try LOCK=SHARED SHOW CREATE TABLE tab; Table Create Table tab CREATE TABLE `tab` ( `c1` int(11) NOT NULL, `c2` geometry NOT NULL, - `c3` linestring NOT NULL, + `c3` polygon NOT NULL, `c4` polygon NOT NULL, `c5` geometry NOT NULL, + `c7` point NOT NULL, + `c8` point NOT NULL, PRIMARY KEY (`c1`), SPATIAL KEY `idx2` (`c2`), SPATIAL KEY `idx3` (`c3`), @@ -525,9 +564,11 @@ Table Create Table tab CREATE TABLE `tab` ( `c1` int(11) NOT NULL, `c2` geometry NOT NULL, - `c3` linestring NOT NULL, + `c3` polygon NOT NULL, `c4` geometry NOT NULL, `c5` geometry NOT NULL, + `c7` point NOT NULL, + `c8` point NOT NULL, PRIMARY KEY (`c1`), SPATIAL KEY `idx2` (`c2`), SPATIAL KEY `idx3` (`c3`), @@ -571,8 +612,8 @@ ALTER TABLE tab MODIFY COLUMN c2 POINT NOT NULL; affected rows: 8 info: Records: 8 Duplicates: 0 Warnings: 0 ALTER TABLE tab MODIFY COLUMN c3 LINESTRING NOT NULL; -affected rows: 0 -info: Records: 0 Duplicates: 0 Warnings: 0 +affected rows: 8 +info: Records: 8 Duplicates: 0 Warnings: 0 ALTER TABLE tab MODIFY COLUMN c4 POLYGON NOT NULL; affected rows: 8 info: Records: 8 Duplicates: 0 Warnings: 0 @@ -584,6 +625,8 @@ tab CREATE TABLE `tab` ( `c3` linestring NOT NULL, `c4` polygon NOT NULL, `c5` geometry NOT NULL, + `c7` point NOT NULL, + `c8` point NOT NULL, PRIMARY KEY (`c1`), SPATIAL KEY `idx2` (`c2`), SPATIAL KEY `idx3` (`c3`), diff --git a/mysql-test/suite/innodb_gis/t/alter_spatial_index.test b/mysql-test/suite/innodb_gis/t/alter_spatial_index.test index d0a5dc584c5..653e250017a 100644 --- a/mysql-test/suite/innodb_gis/t/alter_spatial_index.test +++ b/mysql-test/suite/innodb_gis/t/alter_spatial_index.test @@ -476,25 +476,24 @@ ALTER TABLE tab ADD INDEX idx6(c4(10)) USING BTREE; ALTER TABLE tab MODIFY COLUMN c2 GEOMETRY NOT NULL; -# --error ER_CANT_CREATE_GEOMETRY_OBJECT -# ALTER TABLE tab MODIFY COLUMN c3 POLYGON NOT NULL; +ALTER TABLE tab MODIFY COLUMN c3 POLYGON NOT NULL; -# --error ER_INVALID_USE_OF_NULL -# ALTER TABLE tab add COLUMN c7 POINT NOT NULL; +ALTER TABLE tab add COLUMN c7 POINT NOT NULL; +--disable_info # instant add, supported -#ALTER TABLE tab add COLUMN c8 POINT NOT NULL, ALGORITHM = INPLACE, LOCK=NONE; -#SELECT HEX(c8) FROM tab; -#BEGIN; -#INSERT INTO tab SELECT 0,c2,c3,c4,c5,ST_GeomFromText('POINT(67 89)') -#FROM tab LIMIT 1; -#SELECT HEX(c8) FROM tab; -#ROLLBACK; +ALTER TABLE tab add COLUMN c8 POINT NOT NULL, ALGORITHM = INPLACE, LOCK=NONE; +SELECT HEX(c8) FROM tab; +BEGIN; +INSERT INTO tab SELECT 0,c2,c3,c4,c5, +ST_GeomFromText('POINT(67 89)'),ST_GeomFromText('POINT(67 89)') +FROM tab LIMIT 1; +SELECT HEX(c8) FROM tab; +ROLLBACK; # not instant, not supported --error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON -ALTER TABLE tab add COLUMN c8 POINT NOT NULL AFTER c5, ALGORITHM = INPLACE, LOCK=NONE; ---disable_info +ALTER TABLE tab add COLUMN c9 POINT NOT NULL AFTER c5, ALGORITHM = INPLACE, LOCK=NONE; SHOW CREATE TABLE tab; diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index b5676382e26..7b9980c22bb 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -602,6 +602,49 @@ 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) +{ + 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 @@ -654,11 +697,6 @@ ha_innobase::check_if_supported_inplace_alter( update_thd(); - // FIXME: Construct ha_innobase_inplace_ctx here and determine - // if instant ALTER TABLE is possible. If yes, we will be able to - // allow ADD COLUMN even if SPATIAL INDEX, FULLTEXT INDEX or - // virtual columns exist, also together with adding virtual columns. - if (ha_alter_info->handler_flags & ~(INNOBASE_INPLACE_IGNORE | INNOBASE_ALTER_NOREBUILD @@ -973,6 +1011,122 @@ ha_innobase::check_if_supported_inplace_alter( m_prebuilt->trx->will_lock++; + /* 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 + || (ha_alter_info->handler_flags + & Alter_inplace_info::ADD_COLUMN)); + + if (const Field* f = cf->field) { + /* This could be changing an existing column + from NULL to NOT NULL. */ + switch ((*af)->type()) { + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: + /* Inserting NULL into a TIMESTAMP column + would cause the DEFAULT value to be + replaced. Ensure that the DEFAULT + expression is not changing during + ALTER TABLE. */ + if (!f->real_maybe_null() + || (*af)->real_maybe_null()) { + /* The column was NOT NULL, or it + will allow NULL after ALTER TABLE. */ + goto next_column; + } + + if (!(*af)->default_value + && (*af)->is_real_null()) { + /* No DEFAULT value is + specified. We can report + errors for any NULL values for + the TIMESTAMP. + + FIXME: Allow any DEFAULT + expression whose value does + not change during ALTER TABLE. + This would require a fix in + row_merge_read_clustered_index() + to try to replace the DEFAULT + value before reporting + DB_INVALID_NULL. */ + goto next_column; + } + break; + default: + /* For any other data type, NULL + values are not converted. + (An AUTO_INCREMENT attribute cannot + be introduced to a column with + ALGORITHM=INPLACE.) */ + ut_ad((MTYP_TYPENR((*af)->unireg_check) + == Field::NEXT_NUMBER) + == (MTYP_TYPENR(f->unireg_check) + == Field::NEXT_NUMBER)); + goto next_column; + } + + ha_alter_info->unsupported_reason + = innobase_get_err_msg( + ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL); + } 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(), + are evaluated from a timestamp that is assigned + at the start of the statement. Session + functions, such as USER(), always evaluate the + same within a statement.) */ + + /* Compute the DEFAULT values of non-constant columns + (VCOL_SESSION_FUNC | VCOL_TIME_FUNC). */ + switch ((*af)->set_default()) { + case 0: /* OK */ + case 3: /* DATETIME to TIME or DATE conversion */ + goto next_column; + case -1: /* OOM, or GEOMETRY type mismatch */ + case 1: /* A number adjusted to the min/max value */ + case 2: /* String truncation, or conversion problem */ + break; + } + } + + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + +next_column: + 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. */ @@ -1037,102 +1191,6 @@ ha_innobase::check_if_supported_inplace_alter( } } - /* 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; - - while (Create_field* cf = cf_it++) { - DBUG_ASSERT(cf->field - || (ha_alter_info->handler_flags - & Alter_inplace_info::ADD_COLUMN)); - - if (const Field* f = cf->field) { - /* This could be changing an existing column - from NULL to NOT NULL. */ - switch ((*af)->type()) { - case MYSQL_TYPE_TIMESTAMP: - case MYSQL_TYPE_TIMESTAMP2: - /* Inserting NULL into a TIMESTAMP column - would cause the DEFAULT value to be - replaced. Ensure that the DEFAULT - expression is not changing during - ALTER TABLE. */ - if (!f->real_maybe_null() - || (*af)->real_maybe_null()) { - /* The column was NOT NULL, or it - will allow NULL after ALTER TABLE. */ - goto next_column; - } - - if (!(*af)->default_value - && (*af)->is_real_null()) { - /* No DEFAULT value is - specified. We can report - errors for any NULL values for - the TIMESTAMP. - - FIXME: Allow any DEFAULT - expression whose value does - not change during ALTER TABLE. - This would require a fix in - row_merge_read_clustered_index() - to try to replace the DEFAULT - value before reporting - DB_INVALID_NULL. */ - goto next_column; - } - break; - default: - /* For any other data type, NULL - values are not converted. - (An AUTO_INCREMENT attribute cannot - be introduced to a column with - ALGORITHM=INPLACE.) */ - ut_ad((MTYP_TYPENR((*af)->unireg_check) - == Field::NEXT_NUMBER) - == (MTYP_TYPENR(f->unireg_check) - == Field::NEXT_NUMBER)); - goto next_column; - } - - ha_alter_info->unsupported_reason - = innobase_get_err_msg( - ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL); - } else if (!(*af)->default_value - || !((*af)->default_value->flags - & ~(VCOL_SESSION_FUNC | VCOL_TIME_FUNC))) { - /* The added NOT NULL column lacks a DEFAULT value, - or the DEFAULT is the same for all rows. - (Time functions, such as CURRENT_TIMESTAMP(), - are evaluated from a timestamp that is assigned - at the start of the statement. Session - functions, such as USER(), always evaluate the - same within a statement.) */ - - /* Compute the DEFAULT values of non-constant columns - (VCOL_SESSION_FUNC | VCOL_TIME_FUNC). */ - switch ((*af)->set_default()) { - case 0: /* OK */ - case 3: /* DATETIME to TIME or DATE conversion */ - goto next_column; - case -1: /* OOM, or GEOMETRY type mismatch */ - case 1: /* A number adjusted to the min/max value */ - case 2: /* String truncation, or conversion problem */ - break; - } - } - - DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); - -next_column: - af++; - } - - cf_it.rewind(); - DBUG_RETURN(online ? HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE : HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE); @@ -4711,29 +4769,6 @@ prepare_inplace_alter_table_dict( 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; - } - /* The primary index would be rebuilt if a FTS Doc ID column is to be added, and the primary index definition is just copied from old table and stored in indexdefs[0] */ @@ -5077,22 +5112,8 @@ new_clustered_failed: == !!new_clustered); } - if (ctx->need_rebuild() && ctx->new_table->supports_instant()) { - if (~ha_alter_info->handler_flags - & Alter_inplace_info::ADD_STORED_BASE_COLUMN) { - goto not_instant_add_column; - } - - if (ha_alter_info->handler_flags - & (INNOBASE_ALTER_REBUILD - & ~Alter_inplace_info::ADD_STORED_BASE_COLUMN - & ~Alter_inplace_info::CHANGE_CREATE_OPTION)) { - goto not_instant_add_column; - } - - if ((ha_alter_info->handler_flags & ~INNOBASE_INPLACE_IGNORE) - == Alter_inplace_info::CHANGE_CREATE_OPTION - && create_option_need_rebuild(ha_alter_info, old_table)) { + if (ctx->need_rebuild() && user_table->supports_instant()) { + if (!instant_alter_column_possible(ha_alter_info, old_table)) { goto not_instant_add_column; } @@ -5105,28 +5126,6 @@ new_clustered_failed: DBUG_ASSERT(ctx->new_table->n_cols > ctx->old_table->n_cols); - if (ha_alter_info->handler_flags & INNOBASE_ONLINE_CREATE) { - /* 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 ADD INDEX on pre-existing columns. */ - goto not_instant_add_column; - } - 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, @@ -5134,8 +5133,15 @@ new_clustered_failed: 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; @@ -5253,6 +5259,33 @@ new_clustered_failed: ctx->prepare_instant(); } + if (!ctx->is_instant()) { + 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; + } + } + if (ctx->need_rebuild()) { not_instant_add_column: uint32_t key_id = FIL_DEFAULT_ENCRYPTION_KEY;