summaryrefslogtreecommitdiff
path: root/storage/innobase
diff options
context:
space:
mode:
Diffstat (limited to 'storage/innobase')
-rw-r--r--storage/innobase/dict/dict0load.c83
-rw-r--r--storage/innobase/handler/ha_innodb.cc10
-rw-r--r--storage/innobase/include/db0err.h3
-rw-r--r--storage/innobase/include/dict0load.h2
-rw-r--r--storage/innobase/include/dict0mem.h21
-rw-r--r--storage/innobase/include/que0que.h3
-rw-r--r--storage/innobase/row/row0mysql.c21
7 files changed, 133 insertions, 10 deletions
diff --git a/storage/innobase/dict/dict0load.c b/storage/innobase/dict/dict0load.c
index d5e7600f4d0..625956600c0 100644
--- a/storage/innobase/dict/dict0load.c
+++ b/storage/innobase/dict/dict0load.c
@@ -864,16 +864,27 @@ err_exit:
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
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
all indexes were loaded. */
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) {
dict_table_remove_from_cache(table);
table = NULL;
}
+
+ table->fk_max_recusive_level = 0;
#if 0
if (err != DB_SUCCESS && table != NULL) {
@@ -1095,8 +1106,12 @@ dict_load_foreign(
/* out: DB_SUCCESS or error code */
const char* id, /* in: foreign constraint id as a
null-terminated string */
- ibool check_charsets)
+ ibool check_charsets,
/* 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_table_t* sys_foreign;
@@ -1110,6 +1125,8 @@ dict_load_foreign(
ulint len;
ulint n_fields_and_type;
mtr_t mtr;
+ dict_table_t* for_table;
+ dict_table_t* ref_table;
ut_ad(mutex_own(&(dict_sys->mutex)));
@@ -1194,11 +1211,54 @@ dict_load_foreign(
dict_load_foreign_cols(id, foreign);
- /* 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. */
-
- dict_table_get_low(foreign->foreign_table_name);
+ ref_table = dict_table_check_if_in_cache_low(
+ foreign->referenced_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
the dictionary cache for this constraint: then the following
@@ -1223,6 +1283,8 @@ dict_load_foreigns(
/*===============*/
/* out: DB_SUCCESS or error code */
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
compatibility */
{
@@ -1324,7 +1386,7 @@ loop:
/* 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) {
btr_pcur_close(&pcur);
@@ -1352,6 +1414,11 @@ load_next_index:
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;
}
diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc
index d10fcb8d31e..930785d7dcd 100644
--- a/storage/innobase/handler/ha_innodb.cc
+++ b/storage/innobase/handler/ha_innodb.cc
@@ -763,6 +763,16 @@ convert_error_code_to_mysql(
my_error(ER_QUERY_INTERRUPTED, MYF(0));
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 {
return(-1); // Unknown error
}
diff --git a/storage/innobase/include/db0err.h b/storage/innobase/include/db0err.h
index 2be6005622d..af3e78fe833 100644
--- a/storage/innobase/include/db0err.h
+++ b/storage/innobase/include/db0err.h
@@ -73,6 +73,9 @@ Created 5/24/1996 Heikki Tuuri
a later version of the engine. */
#define DB_INTERRUPTED 49 /* the query has been interrupted with
"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 */
#define DB_FAIL 1000
diff --git a/storage/innobase/include/dict0load.h b/storage/innobase/include/dict0load.h
index 7e19c2eb3c0..eb6083e06f9 100644
--- a/storage/innobase/include/dict0load.h
+++ b/storage/innobase/include/dict0load.h
@@ -82,6 +82,8 @@ dict_load_foreigns(
/*===============*/
/* out: DB_SUCCESS or error code */
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
compatibility */
/************************************************************************
diff --git a/storage/innobase/include/dict0mem.h b/storage/innobase/include/dict0mem.h
index ac28fdb1bae..2f2a7441478 100644
--- a/storage/innobase/include/dict0mem.h
+++ b/storage/innobase/include/dict0mem.h
@@ -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_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 */
struct dict_table_struct{
@@ -339,6 +354,12 @@ struct dict_table_struct{
NOT allowed until this count gets to zero;
MySQL does NOT itself check the number of
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;
/* count of how many foreign key check
operations are currently being performed
diff --git a/storage/innobase/include/que0que.h b/storage/innobase/include/que0que.h
index 8fbf5330c89..71f4cfdfb8f 100644
--- a/storage/innobase/include/que0que.h
+++ b/storage/innobase/include/que0que.h
@@ -367,6 +367,9 @@ struct que_thr_struct{
thus far */
ulint lock_state; /* lock state of thread (table or
row) */
+ ulint fk_cascade_depth; /*!< maximum cascading call depth
+ supported for foreign key constraint
+ related delete/updates */
};
#define QUE_THR_MAGIC_N 8476583
diff --git a/storage/innobase/row/row0mysql.c b/storage/innobase/row/row0mysql.c
index 4a834c4efc2..3b76ffa76f1 100644
--- a/storage/innobase/row/row0mysql.c
+++ b/storage/innobase/row/row0mysql.c
@@ -555,6 +555,12 @@ handle_new_error:
"forcing-recovery.html"
" 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 {
fprintf(stderr, "InnoDB: unknown error code %lu\n",
(ulong) err);
@@ -1406,11 +1412,15 @@ row_update_for_mysql(
run_again:
thr->run_node = node;
thr->prev_node = node;
+ thr->fk_cascade_depth = 0;
row_upd_step(thr);
err = trx->error_state;
+ /* Reset fk_cascade_depth back to 0 */
+ thr->fk_cascade_depth = 0;
+
if (err != DB_SUCCESS) {
que_thr_stop_for_mysql(thr);
@@ -1597,6 +1607,12 @@ row_update_cascade_for_mysql(
trx_t* trx;
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:
thr->run_node = node;
thr->prev_node = node;
@@ -2129,7 +2145,7 @@ row_table_add_foreign_constraints(
if (err == DB_SUCCESS) {
/* Check that also referencing constraints are ok */
- err = dict_load_foreigns(name, TRUE);
+ err = dict_load_foreigns(name, FALSE, TRUE);
}
if (err != DB_SUCCESS) {
@@ -3878,7 +3894,8 @@ end:
an ALTER, not in a RENAME. */
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) {
ut_print_timestamp(stderr);