From 0f3b6ab18b09421ceba02569e7e23cd53e2a047b Mon Sep 17 00:00:00 2001 From: Jimmy Yang Date: Wed, 2 Jun 2010 04:04:00 -0700 Subject: [PATCH] Port bug fix #53592 from mysql-5.1-innodb to mysql-trunk-innodb. --- .../suite/innodb/r/innodb_bug53592.result | 43 ++++++++++ .../suite/innodb/t/innodb_bug53592.test | 82 ++++++++++++++++++ storage/innobase/handler/ha_innodb.cc | 84 ++++++++++++++++++- storage/innobase/include/row0mysql.h | 9 -- storage/innobase/row/row0mysql.c | 31 ------- 5 files changed, 206 insertions(+), 43 deletions(-) create mode 100644 mysql-test/suite/innodb/r/innodb_bug53592.result create mode 100644 mysql-test/suite/innodb/t/innodb_bug53592.test diff --git a/mysql-test/suite/innodb/r/innodb_bug53592.result b/mysql-test/suite/innodb/r/innodb_bug53592.result new file mode 100644 index 00000000000..63b30f50413 --- /dev/null +++ b/mysql-test/suite/innodb/r/innodb_bug53592.result @@ -0,0 +1,43 @@ +set old_alter_table=0; +create table bug53592(a int) engine=innodb row_format=compact; +alter table bug53592 add column b text charset utf8; +alter table bug53592 add column c blob not null; +create index bug53592_b on bug53592(b(81)); +create unique index bug53592_c on bug53592(c(1)); +replace into bug53592 values (),(); +Warnings: +Warning 1364 Field 'c' doesn't have a default value +check table bug53592; +Table Op Msg_type Msg_text +test.bug53592 check status OK +drop table bug53592; +set old_alter_table=1; +create table bug53592(a int) engine=innodb row_format=compact; +alter table bug53592 add column b text charset utf8; +alter table bug53592 add column c blob not null; +create index bug53592_b on bug53592(b(81)); +create unique index bug53592_c on bug53592(c(1)); +replace into bug53592 values (),(); +Warnings: +Warning 1364 Field 'c' doesn't have a default value +check table bug53592; +Table Op Msg_type Msg_text +test.bug53592 check status OK +drop table bug53592; +CREATE TABLE bug53592_1( +col1 int, col2 int, +PRIMARY KEY (col1, col2) +) ENGINE=InnoDB; +CREATE TABLE bug53592_2 ( +col int PRIMARY KEY, +FOREIGN KEY (col) REFERENCES bug53592_1 (col1) +ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; +INSERT INTO bug53592_1 VALUES (1, 2); +INSERT INTO bug53592_1 VALUES (3, 4); +INSERT INTO bug53592_2 VALUES (1); +INSERT INTO bug53592_2 VALUES (3); +UPDATE bug53592_1 SET col1 = 3 WHERE col2 = 2; +ERROR 23000: Upholding foreign key constraints for table 'bug53592_1', entry '3-2', key 1 would lead to a duplicate entry +drop table bug53592_2; +drop table bug53592_1; diff --git a/mysql-test/suite/innodb/t/innodb_bug53592.test b/mysql-test/suite/innodb/t/innodb_bug53592.test new file mode 100644 index 00000000000..bc37743f6bf --- /dev/null +++ b/mysql-test/suite/innodb/t/innodb_bug53592.test @@ -0,0 +1,82 @@ +# Testcase for Bug #53592 - "crash replacing duplicates into +# table after fast alter table added unique key". The fix is to make +# sure index number lookup should go through "index translation table". + +--source include/have_innodb.inc + +# Use FIC for index creation +set old_alter_table=0; + +create table bug53592(a int) engine=innodb row_format=compact; + +alter table bug53592 add column b text charset utf8; + +alter table bug53592 add column c blob not null; + +# Create a non-unique nonclustered index +create index bug53592_b on bug53592(b(81)); + +# Create a unique index, this unique index should have smaller +# index number than bug53592_b, since unique index ranks higher +# than regular index does +create unique index bug53592_c on bug53592(c(1)); + +# This will trigger a dup key error and will require fetching +# the index number through a index structure for the error reporting. +# To get the correct index number, the code should go through index +# translation table. Otherwise, it will get the wrong index +# number and later trigger a server crash. +replace into bug53592 values (),(); + +check table bug53592; + +drop table bug53592; + +# Running the same set of test when "old_alter_table" is turned on +set old_alter_table=1; + +create table bug53592(a int) engine=innodb row_format=compact; + +alter table bug53592 add column b text charset utf8; + +alter table bug53592 add column c blob not null; + +# Create a non-unique nonclustered index +create index bug53592_b on bug53592(b(81)); + +# Create a unique index +create unique index bug53592_c on bug53592(c(1)); + +# This will trigger a dup key error and will require fetching +# the index number through a index structure for the error reporting. +# To get the correct index number, the code should go through index +# translation table. Otherwise, it will get the wrong index +# number and later trigger a server crash. +replace into bug53592 values (),(); + +check table bug53592; +drop table bug53592; + +# Test a dup key reported by foreign key constriant. +CREATE TABLE bug53592_1( + col1 int, col2 int, + PRIMARY KEY (col1, col2) +) ENGINE=InnoDB; + +CREATE TABLE bug53592_2 ( + col int PRIMARY KEY, + FOREIGN KEY (col) REFERENCES bug53592_1 (col1) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; + +INSERT INTO bug53592_1 VALUES (1, 2); +INSERT INTO bug53592_1 VALUES (3, 4); + +INSERT INTO bug53592_2 VALUES (1); +INSERT INTO bug53592_2 VALUES (3); + +--error ER_FOREIGN_DUPLICATE_KEY +UPDATE bug53592_1 SET col1 = 3 WHERE col2 = 2; + +drop table bug53592_2; +drop table bug53592_1; diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 710841daf55..fa48adc75ad 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -6866,7 +6866,7 @@ ha_innobase::create( (int) form->s->primary_key : -1); - /* Our function row_get_mysql_key_number_for_index assumes + /* Our function innobase_get_mysql_key_number_for_index assumes the primary key is always number 0, if it exists */ ut_a(primary_key_no == -1 || primary_key_no == 0); @@ -7582,6 +7582,84 @@ ha_innobase::read_time( return(ranges + (double) rows / (double) total_rows * time_for_scan); } +/*********************************************************************//** +Calculates the key number used inside MySQL for an Innobase index. We will +first check the "index translation table" for a match of the index to get +the index number. If there does not exist an "index translation table", +or not able to find the index in the translation table, then we will fall back +to the traditional way of looping through dict_index_t list to find a +match. In this case, we have to take into account if we generated a +default clustered index for the table +@return the key number used inside MySQL */ +static +unsigned int +innobase_get_mysql_key_number_for_index( +/*====================================*/ + INNOBASE_SHARE* share, /*!< in: share structure for index + translation table. */ + const TABLE* table, /*!< in: table in MySQL data + dictionary */ + dict_table_t* ib_table,/*!< in: table in Innodb data + dictionary */ + const dict_index_t* index) /*!< in: index */ +{ + const dict_index_t* ind; + unsigned int i; + + ut_a(index); + + /* If index does not belong to the table of share structure. Search + index->table instead */ + if (index->table != ib_table + && innobase_strcasecmp(index->table->name, share->table_name)) { + i = 0; + ind = dict_table_get_first_index(index->table); + + while (index != ind) { + ind = dict_table_get_next_index(ind); + i++; + } + + if (row_table_got_default_clust_index(index->table)) { + ut_a(i > 0); + i--; + } + + return(i); + } + + /* If index translation table exists, we will first check + the index through index translation table for a match. */ + if (share->idx_trans_tbl.index_mapping) { + for (i = 0; i < share->idx_trans_tbl.index_count; i++) { + if (share->idx_trans_tbl.index_mapping[i] == index) { + return(i); + } + } + + /* Print an error message if we cannot find the index + ** in the "index translation table". */ + sql_print_error("Cannot find index %s in InnoDB index " + "translation table.", index->name); + } + + /* If we do not have an "index translation table", or not able + to find the index in the translation table, we'll directly find + matching index with information from mysql TABLE structure and + InnoDB dict_index_t list */ + for (i = 0; i < table->s->keys; i++) { + ind = dict_table_get_index_on_name( + ib_table, table->key_info[i].name); + + if (index == ind) { + return(i); + } + } + + ut_error; + + return(0); +} /*********************************************************************//** Returns statistics information of the table to the MySQL interpreter, in various fields of the handle object. */ @@ -7851,8 +7929,8 @@ ha_innobase::info( err_index = trx_get_error_info(prebuilt->trx); if (err_index) { - errkey = (unsigned int) - row_get_mysql_key_number_for_index(err_index); + errkey = innobase_get_mysql_key_number_for_index( + share, table, ib_table, err_index); } else { errkey = (unsigned int) prebuilt->trx->error_key_num; } diff --git a/storage/innobase/include/row0mysql.h b/storage/innobase/include/row0mysql.h index bf9cda1ba80..e90742abe7c 100644 --- a/storage/innobase/include/row0mysql.h +++ b/storage/innobase/include/row0mysql.h @@ -253,15 +253,6 @@ row_table_got_default_clust_index( /*==============================*/ const dict_table_t* table); /*!< in: table */ /*********************************************************************//** -Calculates the key number used inside MySQL for an Innobase index. We have -to take into account if we generated a default clustered index for the table -@return the key number used inside MySQL */ -UNIV_INTERN -ulint -row_get_mysql_key_number_for_index( -/*===============================*/ - const dict_index_t* index); /*!< in: index */ -/*********************************************************************//** Does an update or delete of a row for MySQL. @return error code or DB_SUCCESS */ UNIV_INTERN diff --git a/storage/innobase/row/row0mysql.c b/storage/innobase/row/row0mysql.c index c9b9a982bef..2cd14ba1b42 100644 --- a/storage/innobase/row/row0mysql.c +++ b/storage/innobase/row/row0mysql.c @@ -1647,37 +1647,6 @@ row_table_got_default_clust_index( return(dict_index_get_nth_col(clust_index, 0)->mtype == DATA_SYS); } -/*********************************************************************//** -Calculates the key number used inside MySQL for an Innobase index. We have -to take into account if we generated a default clustered index for the table -@return the key number used inside MySQL */ -UNIV_INTERN -ulint -row_get_mysql_key_number_for_index( -/*===============================*/ - const dict_index_t* index) /*!< in: index */ -{ - const dict_index_t* ind; - ulint i; - - ut_a(index); - - i = 0; - ind = dict_table_get_first_index(index->table); - - while (index != ind) { - ind = dict_table_get_next_index(ind); - i++; - } - - if (row_table_got_default_clust_index(index->table)) { - ut_a(i > 0); - i--; - } - - return(i); -} - /*********************************************************************//** Locks the data dictionary in shared mode from modifications, for performing foreign key check, rollback, or other operation invisible to MySQL. */