Commit 3a856d44 authored by Konstantin Osipov's avatar Konstantin Osipov

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.

mysql-test/r/merge.result:
  Update results (Bug#47648).
mysql-test/t/merge.test:
  Add a test case for Bug#47648.
  Update the result file to reflect a fix of another bug
  in MyISAMMRG code: not maintaining lex->query_tables_own_last
  allowed a stored function or trigger to modify a merge table
  which was already updated by the main statement.
  It is not allowed for other storage engines, and should
  not be allowed for MyISAMMRG.
storage/myisammrg/ha_myisammrg.cc:
  When adding children to the list of tables to open,
  make sure that we properly set lex->query_tables_own_last.
  When removing the children, update lex->query_tables_own_last
  if necessary.
parent c15886a1
...@@ -2466,7 +2466,9 @@ UNLOCK TABLES; ...@@ -2466,7 +2466,9 @@ UNLOCK TABLES;
DROP TRIGGER t2_ai; DROP TRIGGER t2_ai;
DROP TABLE tm1, t1, t2; DROP TABLE tm1, t1, t2;
# #
# Don't select MERGE child when trying to get prelocked table. # Don't allow an update of a MERGE child in a trigger
# if the table's already being modified by the main
# statement.
# #
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
...@@ -2475,19 +2477,45 @@ CREATE TRIGGER tm1_ai AFTER INSERT ON tm1 ...@@ -2475,19 +2477,45 @@ CREATE TRIGGER tm1_ai AFTER INSERT ON tm1
FOR EACH ROW INSERT INTO t1 VALUES(11); FOR EACH ROW INSERT INTO t1 VALUES(11);
LOCK TABLE tm1 WRITE, t1 WRITE; LOCK TABLE tm1 WRITE, t1 WRITE;
INSERT INTO tm1 VALUES (1); INSERT INTO tm1 VALUES (1);
ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
SELECT * FROM tm1;
c1
1
UNLOCK TABLES;
LOCK TABLE t1 WRITE, tm1 WRITE;
INSERT INTO tm1 VALUES (1);
ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
SELECT * FROM tm1;
c1
1
1
UNLOCK TABLES;
DROP TRIGGER tm1_ai;
DROP TABLE tm1, t1;
#
# Don't select MERGE child when trying to get a prelocked table.
#
# Due to a limitation demonstrated by the previous test
# we can no longer use a write-locked prelocked table.
# The test is kept for historical purposes.
#
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
INSERT_METHOD=LAST;
CREATE TRIGGER tm1_ai AFTER INSERT ON tm1
FOR EACH ROW SELECT max(c1) FROM t1 INTO @var;
LOCK TABLE tm1 WRITE, t1 WRITE;
INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1; SELECT * FROM tm1;
c1 c1
1 1
11
UNLOCK TABLES; UNLOCK TABLES;
LOCK TABLE t1 WRITE, tm1 WRITE; LOCK TABLE t1 WRITE, tm1 WRITE;
INSERT INTO tm1 VALUES (1); INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1; SELECT * FROM tm1;
c1 c1
1 1
11
1 1
11
UNLOCK TABLES; UNLOCK TABLES;
DROP TRIGGER tm1_ai; DROP TRIGGER tm1_ai;
DROP TABLE tm1, t1; DROP TABLE tm1, t1;
...@@ -2499,7 +2527,7 @@ CREATE TABLE t5 (c1 INT) ENGINE=MyISAM; ...@@ -2499,7 +2527,7 @@ CREATE TABLE t5 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2,t3,t4,t5) CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2,t3,t4,t5)
INSERT_METHOD=LAST; INSERT_METHOD=LAST;
CREATE TRIGGER t2_au AFTER UPDATE ON t2 CREATE TRIGGER t2_au AFTER UPDATE ON t2
FOR EACH ROW INSERT INTO t3 VALUES(33); FOR EACH ROW SELECT MAX(c1) FROM t1 INTO @var;
CREATE FUNCTION f1() RETURNS INT CREATE FUNCTION f1() RETURNS INT
RETURN (SELECT MAX(c1) FROM t4); RETURN (SELECT MAX(c1) FROM t4);
LOCK TABLE tm1 WRITE, t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE, t5 WRITE; LOCK TABLE tm1 WRITE, t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE, t5 WRITE;
...@@ -2517,7 +2545,6 @@ c1 ...@@ -2517,7 +2545,6 @@ c1
1 1
4 4
3 3
33
4 4
5 5
DROP TRIGGER t2_au; DROP TRIGGER t2_au;
...@@ -2553,4 +2580,29 @@ ERROR HY000: Unable to open underlying table which is differently defined or of ...@@ -2553,4 +2580,29 @@ ERROR HY000: Unable to open underlying table which is differently defined or of
DROP TABLE t1, t3; DROP TABLE t1, t3;
# Connection default. # Connection default.
# Disconnecting con1, all mdl_tickets must have been released. # Disconnecting con1, all mdl_tickets must have been released.
#
# A test case for Bug#47648 main.merge fails sporadically
#
# Make sure we correctly maintain lex->query_tables_last_own.
#
create table t1 (c1 int not null);
create table t2 (c1 int not null);
create table t3 (c1 int not null);
create function f1 () returns int return (select max(c1) from t3);
create table t4 (c1 int not null) engine=merge union=(t1,t2) insert_method=last ;
select * from t4 where c1 < f1();
c1
prepare stmt from "select * from t4 where c1 < f1()";
execute stmt;
c1
execute stmt;
c1
execute stmt;
c1
drop function f1;
execute stmt;
ERROR 42000: FUNCTION test.f1 does not exist
execute stmt;
ERROR 42000: FUNCTION test.f1 does not exist
drop table t4, t3, t2, t1;
End of 6.0 tests End of 6.0 tests
...@@ -1941,7 +1941,9 @@ UNLOCK TABLES; ...@@ -1941,7 +1941,9 @@ UNLOCK TABLES;
DROP TRIGGER t2_ai; DROP TRIGGER t2_ai;
DROP TABLE tm1, t1, t2; DROP TABLE tm1, t1, t2;
--echo # --echo #
--echo # Don't select MERGE child when trying to get prelocked table. --echo # Don't allow an update of a MERGE child in a trigger
--echo # if the table's already being modified by the main
--echo # statement.
--echo # --echo #
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM; CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
...@@ -1949,6 +1951,31 @@ CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) ...@@ -1949,6 +1951,31 @@ CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
CREATE TRIGGER tm1_ai AFTER INSERT ON tm1 CREATE TRIGGER tm1_ai AFTER INSERT ON tm1
FOR EACH ROW INSERT INTO t1 VALUES(11); FOR EACH ROW INSERT INTO t1 VALUES(11);
LOCK TABLE tm1 WRITE, t1 WRITE; LOCK TABLE tm1 WRITE, t1 WRITE;
--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG
INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1;
UNLOCK TABLES;
LOCK TABLE t1 WRITE, tm1 WRITE;
--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG
INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1;
UNLOCK TABLES;
DROP TRIGGER tm1_ai;
DROP TABLE tm1, t1;
--echo #
--echo # Don't select MERGE child when trying to get a prelocked table.
--echo #
--echo # Due to a limitation demonstrated by the previous test
--echo # we can no longer use a write-locked prelocked table.
--echo # The test is kept for historical purposes.
--echo #
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
INSERT_METHOD=LAST;
CREATE TRIGGER tm1_ai AFTER INSERT ON tm1
FOR EACH ROW SELECT max(c1) FROM t1 INTO @var;
LOCK TABLE tm1 WRITE, t1 WRITE;
INSERT INTO tm1 VALUES (1); INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1; SELECT * FROM tm1;
UNLOCK TABLES; UNLOCK TABLES;
...@@ -1970,7 +1997,7 @@ CREATE TABLE t5 (c1 INT) ENGINE=MyISAM; ...@@ -1970,7 +1997,7 @@ CREATE TABLE t5 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2,t3,t4,t5) CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2,t3,t4,t5)
INSERT_METHOD=LAST; INSERT_METHOD=LAST;
CREATE TRIGGER t2_au AFTER UPDATE ON t2 CREATE TRIGGER t2_au AFTER UPDATE ON t2
FOR EACH ROW INSERT INTO t3 VALUES(33); FOR EACH ROW SELECT MAX(c1) FROM t1 INTO @var;
CREATE FUNCTION f1() RETURNS INT CREATE FUNCTION f1() RETURNS INT
RETURN (SELECT MAX(c1) FROM t4); RETURN (SELECT MAX(c1) FROM t4);
LOCK TABLE tm1 WRITE, t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE, t5 WRITE; LOCK TABLE tm1 WRITE, t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE, t5 WRITE;
...@@ -2037,4 +2064,30 @@ connection default; ...@@ -2037,4 +2064,30 @@ connection default;
--echo # Disconnecting con1, all mdl_tickets must have been released. --echo # Disconnecting con1, all mdl_tickets must have been released.
disconnect con1; disconnect con1;
--echo #
--echo # A test case for Bug#47648 main.merge fails sporadically
--echo #
--echo # Make sure we correctly maintain lex->query_tables_last_own.
--echo #
create table t1 (c1 int not null);
create table t2 (c1 int not null);
create table t3 (c1 int not null);
create function f1 () returns int return (select max(c1) from t3);
create table t4 (c1 int not null) engine=merge union=(t1,t2) insert_method=last ;
select * from t4 where c1 < f1();
prepare stmt from "select * from t4 where c1 < f1()";
execute stmt;
execute stmt;
execute stmt;
drop function f1;
--error ER_SP_DOES_NOT_EXIST
execute stmt;
--error ER_SP_DOES_NOT_EXIST
execute stmt;
drop table t4, t3, t2, t1;
--echo End of 6.0 tests --echo End of 6.0 tests
...@@ -458,6 +458,18 @@ int ha_myisammrg::add_children_list(void) ...@@ -458,6 +458,18 @@ int ha_myisammrg::add_children_list(void)
*/ */
if (thd->lex->query_tables_last == &parent_l->next_global) if (thd->lex->query_tables_last == &parent_l->next_global)
thd->lex->query_tables_last= this->children_last_l; thd->lex->query_tables_last= this->children_last_l;
/*
The branch below works only when re-executing a prepared
statement or a stored routine statement:
We've just modified query_tables_last. Keep it in sync with
query_tables_last_own, if it was set by the prelocking code.
This ensures that the check that prohibits double updates (*)
can correctly identify what tables belong to the main statement.
(*) A double update is, e.g. when a user issues UPDATE t1 and
t1 has an AFTER UPDATE trigger that also modifies t1.
*/
if (thd->lex->query_tables_own_last == &parent_l->next_global)
thd->lex->query_tables_own_last= this->children_last_l;
end: end:
DBUG_RETURN(0); DBUG_RETURN(0);
...@@ -888,6 +900,15 @@ int ha_myisammrg::detach_children(void) ...@@ -888,6 +900,15 @@ int ha_myisammrg::detach_children(void)
if (thd->lex->query_tables_last == this->children_last_l) if (thd->lex->query_tables_last == this->children_last_l)
thd->lex->query_tables_last= this->children_l->prev_global; thd->lex->query_tables_last= this->children_l->prev_global;
/*
If the statement requires prelocking, and prelocked
tables were added right after merge children, modify the
last own table pointer to point at prev_global of the merge
parent.
*/
if (thd->lex->query_tables_own_last == this->children_last_l)
thd->lex->query_tables_own_last= this->children_l->prev_global;
/* Terminate child list. So it cannot be tried to remove again. */ /* Terminate child list. So it cannot be tried to remove again. */
*this->children_last_l= NULL; *this->children_last_l= NULL;
this->children_l->prev_global= NULL; this->children_l->prev_global= NULL;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment