From cc6019f9c3f733c7a53dc3408847296ddd9e14e4 Mon Sep 17 00:00:00 2001 From: Luke Lu Date: Fri, 24 Jul 2026 21:48:16 +0000 Subject: [PATCH] MDEV-35732 Failed ALTER TABLE leaves cached table metadata corrupted A failed ALTER TABLE ... RENAME INDEX ... ALGORITHM=INSTANT changed the outcome of a subsequent, unrelated ALTER TABLE ... ADD FOREIGN KEY. A failed statement must have no side effects, but here the second statement wrongly succeeded where on a fresh table it correctly fails with ER_DUP_KEYNAME. Root cause: while rebuilding the key list, mysql_prepare_alter_table() handled a RENAME INDEX request by clearing HA_GENERATED_KEY in place on key_info->flags. key_info points into the (possibly cached) TABLE object that is reused across statements. The ALGORITHM=INSTANT incompatibility is only detected later, after mysql_prepare_alter_table() has returned, so the statement fails with ER_ALTER_OPERATION_NOT_SUPPORTED with the cleared flag never restored. The cached generated FK-support index (fk1) was thus left permanently marked as user-defined. That corrupted flag flips the de-duplication tie-break in the next ALTER: adding FOREIGN KEY ind1 (b) creates a generated support index on column b that prefix-matches fk1(b). Normally fk1 (generated) is dropped and the new ind1 survives, colliding by name with the existing user index ind1(a) and raising ER_DUP_KEYNAME. With fk1 no longer marked generated, the new ind1 is dropped instead, so no name collision is reached and the ADD FOREIGN KEY silently succeeds. Fix: do not mutate the cached key_info->flags. Track the "renamed => no longer generated" decision in a per-key local variable (generated_key), initialised from the flag, set to false on rename, and passed to the Key constructor. This preserves the in-statement behaviour while leaving the cached TABLE metadata untouched, so a failed ALTER has no lingering effect. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- .../alter_table_failed_rename_index.result | 20 +++++++++++++ .../main/alter_table_failed_rename_index.test | 30 +++++++++++++++++++ sql/sql_table.cc | 5 ++-- 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 mysql-test/main/alter_table_failed_rename_index.result create mode 100644 mysql-test/main/alter_table_failed_rename_index.test diff --git a/mysql-test/main/alter_table_failed_rename_index.result b/mysql-test/main/alter_table_failed_rename_index.result new file mode 100644 index 0000000000000..d315b6a5d5c09 --- /dev/null +++ b/mysql-test/main/alter_table_failed_rename_index.result @@ -0,0 +1,20 @@ +# +# Scenario A: WITH a prior failed ALTER ... RENAME INDEX +# +CREATE TABLE t1 (f1 INT, f2 INT, KEY(f1), KEY(f2)) ENGINE=InnoDB; +CREATE TABLE t2 (pk INT PRIMARY KEY, a INT, b INT, KEY ind1(a), +FOREIGN KEY fk1 (b) REFERENCES t1 (f1)) ENGINE=InnoDB; +ALTER TABLE t2 RENAME INDEX fk1 TO fk, ALGORITHM=INSTANT, ORDER BY a; +ERROR 0A000: ALGORITHM=INSTANT is not supported for this operation. Try ALGORITHM=COPY +ALTER TABLE t2 ADD FOREIGN KEY ind1 (b) REFERENCES t1 (f2); +ERROR 42000: Duplicate key name 'ind1' +DROP TABLE t2, t1; +# +# Scenario B: WITHOUT a prior failed ALTER ... RENAME INDEX (baseline) +# +CREATE TABLE t1 (f1 INT, f2 INT, KEY(f1), KEY(f2)) ENGINE=InnoDB; +CREATE TABLE t2 (pk INT PRIMARY KEY, a INT, b INT, KEY ind1(a), +FOREIGN KEY fk1 (b) REFERENCES t1 (f1)) ENGINE=InnoDB; +ALTER TABLE t2 ADD FOREIGN KEY ind1 (b) REFERENCES t1 (f2); +ERROR 42000: Duplicate key name 'ind1' +DROP TABLE t2, t1; diff --git a/mysql-test/main/alter_table_failed_rename_index.test b/mysql-test/main/alter_table_failed_rename_index.test new file mode 100644 index 0000000000000..11d543e2b791f --- /dev/null +++ b/mysql-test/main/alter_table_failed_rename_index.test @@ -0,0 +1,30 @@ +--source include/have_innodb.inc + +# MDEV-35732: Failed ALTER TABLE causes inconsistency, changes behavior of the next statement + +--echo # +--echo # Scenario A: WITH a prior failed ALTER ... RENAME INDEX +--echo # +CREATE TABLE t1 (f1 INT, f2 INT, KEY(f1), KEY(f2)) ENGINE=InnoDB; +CREATE TABLE t2 (pk INT PRIMARY KEY, a INT, b INT, KEY ind1(a), + FOREIGN KEY fk1 (b) REFERENCES t1 (f1)) ENGINE=InnoDB; + +--error ER_ALTER_OPERATION_NOT_SUPPORTED +ALTER TABLE t2 RENAME INDEX fk1 TO fk, ALGORITHM=INSTANT, ORDER BY a; + +--error ER_DUP_KEYNAME +ALTER TABLE t2 ADD FOREIGN KEY ind1 (b) REFERENCES t1 (f2); + +DROP TABLE t2, t1; + +--echo # +--echo # Scenario B: WITHOUT a prior failed ALTER ... RENAME INDEX (baseline) +--echo # +CREATE TABLE t1 (f1 INT, f2 INT, KEY(f1), KEY(f2)) ENGINE=InnoDB; +CREATE TABLE t2 (pk INT PRIMARY KEY, a INT, b INT, KEY ind1(a), + FOREIGN KEY fk1 (b) REFERENCES t1 (f1)) ENGINE=InnoDB; + +--error ER_DUP_KEYNAME +ALTER TABLE t2 ADD FOREIGN KEY ind1 (b) REFERENCES t1 (f2); + +DROP TABLE t2, t1; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 5ae123c528bab..19d5c5ed6bd5c 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -8941,6 +8941,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, bool long_hash_key= false; if (key_info->flags & HA_INVISIBLE_KEY) continue; + bool generated_key= key_info->flags & HA_GENERATED_KEY; const char *key_name= key_info->name.str; const bool primary_key= table->s->primary_key == i; const bool explicit_pk= primary_key && @@ -9029,7 +9030,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, dropped by mysql_prepare_create_table() and this will confuse code in fill_alter_inplace_info(). */ - key_info->flags&= ~HA_GENERATED_KEY; + generated_key= false; break; } } @@ -9229,7 +9230,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, tmp_name.length= strlen(key_name); /* We dont need LONG_UNIQUE_HASH_FIELD flag because it will be autogenerated */ key= new (thd->mem_root) Key(key_type, &tmp_name, &key_create_info, - key_info->flags & HA_GENERATED_KEY, + generated_key, &key_parts, key_info->option_list, DDL_options()); key->without_overlaps= key_info->without_overlaps; key->period= table->s->period.name;