Skip to content

Commit 837bbba

Browse files
MDEV-23470 InnoDB: Failing assertion: cmp < 0 in row_ins_check_foreign_constraint
During insertion of clustered index, InnoDB does the check for foreign key constraints. Problem is that it uses the clustered index entry to search indexes of referenced tables and it could lead to unexpected result when there is no foreign index. Solution: ======== Rebuild the tuple based on foreign column names before searching it on reference index when there is no foreign index.
1 parent 054f96e commit 837bbba

File tree

3 files changed

+100
-9
lines changed

3 files changed

+100
-9
lines changed

mysql-test/suite/innodb/r/foreign-keys.result

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,19 @@ drop table t1,t2;
220220
ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails
221221
drop table t1,t2;
222222
ERROR 42S02: Unknown table 'test.t2'
223+
#
224+
# MDEV-23470 InnoDB: Failing assertion: cmp < 0 in
225+
# row_ins_check_foreign_constraint
226+
#
227+
CREATE TABLE t1(f1 INT NOT NULL PRIMARY KEY, f2 INT NOT NULL)ENGINE=InnoDB;
228+
CREATE TABLE t2(f1 VARCHAR(100), f2 INT NOT NULL,
229+
INDEX(f2))ENGINE=InnoDB;
230+
INSERT INTO t1 VALUES(99, 2);
231+
ALTER TABLE t2 ADD FOREIGN KEY(f2) REFERENCES t1(f1);
232+
SET FOREIGN_KEY_CHECKS=0;
233+
DROP INDEX f2 ON t2;
234+
SET FOREIGN_KEY_CHECKS=1;
235+
INSERT INTO t2 VALUES('G', 3);
236+
ERROR 23000: Cannot add or update a child row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `t2_ibfk_1` FOREIGN KEY (`f2`) REFERENCES `t1` (`f1`))
237+
DROP TABLE t2, t1;
238+
SET FOREIGN_KEY_CHECKS=DEFAULT;

mysql-test/suite/innodb/t/foreign-keys.test

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,22 @@ show create table t2;
250250
drop table t1,t2;
251251
--error ER_BAD_TABLE_ERROR
252252
drop table t1,t2;
253+
254+
--echo #
255+
--echo # MDEV-23470 InnoDB: Failing assertion: cmp < 0 in
256+
--echo # row_ins_check_foreign_constraint
257+
--echo #
258+
CREATE TABLE t1(f1 INT NOT NULL PRIMARY KEY, f2 INT NOT NULL)ENGINE=InnoDB;
259+
CREATE TABLE t2(f1 VARCHAR(100), f2 INT NOT NULL,
260+
INDEX(f2))ENGINE=InnoDB;
261+
262+
INSERT INTO t1 VALUES(99, 2);
263+
ALTER TABLE t2 ADD FOREIGN KEY(f2) REFERENCES t1(f1);
264+
265+
SET FOREIGN_KEY_CHECKS=0;
266+
DROP INDEX f2 ON t2;
267+
SET FOREIGN_KEY_CHECKS=1;
268+
--error ER_NO_REFERENCED_ROW_2
269+
INSERT INTO t2 VALUES('G', 3);
270+
DROP TABLE t2, t1;
271+
SET FOREIGN_KEY_CHECKS=DEFAULT;

storage/innobase/row/row0ins.cc

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1905,6 +1905,39 @@ row_ins_check_foreign_constraint(
19051905
DBUG_RETURN(err);
19061906
}
19071907

