Skip to content

Commit d408eb3

Browse files
author
Dmitry Lenev
committed
Bug#27821060 "NEWDD FK: DROP TABLES/DATABASE SHOULD CHECK FOR FKS".
After we have started to store information about foreign keys in the New Data Dictionary it became possible to move checks of foreign key definition validity from the storage engine to SQL-layer and thus reduce code duplication and simplify SE implementation. This patch moves check that disallows dropping of parent table in a foreign key without prior dropping of child table from InnoDB SE to SQL-layer code implementing DROP TABLES/DROP DATABASE. Such check now happens before trying to delete any tables mentioned in DROP TABLES statement/belonging to schema to be dropped. So DROP TABLES/DROP DATABASE no longer have any side-effect even on tables in SEs which don't support atomic DDL when they fail due to this check. The check was also relaxed to allow dropping of parent and child table in arbitrary order as long as they are dropped by the same DROP TABLES statement. This is possible thanks to the fact that both our engines supporting foreign keys also support atomic DDL. Finally, error message which is emitted when user attempts to drop parent table without dropping child was changed to more verbose one. New test coverage for this check was added and existing tests were adjusted accordingly. Also, some test cases which previously used foreign key dependency as a way to fail DROP TABLES/DATABASE in the middle of their execution had to be adjusted to rely on error injection instead.
1 parent 7fcf993 commit d408eb3

38 files changed

+626
-467
lines changed

mysql-test/extra/binlog_tests/database.test

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
source include/have_log_bin.inc;
2+
source include/have_debug.inc;
23

34
# Checking that the drop of a database does not replicate anything in
45
# addition to the drop of the database
@@ -48,20 +49,20 @@ CREATE DATABASE testing_1;
4849
USE testing_1;
4950
CREATE TABLE t1(c1 INT) ENGINE=MyISAM;
5051
CREATE TABLE t2(c1 INT PRIMARY KEY) ENGINE=InnoDB;
51-
CREATE TABLE test.t1 (c1 INT, FOREIGN KEY(c1) REFERENCES testing_1.t2(c1)) ENGINE=InnoDB;
5252

5353
--echo #
5454
--echo # 'DROP DATABASE' will fail but will delete table t1.
5555
--echo #
5656

57-
--error ER_ROW_IS_REFERENCED
57+
SET SESSION DEBUG='+d,rm_table_no_locks_abort_after_atomic_tables';
58+
--error ER_UNKNOWN_ERROR
5859
DROP DATABASE testing_1;
60+
SET SESSION DEBUG='-d,rm_table_no_locks_abort_after_atomic_tables';
5961
let $wait_binlog_event= DROP TABLE IF EXIST;
6062
source include/wait_for_binlog_event.inc;
6163
let $MYSQLD_DATADIR= `SELECT @@datadir`;
6264

63-
DROP TABLE test.t1;
64-
--echo # Now we can drop the database.
65+
--echo # Cleanup.
6566
DROP DATABASE testing_1;
6667

6768

@@ -79,19 +80,18 @@ DROP TABLE IF EXISTS t3;
7980
CREATE DATABASE db1;
8081
CREATE TABLE db1.t1 (a INT) engine=innodb;
8182
CREATE TABLE db1.t2 (b INT, KEY(b)) engine=innodb;
82-
CREATE TABLE t3 (a INT, KEY (a), FOREIGN KEY(a) REFERENCES db1.t2(b))
83-
engine=innodb;
8483
RESET MASTER;
8584

86-
--error ER_ROW_IS_REFERENCED
87-
DROP DATABASE db1; # Fails because of the fk
85+
SET SESSION DEBUG='+d,rm_table_no_locks_abort_after_atomic_tables';
86+
--error ER_UNKNOWN_ERROR
87+
DROP DATABASE db1;
88+
SET SESSION DEBUG='-d,rm_table_no_locks_abort_after_atomic_tables';
8889
# Both t1 and t2 remain as the whole statement is rolled back.
8990
SHOW TABLES FROM db1;
9091
--let $mask_binlog_commit_events= 1
9192
--source include/show_binlog_events.inc # Check that the binlog is empty.
9293
--let $mask_binlog_commit_events= 0
9394

