diff --git a/include/my_base.h b/include/my_base.h index 168625f4a87..564cd833185 100644 --- a/include/my_base.h +++ b/include/my_base.h @@ -367,8 +367,11 @@ enum ha_base_keytype { given value */ #define HA_ERR_RBR_LOGGING_FAILED 161 /* Row-based binlogging of row failed */ #define HA_ERR_DROP_INDEX_FK 162 /* Index needed in foreign key constr. */ +#define HA_ERR_FOREIGN_DUPLICATE_KEY 163 /* Upholding foreign key constraints + would lead to a duplicate key + error in some other table. */ -#define HA_ERR_LAST 162 /* Copy last error no */ +#define HA_ERR_LAST 163 /* Copy last error no */ /* Add error numbers before HA_ERR_LAST and change it accordingly. */ #define HA_ERR_ERRORS (HA_ERR_LAST - HA_ERR_FIRST + 1) diff --git a/mysql-test/r/innodb.result b/mysql-test/r/innodb.result index 577e6a7192a..30a19bdcba3 100644 --- a/mysql-test/r/innodb.result +++ b/mysql-test/r/innodb.result @@ -2758,3 +2758,21 @@ e varchar(255) character set utf8, key (a,b,c,d,e)) engine=innodb; ERROR 42000: Specified key was too long; max key length is 3072 bytes End of 5.0 tests +CREATE TABLE t1 ( +field1 varchar(8) NOT NULL DEFAULT '', +field2 varchar(8) NOT NULL DEFAULT '', +PRIMARY KEY (field1, field2) +) ENGINE=InnoDB; +CREATE TABLE t2 ( +field1 varchar(8) NOT NULL DEFAULT '' PRIMARY KEY, +FOREIGN KEY (field1) REFERENCES t1 (field1) +ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; +INSERT INTO t1 VALUES ('old', 'somevalu'); +INSERT INTO t1 VALUES ('other', 'anyvalue'); +INSERT INTO t2 VALUES ('old'); +INSERT INTO t2 VALUES ('other'); +UPDATE t1 SET field1 = 'other' WHERE field2 = 'somevalu'; +ERROR 23000: Upholding foreign key constraints for table 't1', entry 'other-somevalu', key 1 would lead to a duplicate entry +DROP TABLE t2; +DROP TABLE t1; diff --git a/mysql-test/t/innodb.test b/mysql-test/t/innodb.test index 10cb1dcf08b..ceffc3ada5b 100644 --- a/mysql-test/t/innodb.test +++ b/mysql-test/t/innodb.test @@ -1715,3 +1715,32 @@ create table t1 (a varchar(255) character set utf8, key (a,b,c,d,e)) engine=innodb; --echo End of 5.0 tests + +# +# Test that cascading updates leading to duplicate keys give the correct +# error message (bug #9680) +# + +CREATE TABLE t1 ( + field1 varchar(8) NOT NULL DEFAULT '', + field2 varchar(8) NOT NULL DEFAULT '', + PRIMARY KEY (field1, field2) +) ENGINE=InnoDB; + +CREATE TABLE t2 ( + field1 varchar(8) NOT NULL DEFAULT '' PRIMARY KEY, + FOREIGN KEY (field1) REFERENCES t1 (field1) + ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB; + +INSERT INTO t1 VALUES ('old', 'somevalu'); +INSERT INTO t1 VALUES ('other', 'anyvalue'); + +INSERT INTO t2 VALUES ('old'); +INSERT INTO t2 VALUES ('other'); + +--error ER_FOREIGN_DUPLICATE_KEY +UPDATE t1 SET field1 = 'other' WHERE field2 = 'somevalu'; + +DROP TABLE t2; +DROP TABLE t1; diff --git a/sql/ha_innodb.cc b/sql/ha_innodb.cc index eaf52a9ca09..ac6220014d1 100644 --- a/sql/ha_innodb.cc +++ b/sql/ha_innodb.cc @@ -465,6 +465,10 @@ convert_error_code_to_mysql( return(HA_ERR_FOUND_DUPP_KEY); + } else if (error == (int) DB_FOREIGN_DUPLICATE_KEY) { + + return(HA_ERR_FOREIGN_DUPLICATE_KEY); + } else if (error == (int) DB_RECORD_NOT_FOUND) { return(HA_ERR_NO_ACTIVE_RECORD); diff --git a/sql/handler.cc b/sql/handler.cc index b40a40684fe..dbd717b5498 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -358,6 +358,7 @@ static int ha_init_errors(void) SETMSG(HA_ERR_TABLE_EXIST, ER(ER_TABLE_EXISTS_ERROR)); SETMSG(HA_ERR_NO_CONNECTION, "Could not connect to storage engine"); SETMSG(HA_ERR_TABLE_DEF_CHANGED, ER(ER_TABLE_DEF_CHANGED)); + SETMSG(HA_ERR_FOREIGN_DUPLICATE_KEY, "FK constraint would lead to duplicate key"); /* Register the error messages for use with my_error(). */ return my_error_register(errmsgs, HA_ERR_FIRST, HA_ERR_LAST); @@ -1868,6 +1869,29 @@ void handler::print_error(int error, myf errflag) textno=ER_DUP_KEY; break; } + case HA_ERR_FOREIGN_DUPLICATE_KEY: + { + uint key_nr= get_dup_key(error); + if ((int) key_nr >= 0) + { + /* Write the key in the error message */ + char key[MAX_KEY_LENGTH]; + String str(key,sizeof(key),system_charset_info); + /* Table is opened and defined at this point */ + key_unpack(&str,table,(uint) key_nr); + uint max_length= MYSQL_ERRMSG_SIZE-(uint) strlen(ER(ER_FOREIGN_DUPLICATE_KEY)); + if (str.length() >= max_length) + { + str.length(max_length-4); + str.append(STRING_WITH_LEN("...")); + } + my_error(ER_FOREIGN_DUPLICATE_KEY, MYF(0), table_share->table_name.str, + str.c_ptr(), key_nr+1); + DBUG_VOID_RETURN; + } + textno= ER_DUP_KEY; + break; + } case HA_ERR_NULL_IN_SPATIAL: textno= ER_UNKNOWN_ERROR; break; @@ -2003,8 +2027,9 @@ uint handler::get_dup_key(int error) { DBUG_ENTER("handler::get_dup_key"); table->file->errkey = (uint) -1; - if (error == HA_ERR_FOUND_DUPP_KEY || error == HA_ERR_FOUND_DUPP_UNIQUE || - error == HA_ERR_NULL_IN_SPATIAL || error == HA_ERR_DROP_INDEX_FK) + if (error == HA_ERR_FOUND_DUPP_KEY || error == HA_ERR_FOREIGN_DUPLICATE_KEY || + error == HA_ERR_FOUND_DUPP_UNIQUE || error == HA_ERR_NULL_IN_SPATIAL || + error == HA_ERR_DROP_INDEX_FK) info(HA_STATUS_ERRKEY | HA_STATUS_NO_LOCK); DBUG_RETURN(table->file->errkey); } diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index 1099b410290..a1c7ca652fd 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -5800,3 +5800,5 @@ ER_CANT_READ_LOCK_LOG_TABLE eng "You can't use usual read lock with log tables. Try READ LOCAL instead." ER_SP_WRONG_NAME 42000 eng "Incorrect routine name '%-.64s'" +ER_FOREIGN_DUPLICATE_KEY 23000 S1009 + eng "Upholding foreign key constraints for table '%.64s', entry '%-.64s', key %d would lead to a duplicate entry" diff --git a/storage/innobase/include/db0err.h b/storage/innobase/include/db0err.h index de5ac44e73f..7f75e7e10a6 100644 --- a/storage/innobase/include/db0err.h +++ b/storage/innobase/include/db0err.h @@ -57,6 +57,10 @@ Created 5/24/1996 Heikki Tuuri buffer pool (for big transactions, InnoDB stores the lock structs in the buffer pool) */ +#define DB_FOREIGN_DUPLICATE_KEY 46 /* foreign key constraints + activated by the operation would + lead to a duplicate key in some + table */ /* The following are partial failure codes */ #define DB_FAIL 1000 diff --git a/storage/innobase/row/row0ins.c b/storage/innobase/row/row0ins.c index 5e833372299..128b46f9aa4 100644 --- a/storage/innobase/row/row0ins.c +++ b/storage/innobase/row/row0ins.c @@ -1376,6 +1376,21 @@ run_again: thr, foreign, &pcur, entry, &mtr); if (err != DB_SUCCESS) { + /* Since reporting a plain + "duplicate key" error + message to the user in + cases where a long CASCADE + operation would lead to a + duplicate key in some + other table is very + confusing, map duplicate + key errors resulting from + FK constraints to a + separate error code. */ + + if (err == DB_DUPLICATE_KEY) { + err = DB_FOREIGN_DUPLICATE_KEY; + } break; } diff --git a/storage/innobase/row/row0mysql.c b/storage/innobase/row/row0mysql.c index 86d9ecf9c54..a16ffc5aa41 100644 --- a/storage/innobase/row/row0mysql.c +++ b/storage/innobase/row/row0mysql.c @@ -473,8 +473,9 @@ handle_new_error: ut_a(err != DB_SUCCESS); trx->error_state = DB_SUCCESS; - - if (err == DB_DUPLICATE_KEY) { + + if ((err == DB_DUPLICATE_KEY) + || (err == DB_FOREIGN_DUPLICATE_KEY)) { if (savept) { /* Roll back the latest, possibly incomplete insertion or update */