1908+
/** Sets the values of the dtuple fields in ref_entry from the values of
1909+
foreign columns in entry.
1910+
@param[in] foreign foreign key constraint
1911+
@param[in] index clustered index
1912+
@param[in] entry tuple of clustered index
1913+
@param[in] ref_entry tuple of foreign columns
1914+
@return true if all foreign key fields present in clustered index */
1915+
static
1916+
bool row_ins_foreign_index_entry(dict_foreign_t *foreign,
1917+
const dict_index_t *index,
1918+
const dtuple_t *entry,
1919+
dtuple_t *ref_entry)
1920+
{
1921+
for (ulint i= 0; i < foreign->n_fields; i++)
1922+
{
1923+
for (ulint j= 0; j < index->n_fields; j++)
1924+
{
1925+
const char *col_name= dict_table_get_col_name(
1926+
index->table, dict_index_get_nth_col_no(index, j));
1927+
if (0 == innobase_strcasecmp(col_name, foreign->foreign_col_names[i]))
1928+
{
1929+
dfield_copy(&ref_entry->fields[i], &entry->fields[j]);
1930+
goto got_match;
1931+
}
1932+
}
1933+
return false;
1934+
got_match:
1935+
continue;
1936+
}
1937+
1938+
return true;
1939+
}
1940+
19081941
/***************************************************************//**
19091942
Checks if foreign key constraints fail for an index entry. If index
19101943
is not mentioned in any constraint, this function does nothing,
@@ -1923,9 +1956,10 @@ row_ins_check_foreign_constraints(
19231956
que_thr_t* thr)/*!< in: query thread */
19241957
{
19251958
dict_foreign_t* foreign;
1926-
dberr_terr;
1959+
dberr_terr = DB_SUCCESS;
19271960
trx_t* trx;
19281961
ibool got_s_lock = FALSE;
1962+
mem_heap_t* heap = NULL;
19291963

19301964
DBUG_ASSERT(index->is_primary() == pk);
19311965

@@ -1935,13 +1969,36 @@ row_ins_check_foreign_constraints(
19351969
"foreign_constraint_check_for_ins");
19361970

19371971
for (dict_foreign_set::iterator it = table->foreign_set.begin();
1938-
it != table->foreign_set.end();
1972+
err == DB_SUCCESS && it != table->foreign_set.end();
19391973
++it) {
19401974

19411975
foreign = *it;
19421976

19431977
if (foreign->foreign_index == index
19441978
|| (pk && !foreign->foreign_index)) {
1979+
1980+
dtuple_t* ref_tuple = entry;
1981+
if (UNIV_UNLIKELY(!foreign->foreign_index)) {
1982+
/* Change primary key entry to
1983+
foreign key index entry */
1984+
if (!heap) {
1985+
heap = mem_heap_create(1000);
1986+
} else {
1987+
mem_heap_empty(heap);
1988+
}
1989+
1990+
ref_tuple = dtuple_create(
1991+
heap, foreign->n_fields);
1992+
dtuple_set_n_fields_cmp(
1993+
ref_tuple, foreign->n_fields);
1994+
if (!row_ins_foreign_index_entry(
1995+
foreign, index, entry, ref_tuple)) {
1996+
err = DB_NO_REFERENCED_ROW;
1997+
break;
1998+
}
1999+
2000+
}
2001+
19452002
dict_table_t* ref_table = NULL;
19462003
dict_table_t* referenced_table
19472004
= foreign->referenced_table;
@@ -1971,7 +2028,7 @@ row_ins_check_foreign_constraints(
19712028
table from being dropped while the check is running. */
19722029

19732030
err = row_ins_check_foreign_constraint(
1974-
TRUE, foreign, table, entry, thr);
2031+
TRUE, foreign, table, ref_tuple, thr);
19752032

19762033
if (referenced_table) {
19772034
my_atomic_addlint(
@@ -1986,15 +2043,14 @@ row_ins_check_foreign_constraints(
19862043
if (ref_table != NULL) {
19872044
dict_table_close(ref_table, FALSE, FALSE);
19882045
}
1989-
1990-
if (err != DB_SUCCESS) {
1991-
1992-
return(err);
1993-
}
19942046
}
19952047
}
19962048

1997-
return(DB_SUCCESS);
2049+
if (UNIV_LIKELY_NULL(heap)) {
2050+
mem_heap_free(heap);
2051+
}
2052+
2053+
return err;
19982054
}
19992055

20002056
/***************************************************************//**

0 commit comments

Comments
 (0)