in storage/innobase/handler/handler0alter.cc [2558:3197]
static MY_ATTRIBUTE((warn_unused_result, nonnull(1,2,3,4)))
bool
prepare_inplace_alter_table_dict(
/*=============================*/
Alter_inplace_info* ha_alter_info,
const TABLE* altered_table,
const TABLE* old_table,
const char* table_name,
ulint flags,
ulint flags2,
ulint fts_doc_id_col,
bool add_fts_doc_id,
bool add_fts_doc_id_idx)
{
bool dict_locked = false;
ulint* add_key_nums; /* MySQL key numbers */
index_def_t* index_defs; /* index definitions */
dict_table_t* user_table;
dict_index_t* fts_index = NULL;
ulint new_clustered = 0;
dberr_t error;
ulint num_fts_index;
ha_innobase_inplace_ctx*ctx;
DBUG_ENTER("prepare_inplace_alter_table_dict");
ctx = static_cast<ha_innobase_inplace_ctx*>
(ha_alter_info->handler_ctx);
DBUG_ASSERT((ctx->add_autoinc != ULINT_UNDEFINED)
== (ctx->sequence.m_max_value > 0));
DBUG_ASSERT(!ctx->num_to_drop_index == !ctx->drop_index);
DBUG_ASSERT(!ctx->num_to_drop_fk == !ctx->drop_fk);
DBUG_ASSERT(!add_fts_doc_id || add_fts_doc_id_idx);
DBUG_ASSERT(!add_fts_doc_id_idx
|| innobase_fulltext_exist(altered_table));
DBUG_ASSERT(!ctx->add_cols);
DBUG_ASSERT(!ctx->add_index);
DBUG_ASSERT(!ctx->add_key_numbers);
DBUG_ASSERT(!ctx->num_to_add_index);
user_table = ctx->new_table;
trx_start_if_not_started_xa(ctx->prebuilt->trx);
/* 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. */
ctx->num_to_add_index = ha_alter_info->index_add_count;
ut_ad(ctx->prebuilt->trx->mysql_thd != NULL);
const char* path = thd_innodb_tmpdir(
ctx->prebuilt->trx->mysql_thd);
index_defs = innobase_create_key_defs(
ctx->heap, ha_alter_info, altered_table, ctx->num_to_add_index,
num_fts_index,
row_table_got_default_clust_index(ctx->new_table),
fts_doc_id_col, add_fts_doc_id, add_fts_doc_id_idx);
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)
|| !innobase_fulltext_exist(altered_table))) {
/* InnoDB can perform an online operation (LOCK=NONE). */
} else {
/* This should have been blocked in
check_if_supported_inplace_alter(). */
ut_ad(0);
my_error(ER_NOT_SUPPORTED_YET, MYF(0),
thd_query_string(ctx->prebuilt->trx->mysql_thd)->str);
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] */
DBUG_ASSERT(!add_fts_doc_id || new_clustered);
DBUG_ASSERT(!!new_clustered ==
(innobase_need_rebuild(ha_alter_info)
|| add_fts_doc_id));
/* Allocate memory for dictionary index definitions */
ctx->add_index = static_cast<dict_index_t**>(
mem_heap_alloc(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) {
error = DB_SUCCESS;
} else {
error = row_merge_lock_table(
ctx->prebuilt->trx, ctx->new_table, LOCK_S);
if (error != DB_SUCCESS) {
goto error_handling;
}
}
/* Latch the InnoDB data dictionary exclusively so that no deadlocks
or lock waits can happen in it during an index create operation. */
row_mysql_lock_data_dictionary(ctx->trx);
dict_locked = true;
/* Wait for background stats processing to stop using the table that
we are going to alter. We know bg stats will not start using it again
until we are holding the data dict locked and we are holding it here
at least until checking ut_ad(user_table->n_ref_count == 1) below.
XXX what may happen if bg stats opens the table after we
have unlocked data dictionary below? */
dict_stats_wait_bg_to_stop_using_table(user_table, ctx->trx);
online_retry_drop_indexes_low(ctx->new_table, ctx->trx);
ut_d(dict_table_check_for_dup_indexes(
ctx->new_table, CHECK_ABORTED_OK));
/* If a new clustered index is defined for the table we need
to rebuild the table with a temporary name. */
if (new_clustered) {
const char* new_table_name
= dict_mem_create_temporary_tablename(
ctx->heap,
ctx->new_table->name,
ctx->new_table->id);
ulint n_cols;
dtuple_t* add_cols;
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;
}
n_cols = altered_table->s->fields;
if (add_fts_doc_id) {
n_cols++;
DBUG_ASSERT(flags2 & DICT_TF2_FTS);
DBUG_ASSERT(add_fts_doc_id_idx);
flags2 |= DICT_TF2_FTS_ADD_DOC_ID
| DICT_TF2_FTS_HAS_DOC_ID
| DICT_TF2_FTS;
}
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. */
ctx->new_table = dict_mem_table_create(
new_table_name, 0, n_cols, flags, flags2);
/* The rebuilt indexed_table will use the renamed
column names. */
ctx->col_names = NULL;
if (DICT_TF_HAS_DATA_DIR(flags)) {
ctx->new_table->data_dir_path =
mem_heap_strdup(ctx->new_table->heap,
user_table->data_dir_path);
}
for (uint i = 0; i < altered_table->s->fields; i++) {
const Field* field = altered_table->field[i];
ulint is_unsigned;
ulint field_type
= (ulint) field->type();
ulint col_type
= get_innobase_type_from_mysql_type(
&is_unsigned, field);
ulint charset_no;
ulint col_len;
/* we assume in dtype_form_prtype() that this
fits in two bytes */
ut_a(field_type <= MAX_CHAR_COLL_NUM);
if (!field->real_maybe_null()) {
field_type |= DATA_NOT_NULL;
}
if (field->binary()) {
field_type |= DATA_BINARY_TYPE;
}
if (is_unsigned) {
field_type |= DATA_UNSIGNED;
}
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),
field->field_name);
goto new_clustered_failed;
}
} else {
charset_no = 0;
}
col_len = field->pack_length();
/* The MySQL pack length contains 1 or 2 bytes
length field for a true VARCHAR. Let us
subtract that, so that the InnoDB column
length in the InnoDB data dictionary is the
real maximum byte length of the actual data. */
if (field->type() == MYSQL_TYPE_VARCHAR) {
uint32 length_bytes
= static_cast<const Field_varstring*>(
field)->length_bytes;
col_len -= length_bytes;
if (length_bytes == 2) {
field_type |= DATA_LONG_TRUE_VARCHAR;
}
}
if (dict_col_name_is_reserved(field->field_name)) {
dict_mem_table_free(ctx->new_table);
my_error(ER_WRONG_COLUMN_NAME, MYF(0),
field->field_name);
goto new_clustered_failed;
}
dict_mem_table_add_col(
ctx->new_table, ctx->heap,
field->field_name,
col_type,
dtype_form_prtype(field_type, charset_no),
col_len);
}
if (add_fts_doc_id) {
fts_add_doc_id_column(ctx->new_table, ctx->heap);
ctx->new_table->fts->doc_col = fts_doc_id_col;
ut_ad(fts_doc_id_col == altered_table->s->fields);
} else if (ctx->new_table->fts) {
ctx->new_table->fts->doc_col = fts_doc_id_col;
}
error = row_create_table_for_mysql(
ctx->new_table, ctx->trx, false);
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, 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->n_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;
default:
my_error_innodb(error, old_table->s->db.str,
table_name, flags);
new_clustered_failed:
DBUG_ASSERT(ctx->trx != ctx->prebuilt->trx);
trx_rollback_to_savepoint(ctx->trx, NULL);
ut_ad(user_table->n_ref_count == 1);
online_retry_drop_indexes_with_trx(
user_table, ctx->trx);
goto err_exit;
}
if (ha_alter_info->handler_flags
& Alter_inplace_info::ADD_COLUMN) {
add_cols = dtuple_create(
ctx->heap,
dict_table_get_n_cols(ctx->new_table));
dict_table_copy_types(add_cols, ctx->new_table);
} else {
add_cols = NULL;
}
ctx->col_map = innobase_build_col_map(
ha_alter_info, altered_table, old_table,
ctx->new_table, user_table,
add_cols, ctx->heap);
ctx->add_cols = add_cols;
} else {
DBUG_ASSERT(!innobase_need_rebuild(ha_alter_info));
DBUG_ASSERT(old_table->s->primary_key
== altered_table->s->primary_key);
if (!ctx->new_table->fts
&& innobase_fulltext_exist(altered_table)) {
ctx->new_table->fts = fts_create(
ctx->new_table);
ctx->new_table->fts->doc_col = fts_doc_id_col;
}
}
/* 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. */
for (ulint a = 0; a < ctx->num_to_add_index; a++) {
ctx->add_index[a] = row_merge_create_index(
ctx->trx, ctx->new_table,
&index_defs[a]);
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;
}
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->ibd_file_missing
|| 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);
bool ok = row_log_allocate(ctx->add_index[a],
NULL, true, NULL,
NULL, path);
rw_lock_x_unlock(&ctx->add_index[a]->lock);
if (!ok) {
error = DB_OUT_OF_MEMORY;
goto error_handling;
}
}
}
ut_ad(new_clustered == ctx->need_rebuild());
DBUG_EXECUTE_IF("innodb_OOM_prepare_inplace_alter",
error = DB_OUT_OF_MEMORY;
goto error_handling;);
if (new_clustered && ctx->online) {
/* Allocate a log for online table rebuild. */
dict_index_t* clust_index = dict_table_get_first_index(
user_table);
rw_lock_x_lock(&clust_index->lock);
bool ok = row_log_allocate(
clust_index, ctx->new_table,
!(ha_alter_info->handler_flags
& Alter_inplace_info::ADD_PK_INDEX),
ctx->add_cols, ctx->col_map, path);
rw_lock_x_unlock(&clust_index->lock);
if (!ok) {
error = DB_OUT_OF_MEMORY;
goto error_handling;
}
}
if (ctx->online) {
/* Assign a consistent read view for
row_merge_read_clustered_index(). */
trx_assign_read_view(ctx->prebuilt->trx);
}
if (fts_index) {
/* Ensure that the dictionary operation mode will
not change while creating the auxiliary tables. */
trx_dict_op_t op = trx_get_dict_operation(ctx->trx);
#ifdef UNIV_DEBUG
switch (op) {
case TRX_DICT_OP_NONE:
break;
case TRX_DICT_OP_TABLE:
case TRX_DICT_OP_INDEX:
goto op_ok;
}
ut_error;
op_ok:
#endif /* UNIV_DEBUG */
ut_ad(ctx->trx->dict_operation_lock_mode == RW_X_LATCH);
ut_ad(mutex_own(&dict_sys->mutex));
#ifdef UNIV_SYNC_DEBUG
ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
#endif /* UNIV_SYNC_DEBUG */
DICT_TF2_FLAG_SET(ctx->new_table, DICT_TF2_FTS);
/* This function will commit the transaction and reset
the trx_t::dict_operation flag on success. */
error = fts_create_index_tables(ctx->trx, fts_index);
DBUG_EXECUTE_IF("innodb_test_fail_after_fts_index_table",
error = DB_LOCK_WAIT_TIMEOUT;
goto error_handling;);
if (error != DB_SUCCESS) {
goto error_handling;
}
trx_start_for_ddl(ctx->trx, op);
if (!ctx->new_table->fts
|| ib_vector_size(ctx->new_table->fts->indexes) == 0) {
error = fts_create_common_tables(
ctx->trx, ctx->new_table,
user_table->name, TRUE);
DBUG_EXECUTE_IF(
"innodb_test_fail_after_fts_common_table",
error = DB_LOCK_WAIT_TIMEOUT;);
if (error != DB_SUCCESS) {
goto error_handling;
}
ctx->new_table->fts->fts_status
|= TABLE_DICT_LOCKED;
error = innobase_fts_load_stopword(
ctx->new_table, ctx->trx,
ctx->prebuilt->trx->mysql_thd)
? DB_SUCCESS : DB_ERROR;
ctx->new_table->fts->fts_status
&= ~TABLE_DICT_LOCKED;
if (error != DB_SUCCESS) {
goto error_handling;
}
}
ut_ad(trx_get_dict_operation(ctx->trx) == op);
}
DBUG_ASSERT(error == DB_SUCCESS);
/* Commit the data dictionary transaction in order to release
the table locks on the system tables. This means that if
MySQL crashes while creating a new primary key inside
row_merge_build_indexes(), ctx->new_table will not be dropped
by trx_rollback_active(). It will have to be recovered or
dropped by the database administrator. */
trx_commit_for_mysql(ctx->trx);
row_mysql_unlock_data_dictionary(ctx->trx);
dict_locked = false;
ut_a(ctx->trx->lock.n_active_thrs == 0);
DBUG_EXECUTE_IF("crash_innodb_add_index_after", DBUG_SUICIDE(););
error_handling:
/* After an error, remove all those index definitions from the
dictionary which were defined. */
switch (error) {
case DB_SUCCESS:
ut_a(!dict_locked);
ut_d(mutex_enter(&dict_sys->mutex));
ut_d(dict_table_check_for_dup_indexes(
user_table, CHECK_PARTIAL_OK));
ut_d(mutex_exit(&dict_sys->mutex));
DBUG_RETURN(false);
case DB_TABLESPACE_EXISTS:
my_error(ER_TABLESPACE_EXISTS, MYF(0), "(unknown)");
break;
case DB_DUPLICATE_KEY:
my_error(ER_DUP_KEY, MYF(0), "SYS_INDEXES");
break;
default:
my_error_innodb(error, old_table->s->db.str,
table_name, user_table->flags);
}
error_handled:
ctx->prebuilt->trx->error_info = NULL;
ctx->trx->error_state = DB_SUCCESS;
if (!dict_locked) {
row_mysql_lock_data_dictionary(ctx->trx);
}
if (new_clustered) {
if (ctx->need_rebuild()) {
if (DICT_TF2_FLAG_IS_SET(
ctx->new_table, DICT_TF2_FTS)) {
innobase_drop_fts_index_table(
ctx->new_table, ctx->trx);
}
dict_table_close(ctx->new_table, TRUE, FALSE);
#if defined UNIV_DEBUG || defined UNIV_DDL_DEBUG
/* Nobody should have initialized the stats of the
newly created table yet. When this is the case, we
know that it has not been added for background stats
gathering. */
ut_a(!ctx->new_table->stat_initialized);
#endif /* UNIV_DEBUG || UNIV_DDL_DEBUG */
row_merge_drop_table(ctx->trx, ctx->new_table);
/* Free the log for online table rebuild, if
one was allocated. */
dict_index_t* clust_index = dict_table_get_first_index(
user_table);
rw_lock_x_lock(&clust_index->lock);
if (clust_index->online_log) {
ut_ad(ctx->online);
row_log_abort_sec(clust_index);
clust_index->online_status
= ONLINE_INDEX_COMPLETE;
}
rw_lock_x_unlock(&clust_index->lock);
}
trx_commit_for_mysql(ctx->trx);
/* 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(user_table->n_ref_count == 1 || ctx->online);
online_retry_drop_indexes_with_trx(user_table, ctx->trx);
} else {
ut_ad(!ctx->need_rebuild());
row_merge_drop_indexes(ctx->trx, user_table, TRUE);
trx_commit_for_mysql(ctx->trx);
}
ut_d(dict_table_check_for_dup_indexes(user_table, CHECK_ALL_COMPLETE));
ut_ad(!user_table->drop_aborted);
err_exit:
/* Clear the to_be_dropped flag in the data dictionary cache. */
for (ulint i = 0; i < ctx->num_to_drop_index; i++) {
DBUG_ASSERT(*ctx->drop_index[i]->name != TEMP_INDEX_PREFIX);
DBUG_ASSERT(ctx->drop_index[i]->to_be_dropped);
ctx->drop_index[i]->to_be_dropped = 0;
}
row_mysql_unlock_data_dictionary(ctx->trx);
trx_free_for_mysql(ctx->trx);
trx_commit_for_mysql(ctx->prebuilt->trx);
delete ctx;
ha_alter_info->handler_ctx = NULL;
DBUG_RETURN(true);
}