9495
# Cleanup
95-
DROP TABLE t3;
9696
DROP DATABASE db1;
9797
RESET MASTER;

mysql-test/extra/rpl_tests/rpl_split_statements.test

Lines changed: 6 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,10 @@
9797
# according to GTID_MODE.
9898
#
9999
# Third, we drop group of 4 tables, where 2 tables are in SE which
100-
# do not support atomic DDL and 2 in SE which support it and the
101-
# last table from the latter group can't be dropped. This triggers
102-
# different code path in DROP TABLE implementation. Again this is
103-
# done for both GTID_MODE='AUTOMATIC' and GTID_MODE='UUID:NUMBER'
104-
# to see difference in splitting/combining. Notice that with GTID
105-
# assigned even such failed DROP TABLE will consume GTID.
100+
# do not support atomic DDL and 2 in SE which support it and then
101+
# fail. This triggers code path in DROP TABLE implementation
102+
# which differs from case when there is no failure. This test has
103+
# been moved into rpl_split_statements_debug.test.
106104
#
107105
# Note that two last tests also provide coverage for changes to binary
108106
# logging and GTID handling for normal and failed DROP TABLES, which
@@ -332,93 +330,11 @@ if (!$gtid_mode_on)
332330
--source include/gtid_step_assert.inc
333331
}
334332

335-
--echo ==== Case 2D: Failing DROP TABLES for base tables with and without atomic DDL support ====
336-
337-
CREATE TABLE base_1_n (a INT) ENGINE = MyISAM;
338-
CREATE TABLE base_2_n (a INT) ENGINE = MyISAM;
339-
CREATE TABLE base_3_a (a INT) ENGINE = InnoDB;
340-
CREATE TABLE base_4_a (pk INT PRIMARY KEY) ENGINE = InnoDB;
341-
CREATE TABLE base_5_a (fk INT, FOREIGN KEY (fk) REFERENCES base_4_a(pk)) ENGINE = InnoDB;
342-
343-
--source include/rpl_sync.inc
344-
345-
--echo ---- GTID_MODE=AUTOMATIC ----
346-
347-
--source include/gtid_step_reset.inc
348-
349-
--error ER_ROW_IS_REFERENCED
350-
DROP TABLES base_1_n, base_2_n, base_3_a, base_4_a;
351-
352-
--echo # In AUTOMATIC mode the above statement should be split into
353-
--echo # two statements for each of MyISAM tables.
354-
--echo # There should be no statement for dropping base_3_a as its
355-
--echo # removal is rolled back.
356-
--let $gtid_step_count= 2.
357-
--source include/gtid_step_assert.inc
358-
359-
--echo # base_3_a and base_4_a should still be there on master
360-
SHOW CREATE TABLE base_3_a;
361-
SHOW CREATE TABLE base_4_a;
362-
363-
--source include/rpl_sync.inc
364-
365-
--connection slave
366-
367-
--echo # base_3_a and base_4_a should still be there on master
368-
SHOW CREATE TABLE base_3_a;
369-
SHOW CREATE TABLE base_4_a;
370-
371-
--connection master
372-
373-
CREATE TABLE base_1_n (a INT) ENGINE = MyISAM;
374-
CREATE TABLE base_2_n (a INT) ENGINE = MyISAM;
375-
376333
--source include/rpl_sync.inc
377334

