Commit 0d7cf06a authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-17938 ALTER TABLE reports ER_TABLESPACE_EXISTS after failed ALTER TABLE

There was a race condition in the error handling of ALTER TABLE when
the table contains FULLTEXT INDEX.

During the error handling of an erroneous ALTER TABLE statement,
when InnoDB would drop the internally created tables for FULLTEXT INDEX,
it could happen that one of the hidden tables was being concurrently
accessed by a background thread. Because of this, InnoDB would defer
the drop operation to the background.

However, related to MDEV-13564 backup-safe TRUNCATE TABLE and its
prerequisite MDEV-14585, we had to make the background drop table queue
crash-safe by renaming the table to a temporary name before enqueueing it.
This renaming was introduced in a follow-up of the MDEV-13407 fix.
As part of this rename operation, we were unnecessarily parsing the
current SQL statement, because the same rename operation could also be
executed as part of ALTER TABLE via ha_innobase::rename_table().

If an ALTER TABLE statement was being refused due to incorrectly formed
FOREIGN KEY constraint, then it could happen that the renaming of the hidden
internal tables for FULLTEXT INDEX could also fail, triggering a host of
error log messages, and causing a subsequent table-rebuilding ALTER TABLE
operation to fail due to the tablespace already existing.

innobase_rename_table(), row_rename_table_for_mysql(): Add the parameter
use_fk for suppressing the parsing of FOREIGN KEY constraints. It
will only be passed as use_fk=true by ha_innobase::rename_table(),
which can be invoked as part of ALTER TABLE...ALGORITHM=COPY.
parent 53440e2d
......@@ -1511,8 +1511,8 @@ fts_rename_one_aux_table(
table_new_name_len - new_db_name_len);
fts_table_new_name[table_new_name_len] = 0;
return(row_rename_table_for_mysql(
fts_table_old_name, fts_table_new_name, trx, false));
return row_rename_table_for_mysql(
fts_table_old_name, fts_table_new_name, trx, false, false);
}
/****************************************************************//**
......@@ -6256,7 +6256,7 @@ fts_rename_one_aux_table_to_hex_format(
}
error = row_rename_table_for_mysql(aux_table->name, new_name, trx,
FALSE);
false, false);
if (error != DB_SUCCESS) {
ib::warn() << "Failed to rename aux table '"
......@@ -6395,7 +6395,7 @@ fts_rename_aux_tables_to_hex_format_low(
DICT_TF2_FLAG_UNSET(table, DICT_TF2_FTS_AUX_HEX_NAME);
err = row_rename_table_for_mysql(table->name.m_name,
aux_table->name,
trx_bg, FALSE);
trx_bg, false, false);
trx_bg->dict_operation_lock_mode = 0;
dict_table_close(table, TRUE, FALSE);
......
......@@ -13315,6 +13315,7 @@ innobase_drop_database(
@param[in] from old table name
@param[in] to new table name
@param[in] commit whether to commit trx
@param[in] use_fk whether to parse and enforce FOREIGN KEY constraints
@return DB_SUCCESS or error code */
inline
dberr_t
......@@ -13322,7 +13323,8 @@ innobase_rename_table(
trx_t* trx,
const char* from,
const char* to,
bool commit = true)
bool commit,
bool use_fk)
{
dberr_t error;
char norm_to[FN_REFLEN];
......@@ -13382,7 +13384,8 @@ innobase_rename_table(
ut_a(trx->will_lock > 0);
error = row_rename_table_for_mysql(norm_from, norm_to, trx, commit);
error = row_rename_table_for_mysql(norm_from, norm_to, trx, commit,
use_fk);
if (error != DB_SUCCESS) {
if (error == DB_TABLE_NOT_FOUND
......@@ -13407,7 +13410,8 @@ innobase_rename_table(
#endif /* _WIN32 */
trx_start_if_not_started(trx, true);
error = row_rename_table_for_mysql(
par_case_name, norm_to, trx, TRUE);
par_case_name, norm_to, trx,
true, false);
}
}
......@@ -13519,7 +13523,8 @@ int ha_innobase::truncate()
trx_set_dict_operation(trx, TRX_DICT_OP_TABLE);
row_mysql_lock_data_dictionary(trx);
int err = convert_error_code_to_mysql(
innobase_rename_table(trx, ib_table->name.m_name, temp_name, false),
innobase_rename_table(trx, ib_table->name.m_name, temp_name,
false, false),
ib_table->flags, m_user_thd);
if (err) {
trx_rollback_for_mysql(trx);
......@@ -13572,7 +13577,7 @@ ha_innobase::rename_table(
++trx->will_lock;
trx_set_dict_operation(trx, TRX_DICT_OP_INDEX);
dberr_t error = innobase_rename_table(trx, from, to);
dberr_t error = innobase_rename_table(trx, from, to, true, true);
DEBUG_SYNC(thd, "after_innobase_rename_table");
......
......@@ -470,7 +470,9 @@ row_rename_table_for_mysql(
const char* old_name, /*!< in: old table name */
const char* new_name, /*!< in: new table name */
trx_t* trx, /*!< in/out: transaction */
bool commit) /*!< in: whether to commit trx */
bool commit, /*!< in: whether to commit trx */
bool use_fk) /*!< in: whether to parse and enforce
FOREIGN KEY constraints */
MY_ATTRIBUTE((nonnull, warn_unused_result));
/*********************************************************************//**
......
......@@ -3483,7 +3483,8 @@ row_drop_table_for_mysql(
ib::info() << "Deferring DROP TABLE " << table->name
<< "; renaming to " << tmp_name;
err = row_rename_table_for_mysql(
table->name.m_name, tmp_name, trx, false);
table->name.m_name, tmp_name, trx,
false, false);
} else {
err = DB_SUCCESS;
}
......@@ -4127,7 +4128,9 @@ row_rename_table_for_mysql(
const char* old_name, /*!< in: old table name */
const char* new_name, /*!< in: new table name */
trx_t* trx, /*!< in/out: transaction */
bool commit) /*!< in: whether to commit trx */
bool commit, /*!< in: whether to commit trx */
bool use_fk) /*!< in: whether to parse and enforce
FOREIGN KEY constraints */
{
dict_table_t* table = NULL;
ibool dict_locked = FALSE;
......@@ -4232,7 +4235,7 @@ row_rename_table_for_mysql(
goto funct_exit;
} else if (!old_is_tmp && new_is_tmp) {
} else if (use_fk && !old_is_tmp && new_is_tmp) {
/* MySQL is doing an ALTER TABLE command and it renames the
original table to a temporary table name. We want to preserve
the original foreign key constraint definitions despite the
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment