mirror of
https://github.com/MariaDB/server.git
synced 2025-07-30 16:24:05 +03:00
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.
This commit is contained in:
@ -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
|
|||||||
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;
|
|||||||
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
|
|||||||
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
|
|||||||
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;
|
|||||||
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)
|
|||||||
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;
|
|||||||
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;
|
|||||||
--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)
|
|||||||
*/
|
*/
|
||||||
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)
|
|||||||
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;
|
||||||
|
Reference in New Issue
Block a user