Skip to content

Commit dd51006

Browse files
author
Konstantin Osipov
committed
A fix and a test case for Bug#47648 "main.merge fails sporadically".
If a prepared statement used both a MyISAMMRG table and a stored function or trigger, execution could fail with "No such table" error or crash. The error would come from a failure of the MyISAMMRG engine to meet the expectations of the prelocking algorithm, in particular maintain lex->query_tables_own_last pointer in sync with lex->query_tables_last pointer/the contents of lex->query_tables. When adding merge children, the merge engine would extend the table list. Then, when adding prelocked tables, the prelocking algorithm would use a pointer to the last merge child to assign to lex->query_tables_own_last. Then, when merge children were removed at the end of open_tables(), lex->query_tables_own_last was not updated, and kept pointing to a removed merge child. The fix ensures that query_tables_own_last is always in sync with lex->query_tables_last. This is a regression introduced by WL#4144 and present only in next-4284 tree and 6.0.
1 parent 7547912 commit dd51006

File tree

3 files changed

+134
-8
lines changed

3 files changed

+134
-8
lines changed

mysql-test/r/merge.result

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2466,7 +2466,9 @@ UNLOCK TABLES;
24662466
DROP TRIGGER t2_ai;
24672467
DROP TABLE tm1, t1, t2;
24682468
#
2469-
# Don't select MERGE child when trying to get prelocked table.
2469+
# Don't allow an update of a MERGE child in a trigger
2470+
# if the table's already being modified by the main
2471+
# statement.
24702472
#
24712473
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
24722474
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
@@ -2475,19 +2477,45 @@ CREATE TRIGGER tm1_ai AFTER INSERT ON tm1
24752477
FOR EACH ROW INSERT INTO t1 VALUES(11);
24762478
LOCK TABLE tm1 WRITE, t1 WRITE;
24772479
INSERT INTO tm1 VALUES (1);
2480+
ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
2481+
SELECT * FROM tm1;
2482+
c1
2483+
1
2484+
UNLOCK TABLES;
2485+
LOCK TABLE t1 WRITE, tm1 WRITE;
2486+
INSERT INTO tm1 VALUES (1);
2487+
ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
2488+
SELECT * FROM tm1;
2489+
c1
2490+
1
2491+
1
2492+
UNLOCK TABLES;
2493+
DROP TRIGGER tm1_ai;
2494+
DROP TABLE tm1, t1;
2495+
#
2496+
# Don't select MERGE child when trying to get a prelocked table.
2497+
#
2498+
# Due to a limitation demonstrated by the previous test
2499+
# we can no longer use a write-locked prelocked table.
2500+
# The test is kept for historical purposes.
2501+
#
2502+
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
2503+
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
2504+
INSERT_METHOD=LAST;
2505+
CREATE TRIGGER tm1_ai AFTER INSERT ON tm1
2506+
FOR EACH ROW SELECT max(c1) FROM t1 INTO @var;
2507+
LOCK TABLE tm1 WRITE, t1 WRITE;
2508+
INSERT INTO tm1 VALUES (1);
24782509
SELECT * FROM tm1;
24792510
c1
24802511
1
2481-
11
24822512
UNLOCK TABLES;
24832513
LOCK TABLE t1 WRITE, tm1 WRITE;
24842514
INSERT INTO tm1 VALUES (1);
24852515
SELECT * FROM tm1;
24862516
c1
24872517
1
2488-
11
24892518
1
2490-
11
24912519
UNLOCK TABLES;
24922520
DROP TRIGGER tm1_ai;
24932521
DROP TABLE tm1, t1;
@@ -2499,7 +2527,7 @@ CREATE TABLE t5 (c1 INT) ENGINE=MyISAM;
24992527
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2,t3,t4,t5)
25002528
INSERT_METHOD=LAST;
25012529
CREATE TRIGGER t2_au AFTER UPDATE ON t2
2502-
FOR EACH ROW INSERT INTO t3 VALUES(33);
2530+
FOR EACH ROW SELECT MAX(c1) FROM t1 INTO @var;
25032531
CREATE FUNCTION f1() RETURNS INT
25042532
RETURN (SELECT MAX(c1) FROM t4);
25052533
LOCK TABLE tm1 WRITE, t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE, t5 WRITE;
@@ -2517,7 +2545,6 @@ c1
25172545
1
25182546
4
25192547
3
2520-
33
25212548
4
25222549
5
25232550
DROP TRIGGER t2_au;
@@ -2553,4 +2580,29 @@ ERROR HY000: Unable to open underlying table which is differently defined or of
25532580
DROP TABLE t1, t3;
25542581
# Connection default.
25552582
# Disconnecting con1, all mdl_tickets must have been released.
2583+
#
2584+
# A test case for Bug#47648 main.merge fails sporadically
2585+
#
2586+
# Make sure we correctly maintain lex->query_tables_last_own.
2587+
#
2588+
create table t1 (c1 int not null);
2589+
create table t2 (c1 int not null);
2590+
create table t3 (c1 int not null);
2591+
create function f1 () returns int return (select max(c1) from t3);
2592+
create table t4 (c1 int not null) engine=merge union=(t1,t2) insert_method=last ;
2593+
select * from t4 where c1 < f1();
2594+
c1
2595+
prepare stmt from "select * from t4 where c1 < f1()";
2596+
execute stmt;
2597+
c1
2598+
execute stmt;
2599+
c1
2600+
execute stmt;
2601+
c1
2602+
drop function f1;
2603+
execute stmt;
2604+
ERROR 42000: FUNCTION test.f1 does not exist
2605+
execute stmt;
2606+
ERROR 42000: FUNCTION test.f1 does not exist
2607+
drop table t4, t3, t2, t1;
25562608
End of 6.0 tests

mysql-test/t/merge.test

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1941,14 +1941,41 @@ UNLOCK TABLES;
19411941
DROP TRIGGER t2_ai;
19421942
DROP TABLE tm1, t1, t2;
19431943
--echo #
1944-
--echo # Don't select MERGE child when trying to get prelocked table.
1944+
--echo # Don't allow an update of a MERGE child in a trigger
1945+
--echo # if the table's already being modified by the main
1946+
--echo # statement.
19451947
--echo #
19461948
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
19471949
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
19481950
INSERT_METHOD=LAST;
19491951
CREATE TRIGGER tm1_ai AFTER INSERT ON tm1
19501952
FOR EACH ROW INSERT INTO t1 VALUES(11);
19511953
LOCK TABLE tm1 WRITE, t1 WRITE;
1954+
--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG
1955+
INSERT INTO tm1 VALUES (1);
1956+
SELECT * FROM tm1;
1957+
UNLOCK TABLES;
1958+
LOCK TABLE t1 WRITE, tm1 WRITE;
1959+
--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG
1960+
INSERT INTO tm1 VALUES (1);
1961+
SELECT * FROM tm1;
1962+
UNLOCK TABLES;
1963+
DROP TRIGGER tm1_ai;
1964+
DROP TABLE tm1, t1;
1965+
1966+
--echo #
1967+
--echo # Don't select MERGE child when trying to get a prelocked table.
1968+
--echo #
1969+
--echo # Due to a limitation demonstrated by the previous test
1970+
--echo # we can no longer use a write-locked prelocked table.
1971+
--echo # The test is kept for historical purposes.
1972+
--echo #
1973+
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
1974+
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
1975+
INSERT_METHOD=LAST;
1976+
CREATE TRIGGER tm1_ai AFTER INSERT ON tm1
1977+
FOR EACH ROW SELECT max(c1) FROM t1 INTO @var;
1978+
LOCK TABLE tm1 WRITE, t1 WRITE;
19521979
INSERT INTO tm1 VALUES (1);
19531980
SELECT * FROM tm1;
19541981
UNLOCK TABLES;
@@ -1970,7 +1997,7 @@ CREATE TABLE t5 (c1 INT) ENGINE=MyISAM;
19701997
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2,t3,t4,t5)
19711998
INSERT_METHOD=LAST;
19721999
CREATE TRIGGER t2_au AFTER UPDATE ON t2
1973-
FOR EACH ROW INSERT INTO t3 VALUES(33);
2000+
FOR EACH ROW SELECT MAX(c1) FROM t1 INTO @var;
19742001
CREATE FUNCTION f1() RETURNS INT
19752002
RETURN (SELECT MAX(c1) FROM t4);
19762003
LOCK TABLE tm1 WRITE, t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE, t5 WRITE;
@@ -2037,4 +2064,30 @@ connection default;
20372064
--echo # Disconnecting con1, all mdl_tickets must have been released.
20382065
disconnect con1;
20392066

2067+
--echo #
2068+
--echo # A test case for Bug#47648 main.merge fails sporadically
2069+
--echo #
2070+
--echo # Make sure we correctly maintain lex->query_tables_last_own.
2071+
--echo #
2072+
create table t1 (c1 int not null);
2073+
create table t2 (c1 int not null);
2074+
create table t3 (c1 int not null);
2075+
2076+
create function f1 () returns int return (select max(c1) from t3);
2077+
2078+
create table t4 (c1 int not null) engine=merge union=(t1,t2) insert_method=last ;
2079+
2080+
select * from t4 where c1 < f1();
2081+
prepare stmt from "select * from t4 where c1 < f1()";
2082+
execute stmt;
2083+
execute stmt;
2084+
execute stmt;
2085+
drop function f1;
2086+
--error ER_SP_DOES_NOT_EXIST
2087+
execute stmt;
2088+
--error ER_SP_DOES_NOT_EXIST
2089+
execute stmt;
2090+
drop table t4, t3, t2, t1;
2091+
20402092
--echo End of 6.0 tests
2093+

storage/myisammrg/ha_myisammrg.cc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,18 @@ int ha_myisammrg::add_children_list(void)
458458
*/
459459
if (thd->lex->query_tables_last == &parent_l->next_global)
460460
thd->lex->query_tables_last= this->children_last_l;
461+
/*
462+
The branch below works only when re-executing a prepared
463+
statement or a stored routine statement:
464+
We've just modified query_tables_last. Keep it in sync with
465+
query_tables_last_own, if it was set by the prelocking code.
466+
This ensures that the check that prohibits double updates (*)
467+
can correctly identify what tables belong to the main statement.
468+
(*) A double update is, e.g. when a user issues UPDATE t1 and
469+
t1 has an AFTER UPDATE trigger that also modifies t1.
470+
*/
471+
if (thd->lex->query_tables_own_last == &parent_l->next_global)
472+
thd->lex->query_tables_own_last= this->children_last_l;
461473

462474
end:
463475
DBUG_RETURN(0);
@@ -888,6 +900,15 @@ int ha_myisammrg::detach_children(void)
888900
if (thd->lex->query_tables_last == this->children_last_l)
889901
thd->lex->query_tables_last= this->children_l->prev_global;
890902

903+
/*
904+
If the statement requires prelocking, and prelocked
905+
tables were added right after merge children, modify the
906+
last own table pointer to point at prev_global of the merge
907+
parent.
908+
*/
909+
if (thd->lex->query_tables_own_last == this->children_last_l)
910+
thd->lex->query_tables_own_last= this->children_l->prev_global;
911+
891912
/* Terminate child list. So it cannot be tried to remove again. */
892913
*this->children_last_l= NULL;
893914
this->children_l->prev_global= NULL;

0 commit comments

Comments
 (0)