row0mysql.c, pars0pars.c, eval0eval.c, dict0load.c, dict0dict.c, dict0crea.c:

  Fix bug #3478: InnoDB's FOREIGN KEY tables treated table and database names as case-insensitive; RENAME TABLE t to T would hang in an endless loop if t had a foreign key constraint defined on it
dict0dict.c:
  Fix bug #3478: InnoDB's FOREIGN KEY tables treated table and database names as case-insensitive; RENAME TABLE t to T would hang in an endless loop if t had a foreign key constraint defined on it; fix also a hang that would occur if one tried in ALTER TABLE or RENAME TABLE to create a foreign key constraint name that collided with another existing name
parent 35e86703
...@@ -1011,6 +1011,12 @@ dict_create_or_check_foreign_constraint_tables(void) ...@@ -1011,6 +1011,12 @@ dict_create_or_check_foreign_constraint_tables(void)
there are 2 secondary indexes on SYS_FOREIGN, and they there are 2 secondary indexes on SYS_FOREIGN, and they
are defined just like below */ are defined just like below */
/* NOTE: when designing InnoDB's foreign key support in 2001, we made
an error and made the table names and the foreign key id of type
'CHAR' (internally, really a VARCHAR). We should have made the type
VARBINARY, like in other InnoDB system tables, to get a clean
design. */
str = (char *) str = (char *)
"PROCEDURE CREATE_FOREIGN_SYS_TABLES_PROC () IS\n" "PROCEDURE CREATE_FOREIGN_SYS_TABLES_PROC () IS\n"
"BEGIN\n" "BEGIN\n"
...@@ -1227,9 +1233,17 @@ loop: ...@@ -1227,9 +1233,17 @@ loop:
fputs(".\nA foreign key constraint of name ", ef); fputs(".\nA foreign key constraint of name ", ef);
ut_print_name(ef, foreign->id); ut_print_name(ef, foreign->id);
fputs("\nalready exists." fputs("\nalready exists."
" (Note that internally InnoDB adds 'databasename/'\n" " (Note that internally InnoDB adds 'databasename/'\n"
"in front of the user-defined constraint name).\n", "in front of the user-defined constraint name).\n",
ef); ef);
fputs("Note that InnoDB's FOREIGN KEY system tables store\n"
"constraint names as case-insensitive, with the\n"
"MySQL standard latin1_swedish_ci collation. If you\n"
"create tables or databases whose names differ only in\n"
"the character case, then collisions in constraint\n"
"names can occur. Workaround: name your constraints\n"
"explicitly with unique names.\n",
ef);
mutex_exit(&dict_foreign_err_mutex); mutex_exit(&dict_foreign_err_mutex);
......
...@@ -132,7 +132,7 @@ dict_index_build_internal_non_clust( ...@@ -132,7 +132,7 @@ dict_index_build_internal_non_clust(
dict_index_t* index); /* in: user representation of a non-clustered dict_index_t* index); /* in: user representation of a non-clustered
index */ index */
/************************************************************************** /**************************************************************************
Removes a foreign constraint struct from the dictionet cache. */ Removes a foreign constraint struct from the dictionary cache. */
static static
void void
dict_foreign_remove_from_cache( dict_foreign_remove_from_cache(
...@@ -581,7 +581,7 @@ dict_table_get_on_id( ...@@ -581,7 +581,7 @@ dict_table_get_on_id(
dict_table_t* table; dict_table_t* table;
if (ut_dulint_cmp(table_id, DICT_FIELDS_ID) <= 0 if (ut_dulint_cmp(table_id, DICT_FIELDS_ID) <= 0
|| trx->dict_operation) { || trx->dict_operation_lock_mode == RW_X_LATCH) {
/* It is a system table which will always exist in the table /* It is a system table which will always exist in the table
cache: we avoid acquiring the dictionary mutex, because cache: we avoid acquiring the dictionary mutex, because
if we are doing a rollback to handle an error in TABLE if we are doing a rollback to handle an error in TABLE
......
...@@ -19,6 +19,7 @@ Created 4/24/1996 Heikki Tuuri ...@@ -19,6 +19,7 @@ Created 4/24/1996 Heikki Tuuri
#include "mach0data.h" #include "mach0data.h"
#include "dict0dict.h" #include "dict0dict.h"
#include "dict0boot.h" #include "dict0boot.h"
#include "rem0cmp.h"
/************************************************************************ /************************************************************************
Finds the first table name in the given database. */ Finds the first table name in the given database. */
...@@ -1121,12 +1122,26 @@ loop: ...@@ -1121,12 +1122,26 @@ loop:
rec = btr_pcur_get_rec(&pcur); rec = btr_pcur_get_rec(&pcur);
field = rec_get_nth_field(rec, 0, &len); field = rec_get_nth_field(rec, 0, &len);
/* Check if the table name in record is the one searched for */ /* Check if the table name in the record is the one searched for; the
if (len != ut_strlen(table_name) following call does the comparison in the latin1_swedish_ci
|| 0 != ut_memcmp(field, table_name, len)) { charset-collation, in a case-insensitive way. */
if (0 != cmp_data_data(dfield_get_type(dfield),
dfield_get_data(dfield), dfield_get_len(dfield),
field, len)) {
goto load_next_index; goto load_next_index;
} }
/* Since table names in SYS_FOREIGN are stored in a case-insensitive
order, we have to check that the table name matches also in a binary
string comparison. On Unix, MySQL allows table names that only differ
in character case. */
if (0 != ut_memcmp(field, table_name, len)) {
goto next_rec;
}
if (rec_get_deleted_flag(rec)) { if (rec_get_deleted_flag(rec)) {
......
...@@ -627,7 +627,11 @@ eval_concat( ...@@ -627,7 +627,11 @@ eval_concat(
} }
/********************************************************************* /*********************************************************************
Evaluates a predefined function node. */ Evaluates a predefined function node. If the first argument is an integer,
this function looks at the second argument which is the integer length in
bytes, and converts the integer to a VARCHAR.
If the first argument is of some other type, this function converts it to
BINARY. */
UNIV_INLINE UNIV_INLINE
void void
eval_to_binary( eval_to_binary(
...@@ -638,12 +642,24 @@ eval_to_binary( ...@@ -638,12 +642,24 @@ eval_to_binary(
que_node_t* arg2; que_node_t* arg2;
dfield_t* dfield; dfield_t* dfield;
byte* str1; byte* str1;
ulint len;
ulint len1; ulint len1;
arg1 = func_node->args; arg1 = func_node->args;
str1 = dfield_get_data(que_node_get_val(arg1)); str1 = dfield_get_data(que_node_get_val(arg1));
if (dtype_get_mtype(que_node_get_data_type(arg1)) != DATA_INT) {
len = dfield_get_len(que_node_get_val(arg1));
dfield = que_node_get_val(func_node);
dfield_set_data(dfield, str1, len);
return;
}
arg2 = que_node_get_next(arg1); arg2 = que_node_get_next(arg1);
len1 = (ulint)eval_node_get_int_val(arg2); len1 = (ulint)eval_node_get_int_val(arg2);
......
...@@ -259,9 +259,13 @@ pars_resolve_func_data_type( ...@@ -259,9 +259,13 @@ pars_resolve_func_data_type(
dtype_set(que_node_get_data_type(node), DATA_VARCHAR, dtype_set(que_node_get_data_type(node), DATA_VARCHAR,
DATA_ENGLISH, 0, 0); DATA_ENGLISH, 0, 0);
} else if (func == PARS_TO_BINARY_TOKEN) { } else if (func == PARS_TO_BINARY_TOKEN) {
ut_a(dtype_get_mtype(que_node_get_data_type(arg)) == DATA_INT); if (dtype_get_mtype(que_node_get_data_type(arg)) == DATA_INT) {
dtype_set(que_node_get_data_type(node), DATA_VARCHAR, dtype_set(que_node_get_data_type(node), DATA_VARCHAR,
DATA_ENGLISH, 0, 0); DATA_ENGLISH, 0, 0);
} else {
dtype_set(que_node_get_data_type(node), DATA_BINARY,
0, 0, 0);
}
} else if (func == PARS_TO_NUMBER_TOKEN) { } else if (func == PARS_TO_NUMBER_TOKEN) {
ut_a(dtype_get_mtype(que_node_get_data_type(arg)) ut_a(dtype_get_mtype(que_node_get_data_type(arg))
== DATA_VARCHAR); == DATA_VARCHAR);
......
...@@ -1981,7 +1981,8 @@ row_drop_table_for_mysql( ...@@ -1981,7 +1981,8 @@ row_drop_table_for_mysql(
"WHILE found = 1 LOOP\n" "WHILE found = 1 LOOP\n"
" SELECT ID INTO foreign_id\n" " SELECT ID INTO foreign_id\n"
" FROM SYS_FOREIGN\n" " FROM SYS_FOREIGN\n"
" WHERE FOR_NAME = table_name;\n" " WHERE FOR_NAME = table_name\n"
" AND TO_BINARY(FOR_NAME) = TO_BINARY(table_name);\n"
" IF (SQL % NOTFOUND) THEN\n" " IF (SQL % NOTFOUND) THEN\n"
" found := 0;\n" " found := 0;\n"
" ELSE" " ELSE"
...@@ -2381,7 +2382,8 @@ row_rename_table_for_mysql( ...@@ -2381,7 +2382,8 @@ row_rename_table_for_mysql(
"WHILE found = 1 LOOP\n" "WHILE found = 1 LOOP\n"
" SELECT ID INTO foreign_id\n" " SELECT ID INTO foreign_id\n"
" FROM SYS_FOREIGN\n" " FROM SYS_FOREIGN\n"
" WHERE FOR_NAME = old_table_name;\n" " WHERE FOR_NAME = old_table_name\n"
" AND TO_BINARY(FOR_NAME) = TO_BINARY(old_table_name);\n"
" IF (SQL % NOTFOUND) THEN\n" " IF (SQL % NOTFOUND) THEN\n"
" found := 0;\n" " found := 0;\n"
" ELSE\n" " ELSE\n"
...@@ -2414,7 +2416,8 @@ row_rename_table_for_mysql( ...@@ -2414,7 +2416,8 @@ row_rename_table_for_mysql(
" END IF;\n" " END IF;\n"
"END LOOP;\n" "END LOOP;\n"
"UPDATE SYS_FOREIGN SET REF_NAME = new_table_name\n" "UPDATE SYS_FOREIGN SET REF_NAME = new_table_name\n"
"WHERE REF_NAME = old_table_name;\n"; "WHERE REF_NAME = old_table_name\n"
" AND TO_BINARY(REF_NAME) = TO_BINARY(old_table_name);\n";
static const char str5[] = static const char str5[] =
"END;\n"; "END;\n";
...@@ -2602,7 +2605,11 @@ row_rename_table_for_mysql( ...@@ -2602,7 +2605,11 @@ row_rename_table_for_mysql(
if (err == DB_DUPLICATE_KEY) { if (err == DB_DUPLICATE_KEY) {
ut_print_timestamp(stderr); ut_print_timestamp(stderr);
fputs(" InnoDB: Error: table ", stderr); fputs(
" InnoDB: Error; possible reasons:\n"
"InnoDB: 1) Table rename would cause two FOREIGN KEY constraints\n"
"InnoDB: to have the same internal name in case-insensitive comparison.\n"
"InnoDB: 2) table ", stderr);
ut_print_name(stderr, new_name); ut_print_name(stderr, new_name);
fputs(" exists in the InnoDB internal data\n" fputs(" exists in the InnoDB internal data\n"
"InnoDB: dictionary though MySQL is trying rename table ", stderr); "InnoDB: dictionary though MySQL is trying rename table ", stderr);
......
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