From 2ff76f67506ec056910835d577e6eb88de106b09 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Thu, 23 Jan 2014 11:04:59 +0100 Subject: [PATCH] MDEV-5406 add index to an innodb table with a uniqueness violation crashes mysqld After table->file->add_index() in mysql_alter_table() the table in the engine has the intermediate temporary structure, it's neither the original nor the final table structure (it'll be final after successful table->file->drop_index() call). So, when add_index() fails with a unique key violation, we cannot simply get the failed key number and easily map it to the key name and key structure via table->key_info[key_no]. For now we'll create this "intermediate temporary structure", emulating InnoDB internal rules. This bug and the fix will go away in 10.0 that uses completely different online alter table code. mysql-test/t/alter_table_trans.test: mdev:5406 --- mysql-test/r/alter_table_trans.result | 12 +++++++++ mysql-test/t/alter_table_trans.test | 20 +++++++++++++++ sql/sql_table.cc | 37 +++++++++++++++++++++------ 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/mysql-test/r/alter_table_trans.result b/mysql-test/r/alter_table_trans.result index 3dd5c00d3d8..67ae4075d57 100644 --- a/mysql-test/r/alter_table_trans.result +++ b/mysql-test/r/alter_table_trans.result @@ -4,3 +4,15 @@ ALTER TABLE t1 RENAME TO t2, DISABLE KEYS; Warnings: Note 1031 Table storage engine for 't1' doesn't have this option DROP TABLE t2; +CREATE TABLE t1 ( +col4 text NOT NULL, +col2 int(11) NOT NULL DEFAULT '0', +col3 int(11) DEFAULT NULL, +extra int(11) DEFAULT NULL, +KEY idx (col4(10)) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +insert t1 values (repeat('1', 8193),3,1,1); +insert t1 values (repeat('3', 8193),3,1,1); +ALTER TABLE t1 ADD PRIMARY KEY (col4(10)) , ADD UNIQUE KEY uidx (col3); +ERROR 23000: Duplicate entry '1' for key 'uidx' +DROP TABLE t1; diff --git a/mysql-test/t/alter_table_trans.test b/mysql-test/t/alter_table_trans.test index 9096a392af4..29b9b4c212f 100644 --- a/mysql-test/t/alter_table_trans.test +++ b/mysql-test/t/alter_table_trans.test @@ -13,3 +13,23 @@ drop table if exists t1,t2; CREATE TABLE t1 (a INT, INDEX(a)) engine=innodb; ALTER TABLE t1 RENAME TO t2, DISABLE KEYS; DROP TABLE t2; + +# +# MDEV-5406 add index to an innodb table with a uniqueness violation crashes mysqld +# + +CREATE TABLE t1 ( + col4 text NOT NULL, + col2 int(11) NOT NULL DEFAULT '0', + col3 int(11) DEFAULT NULL, + extra int(11) DEFAULT NULL, + KEY idx (col4(10)) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +insert t1 values (repeat('1', 8193),3,1,1); +insert t1 values (repeat('3', 8193),3,1,1); +--error ER_DUP_ENTRY +ALTER TABLE t1 ADD PRIMARY KEY (col4(10)) , ADD UNIQUE KEY uidx (col3); +DROP TABLE t1; + + diff --git a/sql/sql_table.cc b/sql/sql_table.cc index eda178fcc64..c9f754a204f 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -6893,14 +6893,35 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, /* Only report error if handler has not already reported an error */ if (!thd->is_error()) { - /* - Exchange the key_info for the error message. If we exchange - key number by key name in the message later, we need correct info. - */ - KEY *save_key_info= table->key_info; - table->key_info= key_info; - table->file->print_error(error, MYF(0)); - table->key_info= save_key_info; + /* + HACK HACK HACK + Prepare the list of keys for an error message. + It must match what the engine does internally in ::add_index(). + Here we emulate what innobase_create_key_def() does. + Luckily, in 10.0 this will go away. + */ + KEY *save_key_info= table->key_info; + uint add_cnt= index_add_count, old_cnt= table->s->keys; + KEY *merged= (KEY*)thd->alloc((old_cnt + add_cnt) * sizeof(KEY)); +#define is_PK(K) (!my_strcasecmp(system_charset_info, (K)->name, "PRIMARY")) + + if (is_PK(key_info)) + { + merged[0]= key_info[0]; + if (is_PK(table->key_info)) + { + old_cnt--; + table->key_info++; + } + memcpy(merged + 1, table->key_info, old_cnt * sizeof(KEY)); + memcpy(merged + old_cnt + 1, key_info + 1, (add_cnt - 1) * sizeof(KEY)); + } + else + merged= key_info; + + table->key_info= merged; + table->file->print_error(error, MYF(0)); + table->key_info= save_key_info; } goto err_new_table_cleanup; }