mirror of
https://github.com/MariaDB/server.git
synced 2025-08-01 03:47:19 +03:00
Fix Bug #54582 stack overflow when opening many tables linked with
foreign keys at once rb://391 approved by Heikki Z
This commit is contained in:
@ -864,16 +864,27 @@ err_exit:
|
|||||||
|
|
||||||
err = dict_load_indexes(table, heap);
|
err = dict_load_indexes(table, heap);
|
||||||
|
|
||||||
|
/* Initialize table foreign_child value. Its value could be
|
||||||
|
changed when dict_load_foreigns() is called below */
|
||||||
|
table->fk_max_recusive_level = 0;
|
||||||
|
|
||||||
/* If the force recovery flag is set, we open the table irrespective
|
/* If the force recovery flag is set, we open the table irrespective
|
||||||
of the error condition, since the user may want to dump data from the
|
of the error condition, since the user may want to dump data from the
|
||||||
clustered index. However we load the foreign key information only if
|
clustered index. However we load the foreign key information only if
|
||||||
all indexes were loaded. */
|
all indexes were loaded. */
|
||||||
if (err == DB_SUCCESS) {
|
if (err == DB_SUCCESS) {
|
||||||
err = dict_load_foreigns(table->name, TRUE);
|
err = dict_load_foreigns(table->name, TRUE, TRUE);
|
||||||
|
|
||||||
|
if (err != DB_SUCCESS) {
|
||||||
|
dict_table_remove_from_cache(table);
|
||||||
|
table = NULL;
|
||||||
|
}
|
||||||
} else if (!srv_force_recovery) {
|
} else if (!srv_force_recovery) {
|
||||||
dict_table_remove_from_cache(table);
|
dict_table_remove_from_cache(table);
|
||||||
table = NULL;
|
table = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table->fk_max_recusive_level = 0;
|
||||||
#if 0
|
#if 0
|
||||||
if (err != DB_SUCCESS && table != NULL) {
|
if (err != DB_SUCCESS && table != NULL) {
|
||||||
|
|
||||||
@ -1095,8 +1106,12 @@ dict_load_foreign(
|
|||||||
/* out: DB_SUCCESS or error code */
|
/* out: DB_SUCCESS or error code */
|
||||||
const char* id, /* in: foreign constraint id as a
|
const char* id, /* in: foreign constraint id as a
|
||||||
null-terminated string */
|
null-terminated string */
|
||||||
ibool check_charsets)
|
ibool check_charsets,
|
||||||
/* in: TRUE=check charset compatibility */
|
/* in: TRUE=check charset compatibility */
|
||||||
|
ibool check_recursive)
|
||||||
|
/* in: Whether to record the foreign table
|
||||||
|
parent count to avoid unlimited recursive
|
||||||
|
load of chained foreign tables */
|
||||||
{
|
{
|
||||||
dict_foreign_t* foreign;
|
dict_foreign_t* foreign;
|
||||||
dict_table_t* sys_foreign;
|
dict_table_t* sys_foreign;
|
||||||
@ -1110,6 +1125,8 @@ dict_load_foreign(
|
|||||||
ulint len;
|
ulint len;
|
||||||
ulint n_fields_and_type;
|
ulint n_fields_and_type;
|
||||||
mtr_t mtr;
|
mtr_t mtr;
|
||||||
|
dict_table_t* for_table;
|
||||||
|
dict_table_t* ref_table;
|
||||||
|
|
||||||
ut_ad(mutex_own(&(dict_sys->mutex)));
|
ut_ad(mutex_own(&(dict_sys->mutex)));
|
||||||
|
|
||||||
@ -1194,11 +1211,54 @@ dict_load_foreign(
|
|||||||
|
|
||||||
dict_load_foreign_cols(id, foreign);
|
dict_load_foreign_cols(id, foreign);
|
||||||
|
|
||||||
/* If the foreign table is not yet in the dictionary cache, we
|
ref_table = dict_table_check_if_in_cache_low(
|
||||||
have to load it so that we are able to make type comparisons
|
foreign->referenced_table_name);
|
||||||
in the next function call. */
|
|
||||||
|
|
||||||
dict_table_get_low(foreign->foreign_table_name);
|
/* We could possibly wind up in a deep recursive calls if
|
||||||
|
we call dict_table_get_low() again here if there
|
||||||
|
is a chain of tables concatenated together with
|
||||||
|
foreign constraints. In such case, each table is
|
||||||
|
both a parent and child of the other tables, and
|
||||||
|
act as a "link" in such table chains.
|
||||||
|
To avoid such scenario, we would need to check the
|
||||||
|
number of ancesters the current table has. If that
|
||||||
|
exceeds DICT_FK_MAX_CHAIN_LEN, we will stop loading
|
||||||
|
the child table.
|
||||||
|
Foreign constraints are loaded in a Breath First fashion,
|
||||||
|
that is, the index on FOR_NAME is scanned first, and then
|
||||||
|
index on REF_NAME. So foreign constrains in which
|
||||||
|
current table is a child (foreign table) are loaded first,
|
||||||
|
and then those constraints where current table is a
|
||||||
|
parent (referenced) table.
|
||||||
|
Thus we could check the parent (ref_table) table's
|
||||||
|
reference count (fk_max_recusive_level) to know how deep the
|
||||||
|
recursive call is. If the parent table (ref_table) is already
|
||||||
|
loaded, and its fk_max_recusive_level is larger than
|
||||||
|
DICT_FK_MAX_CHAIN_LEN, we will stop the recursive loading
|
||||||
|
by skipping loading the child table. It will not affect foreign
|
||||||
|
constraint check for DMLs since child table will be loaded
|
||||||
|
at that time for the constraint check. */
|
||||||
|
if (!ref_table
|
||||||
|
|| ref_table->fk_max_recusive_level < DICT_FK_MAX_RECURSIVE_LOAD) {
|
||||||
|
|
||||||
|
/* If the foreign table is not yet in the dictionary cache, we
|
||||||
|
have to load it so that we are able to make type comparisons
|
||||||
|
in the next function call. */
|
||||||
|
|
||||||
|
for_table = dict_table_get_low(foreign->foreign_table_name);
|
||||||
|
|
||||||
|
if (for_table && ref_table && check_recursive) {
|
||||||
|
/* This is to record the longest chain of ancesters
|
||||||
|
this table has, if the parent has more ancesters
|
||||||
|
than this table has, record it after add 1 (for this
|
||||||
|
parent */
|
||||||
|
if (ref_table->fk_max_recusive_level
|
||||||
|
>= for_table->fk_max_recusive_level) {
|
||||||
|
for_table->fk_max_recusive_level =
|
||||||
|
ref_table->fk_max_recusive_level + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Note that there may already be a foreign constraint object in
|
/* Note that there may already be a foreign constraint object in
|
||||||
the dictionary cache for this constraint: then the following
|
the dictionary cache for this constraint: then the following
|
||||||
@ -1223,6 +1283,8 @@ dict_load_foreigns(
|
|||||||
/*===============*/
|
/*===============*/
|
||||||
/* out: DB_SUCCESS or error code */
|
/* out: DB_SUCCESS or error code */
|
||||||
const char* table_name, /* in: table name */
|
const char* table_name, /* in: table name */
|
||||||
|
ibool check_recursive,/* in: Whether to check recursive
|
||||||
|
load of tables chained by FK */
|
||||||
ibool check_charsets) /* in: TRUE=check charset
|
ibool check_charsets) /* in: TRUE=check charset
|
||||||
compatibility */
|
compatibility */
|
||||||
{
|
{
|
||||||
@ -1324,7 +1386,7 @@ loop:
|
|||||||
|
|
||||||
/* Load the foreign constraint definition to the dictionary cache */
|
/* Load the foreign constraint definition to the dictionary cache */
|
||||||
|
|
||||||
err = dict_load_foreign(id, check_charsets);
|
err = dict_load_foreign(id, check_charsets, check_recursive);
|
||||||
|
|
||||||
if (err != DB_SUCCESS) {
|
if (err != DB_SUCCESS) {
|
||||||
btr_pcur_close(&pcur);
|
btr_pcur_close(&pcur);
|
||||||
@ -1352,6 +1414,11 @@ load_next_index:
|
|||||||
|
|
||||||
mtr_start(&mtr);
|
mtr_start(&mtr);
|
||||||
|
|
||||||
|
/* Switch to scan index on REF_NAME, fk_max_recusive_level
|
||||||
|
already been updated when scanning FOR_NAME index, no need to
|
||||||
|
update again */
|
||||||
|
check_recursive = FALSE;
|
||||||
|
|
||||||
goto start_load;
|
goto start_load;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -763,6 +763,16 @@ convert_error_code_to_mysql(
|
|||||||
|
|
||||||
my_error(ER_QUERY_INTERRUPTED, MYF(0));
|
my_error(ER_QUERY_INTERRUPTED, MYF(0));
|
||||||
return(-1);
|
return(-1);
|
||||||
|
} else if (error == DB_FOREIGN_EXCEED_MAX_CASCADE) {
|
||||||
|
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
|
||||||
|
HA_ERR_ROW_IS_REFERENCED,
|
||||||
|
"InnoDB: Cannot delete/update "
|
||||||
|
"rows with cascading foreign key "
|
||||||
|
"constraints that exceed max "
|
||||||
|
"depth of %d. Please "
|
||||||
|
"drop extra constraints and try "
|
||||||
|
"again", DICT_FK_MAX_RECURSIVE_LOAD);
|
||||||
|
return(-1);
|
||||||
} else {
|
} else {
|
||||||
return(-1); // Unknown error
|
return(-1); // Unknown error
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,9 @@ Created 5/24/1996 Heikki Tuuri
|
|||||||
a later version of the engine. */
|
a later version of the engine. */
|
||||||
#define DB_INTERRUPTED 49 /* the query has been interrupted with
|
#define DB_INTERRUPTED 49 /* the query has been interrupted with
|
||||||
"KILL QUERY N;" */
|
"KILL QUERY N;" */
|
||||||
|
#define DB_FOREIGN_EXCEED_MAX_CASCADE 50/* Foreign key constraint related
|
||||||
|
cascading delete/update exceeds
|
||||||
|
maximum allowed depth */
|
||||||
|
|
||||||
/* The following are partial failure codes */
|
/* The following are partial failure codes */
|
||||||
#define DB_FAIL 1000
|
#define DB_FAIL 1000
|
||||||
|
@ -82,6 +82,8 @@ dict_load_foreigns(
|
|||||||
/*===============*/
|
/*===============*/
|
||||||
/* out: DB_SUCCESS or error code */
|
/* out: DB_SUCCESS or error code */
|
||||||
const char* table_name, /* in: table name */
|
const char* table_name, /* in: table name */
|
||||||
|
ibool check_recursive,/* in: Whether to check recursive
|
||||||
|
load of tables chained by FK */
|
||||||
ibool check_charsets);/* in: TRUE=check charsets
|
ibool check_charsets);/* in: TRUE=check charsets
|
||||||
compatibility */
|
compatibility */
|
||||||
/************************************************************************
|
/************************************************************************
|
||||||
|
@ -283,6 +283,21 @@ a foreign key constraint is enforced, therefore RESTRICT just means no flag */
|
|||||||
#define DICT_FOREIGN_ON_DELETE_NO_ACTION 16
|
#define DICT_FOREIGN_ON_DELETE_NO_ACTION 16
|
||||||
#define DICT_FOREIGN_ON_UPDATE_NO_ACTION 32
|
#define DICT_FOREIGN_ON_UPDATE_NO_ACTION 32
|
||||||
|
|
||||||
|
/** Tables could be chained together with Foreign key constraint. When
|
||||||
|
first load the parent table, we would load all of its descedents.
|
||||||
|
This could result in rescursive calls and out of stack error eventually.
|
||||||
|
DICT_FK_MAX_RECURSIVE_LOAD defines the maximum number of recursive loads,
|
||||||
|
when exceeded, the child table will not be loaded. It will be loaded when
|
||||||
|
the foreign constraint check needs to be run. */
|
||||||
|
#define DICT_FK_MAX_RECURSIVE_LOAD 250
|
||||||
|
|
||||||
|
/** Similarly, when tables are chained together with foreign key constraints
|
||||||
|
with on cascading delete/update clause, delete from parent table could
|
||||||
|
result in recursive cascading calls. This defines the maximum number of
|
||||||
|
such cascading deletes/updates allowed. When exceeded, the delete from
|
||||||
|
parent table will fail, and user has to drop excessive foreign constraint
|
||||||
|
before proceeds. */
|
||||||
|
#define FK_MAX_CASCADE_DEL 300
|
||||||
|
|
||||||
/* Data structure for a database table */
|
/* Data structure for a database table */
|
||||||
struct dict_table_struct{
|
struct dict_table_struct{
|
||||||
@ -339,6 +354,12 @@ struct dict_table_struct{
|
|||||||
NOT allowed until this count gets to zero;
|
NOT allowed until this count gets to zero;
|
||||||
MySQL does NOT itself check the number of
|
MySQL does NOT itself check the number of
|
||||||
open handles at drop */
|
open handles at drop */
|
||||||
|
unsigned fk_max_recusive_level:8;
|
||||||
|
/*!< maximum recursive level we support when
|
||||||
|
loading tables chained together with FK
|
||||||
|
constraints. If exceeds this level, we will
|
||||||
|
stop loading child table into memory along with
|
||||||
|
its parent table */
|
||||||
ulint n_foreign_key_checks_running;
|
ulint n_foreign_key_checks_running;
|
||||||
/* count of how many foreign key check
|
/* count of how many foreign key check
|
||||||
operations are currently being performed
|
operations are currently being performed
|
||||||
|
@ -367,6 +367,9 @@ struct que_thr_struct{
|
|||||||
thus far */
|
thus far */
|
||||||
ulint lock_state; /* lock state of thread (table or
|
ulint lock_state; /* lock state of thread (table or
|
||||||
row) */
|
row) */
|
||||||
|
ulint fk_cascade_depth; /*!< maximum cascading call depth
|
||||||
|
supported for foreign key constraint
|
||||||
|
related delete/updates */
|
||||||
};
|
};
|
||||||
|
|
||||||
#define QUE_THR_MAGIC_N 8476583
|
#define QUE_THR_MAGIC_N 8476583
|
||||||
|
@ -555,6 +555,12 @@ handle_new_error:
|
|||||||
"forcing-recovery.html"
|
"forcing-recovery.html"
|
||||||
" for help.\n", stderr);
|
" for help.\n", stderr);
|
||||||
|
|
||||||
|
} else if (err == DB_FOREIGN_EXCEED_MAX_CASCADE) {
|
||||||
|
fprintf(stderr, "InnoDB: Cannot delete/update rows with"
|
||||||
|
" cascading foreign key constraints that exceed max"
|
||||||
|
" depth of %lu\n"
|
||||||
|
"Please drop excessive foreign constraints"
|
||||||
|
" and try again\n", (ulong) DICT_FK_MAX_RECURSIVE_LOAD);
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "InnoDB: unknown error code %lu\n",
|
fprintf(stderr, "InnoDB: unknown error code %lu\n",
|
||||||
(ulong) err);
|
(ulong) err);
|
||||||
@ -1406,11 +1412,15 @@ row_update_for_mysql(
|
|||||||
run_again:
|
run_again:
|
||||||
thr->run_node = node;
|
thr->run_node = node;
|
||||||
thr->prev_node = node;
|
thr->prev_node = node;
|
||||||
|
thr->fk_cascade_depth = 0;
|
||||||
|
|
||||||
row_upd_step(thr);
|
row_upd_step(thr);
|
||||||
|
|
||||||
err = trx->error_state;
|
err = trx->error_state;
|
||||||
|
|
||||||
|
/* Reset fk_cascade_depth back to 0 */
|
||||||
|
thr->fk_cascade_depth = 0;
|
||||||
|
|
||||||
if (err != DB_SUCCESS) {
|
if (err != DB_SUCCESS) {
|
||||||
que_thr_stop_for_mysql(thr);
|
que_thr_stop_for_mysql(thr);
|
||||||
|
|
||||||
@ -1597,6 +1607,12 @@ row_update_cascade_for_mysql(
|
|||||||
trx_t* trx;
|
trx_t* trx;
|
||||||
|
|
||||||
trx = thr_get_trx(thr);
|
trx = thr_get_trx(thr);
|
||||||
|
|
||||||
|
thr->fk_cascade_depth++;
|
||||||
|
|
||||||
|
if (thr->fk_cascade_depth > FK_MAX_CASCADE_DEL) {
|
||||||
|
return (DB_FOREIGN_EXCEED_MAX_CASCADE);
|
||||||
|
}
|
||||||
run_again:
|
run_again:
|
||||||
thr->run_node = node;
|
thr->run_node = node;
|
||||||
thr->prev_node = node;
|
thr->prev_node = node;
|
||||||
@ -2129,7 +2145,7 @@ row_table_add_foreign_constraints(
|
|||||||
|
|
||||||
if (err == DB_SUCCESS) {
|
if (err == DB_SUCCESS) {
|
||||||
/* Check that also referencing constraints are ok */
|
/* Check that also referencing constraints are ok */
|
||||||
err = dict_load_foreigns(name, TRUE);
|
err = dict_load_foreigns(name, FALSE, TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err != DB_SUCCESS) {
|
if (err != DB_SUCCESS) {
|
||||||
@ -3878,7 +3894,8 @@ end:
|
|||||||
an ALTER, not in a RENAME. */
|
an ALTER, not in a RENAME. */
|
||||||
|
|
||||||
err = dict_load_foreigns(
|
err = dict_load_foreigns(
|
||||||
new_name, old_is_tmp ? trx->check_foreigns : TRUE);
|
new_name, FALSE,
|
||||||
|
old_is_tmp ? trx->check_foreigns : TRUE);
|
||||||
|
|
||||||
if (err != DB_SUCCESS) {
|
if (err != DB_SUCCESS) {
|
||||||
ut_print_timestamp(stderr);
|
ut_print_timestamp(stderr);
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
2010-08-03 The InnoDB Team
|
||||||
|
|
||||||
|
* dict/dict0load.c, handler/ha_innodb.cc, include/db0err.h,
|
||||||
|
include/dict0load.h, include/dict0mem.h, include/que0que.h,
|
||||||
|
row/row0merge.c, row/row0mysql.c:
|
||||||
|
Fix Bug#54582 stack overflow when opening many tables linked
|
||||||
|
with foreign keys at once
|
||||||
|
|
||||||
2010-07-27 The InnoDB Team
|
2010-07-27 The InnoDB Team
|
||||||
|
|
||||||
* include/mem0pool.h, mem/mem0mem.c, mem/mem0pool.c, srv/srv0start.c:
|
* include/mem0pool.h, mem/mem0mem.c, mem/mem0pool.c, srv/srv0start.c:
|
||||||
|
@ -1009,16 +1009,27 @@ err_exit:
|
|||||||
|
|
||||||
err = dict_load_indexes(table, heap);
|
err = dict_load_indexes(table, heap);
|
||||||
|
|
||||||
|
/* Initialize table foreign_child value. Its value could be
|
||||||
|
changed when dict_load_foreigns() is called below */
|
||||||
|
table->fk_max_recusive_level = 0;
|
||||||
|
|
||||||
/* If the force recovery flag is set, we open the table irrespective
|
/* If the force recovery flag is set, we open the table irrespective
|
||||||
of the error condition, since the user may want to dump data from the
|
of the error condition, since the user may want to dump data from the
|
||||||
clustered index. However we load the foreign key information only if
|
clustered index. However we load the foreign key information only if
|
||||||
all indexes were loaded. */
|
all indexes were loaded. */
|
||||||
if (err == DB_SUCCESS) {
|
if (err == DB_SUCCESS) {
|
||||||
err = dict_load_foreigns(table->name, TRUE);
|
err = dict_load_foreigns(table->name, TRUE, TRUE);
|
||||||
|
|
||||||
|
if (err != DB_SUCCESS) {
|
||||||
|
dict_table_remove_from_cache(table);
|
||||||
|
table = NULL;
|
||||||
|
}
|
||||||
} else if (!srv_force_recovery) {
|
} else if (!srv_force_recovery) {
|
||||||
dict_table_remove_from_cache(table);
|
dict_table_remove_from_cache(table);
|
||||||
table = NULL;
|
table = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table->fk_max_recusive_level = 0;
|
||||||
#if 0
|
#if 0
|
||||||
if (err != DB_SUCCESS && table != NULL) {
|
if (err != DB_SUCCESS && table != NULL) {
|
||||||
|
|
||||||
@ -1241,8 +1252,12 @@ dict_load_foreign(
|
|||||||
/*==============*/
|
/*==============*/
|
||||||
const char* id, /*!< in: foreign constraint id as a
|
const char* id, /*!< in: foreign constraint id as a
|
||||||
null-terminated string */
|
null-terminated string */
|
||||||
ibool check_charsets)
|
ibool check_charsets,
|
||||||
/*!< in: TRUE=check charset compatibility */
|
/*!< in: TRUE=check charset compatibility */
|
||||||
|
ibool check_recursive)
|
||||||
|
/*!< in: Whether to record the foreign table
|
||||||
|
parent count to avoid unlimited recursive
|
||||||
|
load of chained foreign tables */
|
||||||
{
|
{
|
||||||
dict_foreign_t* foreign;
|
dict_foreign_t* foreign;
|
||||||
dict_table_t* sys_foreign;
|
dict_table_t* sys_foreign;
|
||||||
@ -1256,6 +1271,8 @@ dict_load_foreign(
|
|||||||
ulint len;
|
ulint len;
|
||||||
ulint n_fields_and_type;
|
ulint n_fields_and_type;
|
||||||
mtr_t mtr;
|
mtr_t mtr;
|
||||||
|
dict_table_t* for_table;
|
||||||
|
dict_table_t* ref_table;
|
||||||
|
|
||||||
ut_ad(mutex_own(&(dict_sys->mutex)));
|
ut_ad(mutex_own(&(dict_sys->mutex)));
|
||||||
|
|
||||||
@ -1340,11 +1357,54 @@ dict_load_foreign(
|
|||||||
|
|
||||||
dict_load_foreign_cols(id, foreign);
|
dict_load_foreign_cols(id, foreign);
|
||||||
|
|
||||||
/* If the foreign table is not yet in the dictionary cache, we
|
ref_table = dict_table_check_if_in_cache_low(
|
||||||
have to load it so that we are able to make type comparisons
|
foreign->referenced_table_name);
|
||||||
in the next function call. */
|
|
||||||
|
|
||||||
dict_table_get_low(foreign->foreign_table_name);
|
/* We could possibly wind up in a deep recursive calls if
|
||||||
|
we call dict_table_get_low() again here if there
|
||||||
|
is a chain of tables concatenated together with
|
||||||
|
foreign constraints. In such case, each table is
|
||||||
|
both a parent and child of the other tables, and
|
||||||
|
act as a "link" in such table chains.
|
||||||
|
To avoid such scenario, we would need to check the
|
||||||
|
number of ancesters the current table has. If that
|
||||||
|
exceeds DICT_FK_MAX_CHAIN_LEN, we will stop loading
|
||||||
|
the child table.
|
||||||
|
Foreign constraints are loaded in a Breath First fashion,
|
||||||
|
that is, the index on FOR_NAME is scanned first, and then
|
||||||
|
index on REF_NAME. So foreign constrains in which
|
||||||
|
current table is a child (foreign table) are loaded first,
|
||||||
|
and then those constraints where current table is a
|
||||||
|
parent (referenced) table.
|
||||||
|
Thus we could check the parent (ref_table) table's
|
||||||
|
reference count (fk_max_recusive_level) to know how deep the
|
||||||
|
recursive call is. If the parent table (ref_table) is already
|
||||||
|
loaded, and its fk_max_recusive_level is larger than
|
||||||
|
DICT_FK_MAX_CHAIN_LEN, we will stop the recursive loading
|
||||||
|
by skipping loading the child table. It will not affect foreign
|
||||||
|
constraint check for DMLs since child table will be loaded
|
||||||
|
at that time for the constraint check. */
|
||||||
|
if (!ref_table
|
||||||
|
|| ref_table->fk_max_recusive_level < DICT_FK_MAX_RECURSIVE_LOAD) {
|
||||||
|
|
||||||
|
/* If the foreign table is not yet in the dictionary cache, we
|
||||||
|
have to load it so that we are able to make type comparisons
|
||||||
|
in the next function call. */
|
||||||
|
|
||||||
|
for_table = dict_table_get_low(foreign->foreign_table_name);
|
||||||
|
|
||||||
|
if (for_table && ref_table && check_recursive) {
|
||||||
|
/* This is to record the longest chain of ancesters
|
||||||
|
this table has, if the parent has more ancesters
|
||||||
|
than this table has, record it after add 1 (for this
|
||||||
|
parent */
|
||||||
|
if (ref_table->fk_max_recusive_level
|
||||||
|
>= for_table->fk_max_recusive_level) {
|
||||||
|
for_table->fk_max_recusive_level =
|
||||||
|
ref_table->fk_max_recusive_level + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Note that there may already be a foreign constraint object in
|
/* Note that there may already be a foreign constraint object in
|
||||||
the dictionary cache for this constraint: then the following
|
the dictionary cache for this constraint: then the following
|
||||||
@ -1369,6 +1429,8 @@ ulint
|
|||||||
dict_load_foreigns(
|
dict_load_foreigns(
|
||||||
/*===============*/
|
/*===============*/
|
||||||
const char* table_name, /*!< in: table name */
|
const char* table_name, /*!< in: table name */
|
||||||
|
ibool check_recursive,/*!< in: Whether to check recursive
|
||||||
|
load of tables chained by FK */
|
||||||
ibool check_charsets) /*!< in: TRUE=check charset
|
ibool check_charsets) /*!< in: TRUE=check charset
|
||||||
compatibility */
|
compatibility */
|
||||||
{
|
{
|
||||||
@ -1470,7 +1532,7 @@ loop:
|
|||||||
|
|
||||||
/* Load the foreign constraint definition to the dictionary cache */
|
/* Load the foreign constraint definition to the dictionary cache */
|
||||||
|
|
||||||
err = dict_load_foreign(id, check_charsets);
|
err = dict_load_foreign(id, check_charsets, check_recursive);
|
||||||
|
|
||||||
if (err != DB_SUCCESS) {
|
if (err != DB_SUCCESS) {
|
||||||
btr_pcur_close(&pcur);
|
btr_pcur_close(&pcur);
|
||||||
@ -1498,6 +1560,11 @@ load_next_index:
|
|||||||
|
|
||||||
mtr_start(&mtr);
|
mtr_start(&mtr);
|
||||||
|
|
||||||
|
/* Switch to scan index on REF_NAME, fk_max_recusive_level
|
||||||
|
already been updated when scanning FOR_NAME index, no need to
|
||||||
|
update again */
|
||||||
|
check_recursive = FALSE;
|
||||||
|
|
||||||
goto start_load;
|
goto start_load;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -767,6 +767,19 @@ convert_error_code_to_mysql(
|
|||||||
case DB_INTERRUPTED:
|
case DB_INTERRUPTED:
|
||||||
my_error(ER_QUERY_INTERRUPTED, MYF(0));
|
my_error(ER_QUERY_INTERRUPTED, MYF(0));
|
||||||
/* fall through */
|
/* fall through */
|
||||||
|
|
||||||
|
case DB_FOREIGN_EXCEED_MAX_CASCADE:
|
||||||
|
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
|
||||||
|
HA_ERR_ROW_IS_REFERENCED,
|
||||||
|
"InnoDB: Cannot delete/update "
|
||||||
|
"rows with cascading foreign key "
|
||||||
|
"constraints that exceed max "
|
||||||
|
"depth of %d. Please "
|
||||||
|
"drop extra constraints and try "
|
||||||
|
"again", DICT_FK_MAX_RECURSIVE_LOAD);
|
||||||
|
|
||||||
|
/* fall through */
|
||||||
|
|
||||||
case DB_ERROR:
|
case DB_ERROR:
|
||||||
default:
|
default:
|
||||||
return(-1); /* unspecified error */
|
return(-1); /* unspecified error */
|
||||||
|
@ -94,6 +94,9 @@ enum db_err {
|
|||||||
|
|
||||||
DB_PRIMARY_KEY_IS_NULL, /* a column in the PRIMARY KEY
|
DB_PRIMARY_KEY_IS_NULL, /* a column in the PRIMARY KEY
|
||||||
was found to be NULL */
|
was found to be NULL */
|
||||||
|
DB_FOREIGN_EXCEED_MAX_CASCADE, /* Foreign key constraint related
|
||||||
|
cascading delete/update exceeds
|
||||||
|
maximum allowed depth */
|
||||||
|
|
||||||
/* The following are partial failure codes */
|
/* The following are partial failure codes */
|
||||||
DB_FAIL = 1000,
|
DB_FAIL = 1000,
|
||||||
|
@ -97,6 +97,8 @@ ulint
|
|||||||
dict_load_foreigns(
|
dict_load_foreigns(
|
||||||
/*===============*/
|
/*===============*/
|
||||||
const char* table_name, /*!< in: table name */
|
const char* table_name, /*!< in: table name */
|
||||||
|
ibool check_recursive,/*!< in: Whether to check recursive
|
||||||
|
load of tables chained by FK */
|
||||||
ibool check_charsets);/*!< in: TRUE=check charsets
|
ibool check_charsets);/*!< in: TRUE=check charsets
|
||||||
compatibility */
|
compatibility */
|
||||||
/********************************************************************//**
|
/********************************************************************//**
|
||||||
|
@ -112,6 +112,21 @@ ROW_FORMAT=REDUNDANT. */
|
|||||||
in table->flags. */
|
in table->flags. */
|
||||||
/* @} */
|
/* @} */
|
||||||
|
|
||||||
|
/** Tables could be chained together with Foreign key constraint. When
|
||||||
|
first load the parent table, we would load all of its descedents.
|
||||||
|
This could result in rescursive calls and out of stack error eventually.
|
||||||
|
DICT_FK_MAX_RECURSIVE_LOAD defines the maximum number of recursive loads,
|
||||||
|
when exceeded, the child table will not be loaded. It will be loaded when
|
||||||
|
the foreign constraint check needs to be run. */
|
||||||
|
#define DICT_FK_MAX_RECURSIVE_LOAD 250
|
||||||
|
|
||||||
|
/** Similarly, when tables are chained together with foreign key constraints
|
||||||
|
with on cascading delete/update clause, delete from parent table could
|
||||||
|
result in recursive cascading calls. This defines the maximum number of
|
||||||
|
such cascading deletes/updates allowed. When exceeded, the delete from
|
||||||
|
parent table will fail, and user has to drop excessive foreign constraint
|
||||||
|
before proceeds. */
|
||||||
|
#define FK_MAX_CASCADE_DEL 300
|
||||||
|
|
||||||
/**********************************************************************//**
|
/**********************************************************************//**
|
||||||
Creates a table memory object.
|
Creates a table memory object.
|
||||||
@ -434,6 +449,12 @@ struct dict_table_struct{
|
|||||||
NOT allowed until this count gets to zero;
|
NOT allowed until this count gets to zero;
|
||||||
MySQL does NOT itself check the number of
|
MySQL does NOT itself check the number of
|
||||||
open handles at drop */
|
open handles at drop */
|
||||||
|
unsigned fk_max_recusive_level:8;
|
||||||
|
/*!< maximum recursive level we support when
|
||||||
|
loading tables chained together with FK
|
||||||
|
constraints. If exceeds this level, we will
|
||||||
|
stop loading child table into memory along with
|
||||||
|
its parent table */
|
||||||
ulint n_foreign_key_checks_running;
|
ulint n_foreign_key_checks_running;
|
||||||
/*!< count of how many foreign key check
|
/*!< count of how many foreign key check
|
||||||
operations are currently being performed
|
operations are currently being performed
|
||||||
|
@ -381,6 +381,9 @@ struct que_thr_struct{
|
|||||||
thus far */
|
thus far */
|
||||||
ulint lock_state; /*!< lock state of thread (table or
|
ulint lock_state; /*!< lock state of thread (table or
|
||||||
row) */
|
row) */
|
||||||
|
ulint fk_cascade_depth; /*!< maximum cascading call depth
|
||||||
|
supported for foreign key constraint
|
||||||
|
related delete/updates */
|
||||||
};
|
};
|
||||||
|
|
||||||
#define QUE_THR_MAGIC_N 8476583
|
#define QUE_THR_MAGIC_N 8476583
|
||||||
|
@ -2395,7 +2395,7 @@ row_merge_rename_tables(
|
|||||||
goto err_exit;
|
goto err_exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = dict_load_foreigns(old_name, TRUE);
|
err = dict_load_foreigns(old_name, FALSE, TRUE);
|
||||||
|
|
||||||
if (err != DB_SUCCESS) {
|
if (err != DB_SUCCESS) {
|
||||||
err_exit:
|
err_exit:
|
||||||
|
@ -576,6 +576,13 @@ handle_new_error:
|
|||||||
"InnoDB: " REFMAN "forcing-recovery.html"
|
"InnoDB: " REFMAN "forcing-recovery.html"
|
||||||
" for help.\n", stderr);
|
" for help.\n", stderr);
|
||||||
break;
|
break;
|
||||||
|
case DB_FOREIGN_EXCEED_MAX_CASCADE:
|
||||||
|
fprintf(stderr, "InnoDB: Cannot delete/update rows with"
|
||||||
|
" cascading foreign key constraints that exceed max"
|
||||||
|
" depth of %lu\n"
|
||||||
|
"Please drop excessive foreign constraints"
|
||||||
|
" and try again\n", (ulong) DICT_FK_MAX_RECURSIVE_LOAD);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
fprintf(stderr, "InnoDB: unknown error code %lu\n",
|
fprintf(stderr, "InnoDB: unknown error code %lu\n",
|
||||||
(ulong) err);
|
(ulong) err);
|
||||||
@ -1381,11 +1388,15 @@ row_update_for_mysql(
|
|||||||
run_again:
|
run_again:
|
||||||
thr->run_node = node;
|
thr->run_node = node;
|
||||||
thr->prev_node = node;
|
thr->prev_node = node;
|
||||||
|
thr->fk_cascade_depth = 0;
|
||||||
|
|
||||||
row_upd_step(thr);
|
row_upd_step(thr);
|
||||||
|
|
||||||
err = trx->error_state;
|
err = trx->error_state;
|
||||||
|
|
||||||
|
/* Reset fk_cascade_depth back to 0 */
|
||||||
|
thr->fk_cascade_depth = 0;
|
||||||
|
|
||||||
if (err != DB_SUCCESS) {
|
if (err != DB_SUCCESS) {
|
||||||
que_thr_stop_for_mysql(thr);
|
que_thr_stop_for_mysql(thr);
|
||||||
|
|
||||||
@ -1576,6 +1587,12 @@ row_update_cascade_for_mysql(
|
|||||||
trx_t* trx;
|
trx_t* trx;
|
||||||
|
|
||||||
trx = thr_get_trx(thr);
|
trx = thr_get_trx(thr);
|
||||||
|
|
||||||
|
thr->fk_cascade_depth++;
|
||||||
|
|
||||||
|
if (thr->fk_cascade_depth > FK_MAX_CASCADE_DEL) {
|
||||||
|
return (DB_FOREIGN_EXCEED_MAX_CASCADE);
|
||||||
|
}
|
||||||
run_again:
|
run_again:
|
||||||
thr->run_node = node;
|
thr->run_node = node;
|
||||||
thr->prev_node = node;
|
thr->prev_node = node;
|
||||||
@ -2056,7 +2073,7 @@ row_table_add_foreign_constraints(
|
|||||||
name, reject_fks);
|
name, reject_fks);
|
||||||
if (err == DB_SUCCESS) {
|
if (err == DB_SUCCESS) {
|
||||||
/* Check that also referencing constraints are ok */
|
/* Check that also referencing constraints are ok */
|
||||||
err = dict_load_foreigns(name, TRUE);
|
err = dict_load_foreigns(name, FALSE, TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err != DB_SUCCESS) {
|
if (err != DB_SUCCESS) {
|
||||||
@ -3915,7 +3932,7 @@ end:
|
|||||||
an ALTER, not in a RENAME. */
|
an ALTER, not in a RENAME. */
|
||||||
|
|
||||||
err = dict_load_foreigns(
|
err = dict_load_foreigns(
|
||||||
new_name, !old_is_tmp || trx->check_foreigns);
|
new_name, FALSE, !old_is_tmp || trx->check_foreigns);
|
||||||
|
|
||||||
if (err != DB_SUCCESS) {
|
if (err != DB_SUCCESS) {
|
||||||
ut_print_timestamp(stderr);
|
ut_print_timestamp(stderr);
|
||||||
|
Reference in New Issue
Block a user