378-
--echo ---- GTID_NEXT=non-automatic ----
379-
380-
--source include/gtid_step_reset.inc
381-
382-
--source include/set_gtid_next_gtid_mode_agnostic.inc
383-
384-
--error ER_ROW_IS_REFERENCED
385-
DROP TABLES base_1_n, base_2_n, base_3_a, base_4_a;
386-
387-
SET GTID_NEXT= 'AUTOMATIC';
388-
389-
if ($gtid_mode_on)
390-
{
391-
--echo # With GTID assigned the above statement should not be split.
392-
--let $gtid_step_count= 1
393-
--source include/gtid_step_assert.inc
394-
}
395-
if (!$gtid_mode_on)
396-
{
397-
--echo # Without GTID assigned the above statement should be split into
398-
--echo # two as in AUTOMATIC mode.
399-
--let $gtid_step_count= 2
400-
--source include/gtid_step_assert.inc
401-
}
402-
403-
--echo # base_3_a and base_4_a should still be there on master
404-
SHOW CREATE TABLE base_3_a;
405-
SHOW CREATE TABLE base_4_a;
406-
407-
--source include/rpl_sync.inc
408-
409-
--connection slave
410-
411-
--echo # base_3_a and base_4_a should still be there on master
412-
SHOW CREATE TABLE base_3_a;
413-
SHOW CREATE TABLE base_4_a;
414-
415-
--connection master
416-
417-
--echo ---- Clean up ----
418-
419-
DROP TABLE base_5_a, base_4_a, base_3_a;
335+
--echo ==== Case 2D: Failing DROP TABLES for base tables with and without atomic DDL support ====
420336

421-
--source include/rpl_sync.inc
337+
--echo See rpl_split_statements_debug.test
422338

423339
--echo ==== Case 3: DROP DATABASE ====
424340

mysql-test/extra/rpl_tests/rpl_split_statements_debug.test

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
11
# ==== Purpose ====
22
#
33
# This is part of rpl_split_statements.test which covers DROP DATABASE
4-
# statement.
4+
# statement and one scenario for DROP TABLES statement.
5+
#
6+
# ==============================================================================
7+
#
8+
# DROP TABLES: See detailed description of its behavior in
9+
# rpl_split_statements.test file.
10+
#
11+
# This file contains coverage for the following scenario:
12+
#
13+
# We drop group of 4 tables, where 2 tables are in SE which
14+
# do not support atomic DDL and 2 in SE which support it and
15+
# then fail. This triggers code path in DROP TABLE implementation
16+
# which differs from case when there is no failure.
17+
# This is done for both GTID_MODE='AUTOMATIC' and GTID_MODE='UUID:NUMBER'
18+
# to see difference in splitting/combining. Notice that with GTID
19+
# assigned even such failed DROP TABLE will consume GTID.
20+
#
21+
# ==============================================================================
522
#
623
# DROP DATABASE: this statement will first drop all non-temporary
724
# tables in the database, then stored routines and events in it,
@@ -52,6 +69,7 @@
5269
# This last scenario and scenario 3.6 also provide coverage for changes to
5370
# DROP DATABASE binary logging which were implemented as part of WL#7743
5471
# "New data dictionary: changes to DDL-related parts of SE API".
72+
#
5573

5674
# We use DBUG_EXECUTE_IF to simulate errors during DROP DATABASE.
5775
--source include/have_debug.inc
@@ -77,6 +95,99 @@ CALL mtr.add_suppression("Slave SQL for channel '': .* Error_code: MY-001105");
7795

7896
--let $gtid_step_gtid_mode_agnostic= 1
7997

98+
--echo ==== Case 2: DROP TABLES ====
99+
100+
--echo ==== Case 2D: Failing DROP TABLES for base tables with and without atomic DDL support ====
101+
102+
CREATE TABLE base_1_n (a INT) ENGINE = MyISAM;
103+
CREATE TABLE base_2_n (a INT) ENGINE = MyISAM;
104+
CREATE TABLE base_3_a (a INT) ENGINE = InnoDB;
105+
CREATE TABLE base_4_a (pk INT PRIMARY KEY) ENGINE = InnoDB;
106+
107+
--source include/rpl_sync.inc
108+
109+
--echo ---- GTID_MODE=AUTOMATIC ----
110+
111+
--source include/gtid_step_reset.inc
112+
113+
SET SESSION DEBUG='+d,rm_table_no_locks_abort_after_atomic_tables';
114+
--error ER_UNKNOWN_ERROR
115+
DROP TABLES base_1_n, base_2_n, base_3_a, base_4_a;
116+
SET SESSION DEBUG='-d,rm_table_no_locks_abort_after_atomic_tables';
117+
118+
--echo # In AUTOMATIC mode the above statement should be split into
119+
--echo # two statements for each of MyISAM tables.
120+
--echo # There should be no statement for dropping base_3_a or
121+
--echo # base_4_a as their removal is rolled back.
122+
--let $gtid_step_count= 2.
123+
--source include/gtid_step_assert.inc
124+
125+
--echo # base_3_a and base_4_a should still be there on master
126+
SHOW CREATE TABLE base_3_a;
127+
SHOW CREATE TABLE base_4_a;
128+
129+
--source include/rpl_sync.inc
130+
131+
--connection slave
132+
133+
--echo # base_3_a and base_4_a should still be there on slave
134+
SHOW CREATE TABLE base_3_a;
135+
SHOW CREATE TABLE base_4_a;
136+
137+
--connection master
138+
139+
CREATE TABLE base_1_n (a INT) ENGINE = MyISAM;
140+
CREATE TABLE base_2_n (a INT) ENGINE = MyISAM;
141+
142+
--source include/rpl_sync.inc
143+
144+
--echo ---- GTID_NEXT=non-automatic ----
145+
146+
--source include/gtid_step_reset.inc
147+
148+
--source include/set_gtid_next_gtid_mode_agnostic.inc
149+
150+
SET SESSION DEBUG='+d,rm_table_no_locks_abort_after_atomic_tables';
151+
--error ER_UNKNOWN_ERROR
152+
DROP TABLES base_1_n, base_2_n, base_3_a, base_4_a;
153+
SET SESSION DEBUG='-d,rm_table_no_locks_abort_after_atomic_tables';
154+
155+
SET GTID_NEXT= 'AUTOMATIC';
156+
157+
if ($gtid_mode_on)
158+
{
159+
--echo # With GTID assigned the above statement should not be split.
160+
--let $gtid_step_count= 1
161+
--source include/gtid_step_assert.inc
162+
}
163+
if (!$gtid_mode_on)
164+
{
165+
--echo # Without GTID assigned the above statement should be split into
166+
--echo # two as in AUTOMATIC mode.
167+
--let $gtid_step_count= 2
168+
--source include/gtid_step_assert.inc
169+
}
170+
171+
--echo # base_3_a and base_4_a should still be there on master
172+
SHOW CREATE TABLE base_3_a;
173+
SHOW CREATE TABLE base_4_a;
174+
175+
--source include/rpl_sync.inc
176+
177+
--connection slave
178+
179+
--echo # base_3_a and base_4_a should still be there on slave
180+
SHOW CREATE TABLE base_3_a;
181+
SHOW CREATE TABLE base_4_a;
182+
183+
--connection master
184+
185+
--echo ---- Clean up ----
186+
187+
DROP TABLE base_4_a, base_3_a;
188+
189+
--source include/rpl_sync.inc
190+
80191
--echo ==== Case 3: DROP DATABASE ====
81192

82193
--echo ---- Initialize ----

