Commit 6ed10ee5 authored by unknown's avatar unknown

Bug#60309 - Bug#12356829: MYSQL 5.5.9 FOR MAC OSX HAS BUG WITH FOREIGN KEY CONSTRAINTS

The innoDB global variable srv_lower_case_table_names is set to the value of lower_case_table_names declared in mysqld.h server in ha_innodb.cc.  Since this variable can change at runtime, it is reset for each handler call to ::create, ::open, ::rename_table & ::delete_table.

But it is possible for tables to be implicitly opened before an explicit handler call is made when an engine is first started or restarted.  I was able to reproduce that with the testcase in this patch on a version of InnoDB from 2 weeks ago.  It seemed like the change buffer entries for the secondary key was getting put into pages after the restart.  (But I am not sure, I did not write down the call stack while it was reproducing.)  In the current code, the implicit open, which is actually a call to dict_load_foreigns(), does not occur with this testcase.

The change is to replace srv_lower_case_table_names by an interface function in innodb.cc that retrieves the server global variable when it is needed.
parent 70695a4b
......@@ -71,3 +71,47 @@ FK1_Key FK2_Key
DROP TABLE Bug_60196;
DROP TABLE Bug_60196_FK1;
DROP TABLE Bug_60196_FK2;
CREATE TABLE Bug_60309_FK (
ID INT PRIMARY KEY,
ID2 INT,
KEY K2(ID2)
) ENGINE=InnoDB;
CREATE TABLE Bug_60309 (
ID INT PRIMARY KEY,
FK_ID INT,
KEY (FK_ID),
CONSTRAINT FK FOREIGN KEY (FK_ID) REFERENCES Bug_60309_FK (ID)
) ENGINE=InnoDB;
INSERT INTO Bug_60309_FK (ID, ID2) VALUES (1, 1), (2, 2), (3, 3);
INSERT INTO Bug_60309 VALUES (1, 1);
INSERT INTO Bug_60309 VALUES (2, 99);
ERROR 23000: Cannot add or update a child row: a foreign key constraint fails (`test`.`bug_60309`, CONSTRAINT `FK` FOREIGN KEY (`FK_ID`) REFERENCES `Bug_60309_FK` (`ID`))
SELECT * FROM Bug_60309_FK;
ID ID2
1 1
2 2
3 3
SELECT * FROM Bug_60309;
ID FK_ID
1 1
# Stop server
# Restart server.
#
# Try to insert more to the example table with foreign keys.
# Bug60309 causes the foreign key file not to be found after
# the resstart above.
#
SELECT * FROM Bug_60309;
ID FK_ID
1 1
INSERT INTO Bug_60309 VALUES (2, 2);
INSERT INTO Bug_60309 VALUES (3, 3);
SELECT * FROM Bug_60309;
ID FK_ID
1 1
2 2
3 3
# Clean up.
DROP TABLE Bug_60309;
DROP TABLE Bug_60309_FK;
......@@ -85,3 +85,73 @@ DROP TABLE Bug_60196;
DROP TABLE Bug_60196_FK1;
DROP TABLE Bug_60196_FK2;
# Bug#60309/12356829
# MYSQL 5.5.9 FOR MAC OSX HAS BUG WITH FOREIGN KEY CONSTRAINTS
# This testcase is different from that for Bug#60196 in that the
# referenced table contains a secondary key. When the engine is
# restarted, the referenced table is opened by the purge thread,
# which does not notice that lower_case_table_names == 2.
#
# Create test data.
#
CREATE TABLE Bug_60309_FK (
ID INT PRIMARY KEY,
ID2 INT,
KEY K2(ID2)
) ENGINE=InnoDB;
CREATE TABLE Bug_60309 (
ID INT PRIMARY KEY,
FK_ID INT,
KEY (FK_ID),
CONSTRAINT FK FOREIGN KEY (FK_ID) REFERENCES Bug_60309_FK (ID)
) ENGINE=InnoDB;
INSERT INTO Bug_60309_FK (ID, ID2) VALUES (1, 1), (2, 2), (3, 3);
INSERT INTO Bug_60309 VALUES (1, 1);
--error ER_NO_REFERENCED_ROW_2
INSERT INTO Bug_60309 VALUES (2, 99);
SELECT * FROM Bug_60309_FK;
SELECT * FROM Bug_60309;
--echo # Stop server
# Write file to make mysql-test-run.pl wait for the server to stop
-- exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
# Send a shutdown request to the server
-- shutdown_server 10
# Call script that will poll the server waiting for it to disapear
-- source include/wait_until_disconnected.inc
--echo # Restart server.
# Write file to make mysql-test-run.pl start up the server again
--exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
# Turn on reconnect
--enable_reconnect
# Call script that will poll the server waiting for it to be back online again
--source include/wait_until_connected_again.inc
# Turn off reconnect again
--disable_reconnect
--echo #
--echo # Try to insert more to the example table with foreign keys.
--echo # Bug60309 causes the foreign key file not to be found after
--echo # the resstart above.
--echo #
SELECT * FROM Bug_60309;
INSERT INTO Bug_60309 VALUES (2, 2);
INSERT INTO Bug_60309 VALUES (3, 3);
SELECT * FROM Bug_60309;
--echo
--echo # Clean up.
DROP TABLE Bug_60309;
DROP TABLE Bug_60309_FK;
......@@ -52,7 +52,6 @@ UNIV_INTERN dict_index_t* dict_ind_compact;
#include "que0que.h"
#include "rem0cmp.h"
#include "row0merge.h"
#include "srv0srv.h" /* srv_lower_case_table_names */
#include "m_ctype.h" /* my_isspace() */
#include "ha_prototypes.h" /* innobase_strcasecmp(), innobase_casedn_str()*/
......@@ -3029,14 +3028,14 @@ dict_scan_table_name(
/* Values; 0 = Store and compare as given; case sensitive
1 = Store and compare in lower; case insensitive
2 = Store as given, compare in lower; case semi-sensitive */
if (srv_lower_case_table_names == 2) {
if (innobase_get_lower_case_table_names() == 2) {
innobase_casedn_str(ref);
*table = dict_table_get_low(ref);
memcpy(ref, database_name, database_name_len);
ref[database_name_len] = '/';
memcpy(ref + database_name_len + 1, table_name, table_name_len + 1);
} else {
if (srv_lower_case_table_names == 1) {
if (innobase_get_lower_case_table_names() == 1) {
innobase_casedn_str(ref);
}
*table = dict_table_get_low(ref);
......
......@@ -2262,7 +2262,7 @@ dict_load_foreigns(
may not be the same case, but the previous comparison showed that they
match with no-case. */
if ((srv_lower_case_table_names != 2)
if ((innobase_get_lower_case_table_names() != 2)
&& (0 != ut_memcmp(field, table_name, len))) {
goto next_rec;
}
......
......@@ -33,7 +33,6 @@ Created 1/8/1996 Heikki Tuuri
#include "data0type.h"
#include "mach0data.h"
#include "dict0dict.h"
#include "srv0srv.h" /* srv_lower_case_table_names */
#include "ha_prototypes.h" /* innobase_casedn_str()*/
#ifndef UNIV_HOTBACKUP
# include "lock0lock.h"
......@@ -294,9 +293,9 @@ dict_mem_foreign_create(void)
/**********************************************************************//**
Sets the foreign_table_name_lookup pointer based on the value of
srv_lower_case_table_names. If that is 0 or 1, foreign_table_name_lookup
will point to foreign_table_name. If 2, then another string is allocated
of the heap and set to lower case. */
lower_case_table_names. If that is 0 or 1, foreign_table_name_lookup
will point to foreign_table_name. If 2, then another string is
allocated from foreign->heap and set to lower case. */
UNIV_INTERN
void
dict_mem_foreign_table_name_lookup_set(
......@@ -304,7 +303,7 @@ dict_mem_foreign_table_name_lookup_set(
dict_foreign_t* foreign, /*!< in/out: foreign struct */
ibool do_alloc) /*!< in: is an alloc needed */
{
if (srv_lower_case_table_names == 2) {
if (innobase_get_lower_case_table_names() == 2) {
if (do_alloc) {
foreign->foreign_table_name_lookup = mem_heap_alloc(
foreign->heap,
......@@ -321,9 +320,9 @@ dict_mem_foreign_table_name_lookup_set(
/**********************************************************************//**
Sets the referenced_table_name_lookup pointer based on the value of
srv_lower_case_table_names. If that is 0 or 1,
referenced_table_name_lookup will point to referenced_table_name. If 2,
then another string is allocated of the heap and set to lower case. */
lower_case_table_names. If that is 0 or 1, referenced_table_name_lookup
will point to referenced_table_name. If 2, then another string is
allocated from foreign->heap and set to lower case. */
UNIV_INTERN
void
dict_mem_referenced_table_name_lookup_set(
......@@ -331,7 +330,7 @@ dict_mem_referenced_table_name_lookup_set(
dict_foreign_t* foreign, /*!< in/out: foreign struct */
ibool do_alloc) /*!< in: is an alloc needed */
{
if (srv_lower_case_table_names == 2) {
if (innobase_get_lower_case_table_names() == 2) {
if (do_alloc) {
foreign->referenced_table_name_lookup = mem_heap_alloc(
foreign->heap,
......
......@@ -1199,6 +1199,20 @@ innobase_get_stmt(
return(stmt->str);
}
/**********************************************************************//**
Get the current setting of the lower_case_table_names global parameter from
mysqld.cc. We do a dirty read because for one there is no synchronization
object and secondly there is little harm in doing so even if we get a torn
read.
@return value of lower_case_table_names */
extern "C" UNIV_INTERN
ulint
innobase_get_lower_case_table_names(void)
/*=====================================*/
{
return(lower_case_table_names);
}
#if defined (__WIN__) && defined (MYSQL_DYNAMIC_PLUGIN)
extern MYSQL_PLUGIN_IMPORT MY_TMPDIR mysql_tmpdir_list;
/*******************************************************************//**
......@@ -3671,7 +3685,6 @@ ha_innobase::open(
UT_NOT_USED(test_if_locked);
thd = ha_thd();
srv_lower_case_table_names = lower_case_table_names;
/* Under some cases MySQL seems to call this function while
holding btr_search_latch. This breaks the latching order as
......@@ -6362,8 +6375,6 @@ create_table_def(
col_len);
}
srv_lower_case_table_names = lower_case_table_names;
error = row_create_table_for_mysql(table, trx);
if (error == DB_DUPLICATE_KEY) {
......@@ -7223,8 +7234,6 @@ ha_innobase::delete_table(
/* Drop the table in InnoDB */
srv_lower_case_table_names = lower_case_table_names;
error = row_drop_table_for_mysql(norm_name, trx,
thd_sql_command(thd)
== SQLCOM_DROP_DB);
......@@ -7354,8 +7363,6 @@ innobase_rename_table(
row_mysql_lock_data_dictionary(trx);
}
srv_lower_case_table_names = lower_case_table_names;
error = row_rename_table_for_mysql(
norm_from, norm_to, trx, lock_and_commit);
......
......@@ -240,7 +240,9 @@ dict_mem_foreign_create(void);
/**********************************************************************//**
Sets the foreign_table_name_lookup pointer based on the value of
srv_lower_case_table_names. */
lower_case_table_names. If that is 0 or 1, foreign_table_name_lookup
will point to foreign_table_name. If 2, then another string is
allocated from the heap and set to lower case. */
UNIV_INTERN
void
dict_mem_foreign_table_name_lookup_set(
......@@ -249,8 +251,10 @@ dict_mem_foreign_table_name_lookup_set(
ibool do_alloc); /*!< in: is an alloc needed */
/**********************************************************************//**
Sets the reference_table_name_lookup pointer based on the value of
srv_lower_case_table_names. */
Sets the referenced_table_name_lookup pointer based on the value of
lower_case_table_names. If that is 0 or 1, referenced_table_name_lookup
will point to referenced_table_name. If 2, then another string is
allocated from the heap and set to lower case. */
UNIV_INTERN
void
dict_mem_referenced_table_name_lookup_set(
......
......@@ -285,4 +285,15 @@ thd_set_lock_wait_time(
void* thd, /*!< in: thread handle (THD*) */
ulint value); /*!< in: time waited for the lock */
/**********************************************************************//**
Get the current setting of the lower_case_table_names global parameter from
mysqld.cc. We do a dirty read because for one there is no synchronization
object and secondly there is little harm in doing so even if we get a torn
read.
@return value of lower_case_table_names */
UNIV_INTERN
ulint
innobase_get_lower_case_table_names(void);
/*=====================================*/
#endif
......@@ -71,9 +71,6 @@ at a time */
#define SRV_AUTO_EXTEND_INCREMENT \
(srv_auto_extend_increment * ((1024 * 1024) / UNIV_PAGE_SIZE))
/* This is set to the MySQL server value for this variable. */
extern uint srv_lower_case_table_names;
/* Mutex for locking srv_monitor_file */
extern mutex_t srv_monitor_file_mutex;
/* Temporary file for innodb monitor output */
......
......@@ -86,12 +86,6 @@ Created 10/8/1995 Heikki Tuuri
#include "mysql/plugin.h"
#include "mysql/service_thd_wait.h"
/* This is set to the MySQL server value for this variable. It is only
needed for FOREIGN KEY definition parsing since FOREIGN KEY names are not
stored in the server metadata. The server stores and enforces it for
regular database and table names.*/
UNIV_INTERN uint srv_lower_case_table_names = 0;
/* The following counter is incremented whenever there is some user activity
in the server */
UNIV_INTERN ulint srv_activity_count = 0;
......
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