mysql-test/r/drop_debug.result

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,20 +77,17 @@ ERROR 42S02: Table 'test.tt_m' doesn't exist
7777
SELECT * FROM tt_i;
7878
ERROR 42S02: Table 'test.tt_i' doesn't exist
7979
#
80-
# 1.c) DROP TABLES which fails due to foreign key error might
81-
# have side effect. This might change once WL#6049 is
82-
# implemented and we support smarter checks while dropping
83-
# tables with foreign keys. Until this side-effect is
84-
# limited to non-atomic SEs only.
80+
# 1.c) DROP TABLES which fails due to foreign key error does
81+
# not have side effect.
8582
CREATE TABLE t_m (t_m INT) ENGINE=MyISAM;
8683
CREATE TABLE t_i_1 (t_i_1 INT) ENGINE=InnoDB;
8784
CREATE TABLE t_i_2 (t_i_2 INT PRIMARY KEY) ENGINE=InnoDB;
8885
CREATE TABLE t_i_3 (t_i_3 INT, FOREIGN KEY(t_i_3) REFERENCES t_i_2(t_i_2)) ENGINE=InnoDB;
8986
DROP TABLES t_m, t_i_1, t_i_2;
90-
ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails
91-
# Table t_m is gone, t_i_1 and t_i_2 are still there.
87+
ERROR HY000: Cannot drop table 't_i_2' referenced by a foreign key constraint 't_i_3_ibfk_1' on table 't_i_3'.
88+
# All tables are still there.
9289
SELECT * FROM t_m;
93-
ERROR 42S02: Table 'test.t_m' doesn't exist
90+
t_m
9491
SELECT * FROM t_i_1;
9592
t_i_1
9693
SELECT * FROM t_i_2;
@@ -100,7 +97,6 @@ t_i_2
10097
# effect. Tables in engines which do not support atomic DDL
10198
# which we have managed to drop before error stay dropped.
10299
# Removal of InnoDB tables should be rolled back.
103-
CREATE TABLE t_m (t_m INT) ENGINE=MyISAM;
104100
SET SESSION DEBUG='+d,rm_table_no_locks_abort_after_atomic_tables';
105101
DROP TABLES t_m, t_i_1;
106102
ERROR HY000: Unknown error
@@ -157,9 +153,8 @@ Warning 1017 Can't find file: 't1' (errno: 2 - No such file or directory)
157153
# 4) Error handling by DROP DATABASE.
158154
#
159155
#
160-
# 4.a) DROP DATABASE which fails due to foreign key error might
161-
# have side effect. This side-effect is limited to non-atomic
162-
# SEs only.
156+
# 4.a) DROP DATABASE which fails due to foreign key error should
157+
# not have side effect.
163158
CREATE DATABASE mysqltest;
164159
CREATE TABLE mysqltest.t_m (t_m INT) ENGINE=MyISAM;
165160
CREATE TABLE mysqltest.t_i_1 (t_i_1 INT) ENGINE= InnoDB;
@@ -169,11 +164,10 @@ CREATE TABLE t1 (fk INT,
169164
FOREIGN KEY (fk) REFERENCES mysqltest.t_i_2(t_i_2))
170165
ENGINE=InnoDB;
171166
DROP DATABASE mysqltest;
172-
ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails
173-
# Table t_m is gone.
167+
ERROR HY000: Cannot drop table 't_i_2' referenced by a foreign key constraint 't1_ibfk_1' on table 't1'.
168+
# Database and all tables are still there.
174169
SELECT * FROM mysqltest.t_m;
175-
ERROR 42S02: Table 'mysqltest.t_m' doesn't exist
176-
# Database and tables t_i_1, t_i_2 are still there.
170+
t_m
177171
SELECT * FROM mysqltest.t_i_1;
178172
t_i_1
179173
SELECT * FROM mysqltest.t_i_2;
@@ -188,7 +182,6 @@ mysqltest.f1()
188182
# which we have managed to drop before error stay dropped.
189183
# Removal of InnoDB tables should be rolled back.
190184
DROP TABLE t1;
191-
CREATE TABLE mysqltest.t_m (t_m INT) ENGINE=MyISAM;
192185
SET SESSION DEBUG='+d,rm_db_fail_after_dropping_tables';
193186
DROP DATABASE mysqltest;
194187
ERROR HY000: Unknown error

0 commit comments

Comments
 (0)