Commit ef15a335 authored by Konstantin Osipov's avatar Konstantin Osipov

Backport of:

----------------------------------------------------------
revno: 2630.4.38
committer: Konstantin Osipov <konstantin@mysql.com>
branch nick: mysql-6.0-4144
timestamp: Wed 2008-06-25 22:07:06 +0400
message:
  WL#4144 - Lock MERGE engine children.
  Committing a version of the patch merged with WL#3726
  on behalf of Ingo.

  Step #1: Move locking from parent to children.

  MERGE children are now left in the query list of tables
  after inserted there in open_tables(). So they are locked
  by lock_tables() as all other tables are.

  The MERGE parent does not store locks any more. It appears
  in a MYSQL_LOCK with zero lock data. This is kind of a "dummy"
  lock.

  All other lock handling is also done directly on the children.
  To protect against parent or child modifications during LOCK
  TABLES, the children are detached after every statement and
  attached before every statement, even under LOCK TABLES.

  The children table list is removed from the query list of tables
  on every detach and on close of the parent.

  Step #2: Move MERGE specific functionality from SQL layer
  into table handler.

  Functionality moved from SQL layer (mainly sql_base.cc)
  to the table handler (ha_myisammrg.cc).

  Unnecessary code is removed from the SQL layer.

  Step #3: Moved all MERGE specific members from TABLE
  to ha_myisammrg.

  Moved members from TABLE to ha_myisammrg.
  Renamed some mebers.
  Fixed comments.

  Step #4: Valgrind and coverage testing

  Valgrind did not uncover new problems.
  Added purecov comments.

  Added a new test for DATA/INDEX DIRECTORY options.
  Changed handling of ::reset() for non-attached children.
  Fixed the merge-big test.

  Step #5: Fixed crashes detected during review
  Changed detection when to attach/detach.
  Added new tests.

Backport also the fix for Bug#44040 "MySQL allows creating a 
MERGE table upon VIEWs but crashes when using it"
parent f744a841
......@@ -191,10 +191,11 @@ enum ha_extra_function {
/* Inform handler that we will do a rename */
HA_EXTRA_PREPARE_FOR_RENAME,
/*
Orders MERGE handler to attach or detach its child tables. Used at
begin and end of a statement.
Special actions for MERGE tables.
*/
HA_EXTRA_ADD_CHILDREN_LIST,
HA_EXTRA_ATTACH_CHILDREN,
HA_EXTRA_IS_ATTACHED_CHILDREN,
HA_EXTRA_DETACH_CHILDREN
};
......
......@@ -578,23 +578,23 @@ select max(b) from t1 where a = 2;
max(b)
1
drop table t3,t1,t2;
create table t1 (a int not null);
create table t2 (a int not null);
insert into t1 values (1);
insert into t2 values (2);
create temporary table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
select * from t3;
CREATE TABLE t1 (c1 INT NOT NULL);
CREATE TABLE t2 (c1 INT NOT NULL);
INSERT INTO t1 VALUES (1);
INSERT INTO t2 VALUES (2);
CREATE TEMPORARY TABLE t3 (c1 INT NOT NULL) ENGINE=MRG_MYISAM UNION=(t1,t2);
SELECT * FROM t3;
ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist
create temporary table t4 (a int not null);
create temporary table t5 (a int not null);
insert into t4 values (1);
insert into t5 values (2);
create temporary table t6 (a int not null) ENGINE=MERGE UNION=(t4,t5);
select * from t6;
a
1
2
drop table t6, t3, t1, t2, t4, t5;
CREATE TEMPORARY TABLE t4 (c1 INT NOT NULL);
CREATE TEMPORARY TABLE t5 (c1 INT NOT NULL);
INSERT INTO t4 VALUES (4);
INSERT INTO t5 VALUES (5);
CREATE TEMPORARY TABLE t6 (c1 INT NOT NULL) ENGINE=MRG_MYISAM UNION=(t4,t5);
SELECT * FROM t6;
c1
4
5
DROP TABLE t6, t3, t1, t2, t4, t5;
create temporary table t1 (a int not null);
create temporary table t2 (a int not null);
insert into t1 values (1);
......@@ -1258,7 +1258,7 @@ c1
LOCK TABLES t1 WRITE, t2 WRITE, t3 WRITE;
ALTER TABLE t2 RENAME TO t5;
SELECT * FROM t3 ORDER BY c1;
ERROR HY000: Table 't3' was not locked with LOCK TABLES
ERROR HY000: Table 't2' was not locked with LOCK TABLES
ALTER TABLE t5 RENAME TO t2;
ERROR HY000: Table 't5' was not locked with LOCK TABLES
UNLOCK TABLES;
......@@ -1330,9 +1330,9 @@ LOCK TABLES t1 WRITE, t2 WRITE;
INSERT INTO t1 VALUES (1);
DROP TABLE t1;
SELECT * FROM t2;
ERROR HY000: Unable to open underlying table which is differently defined or of non-MyISAM type or doesn't exist
ERROR HY000: Table 't1' was not locked with LOCK TABLES
SELECT * FROM t1;
ERROR 42S02: Table 'test.t1' doesn't exist
ERROR HY000: Table 't1' was not locked with LOCK TABLES
UNLOCK TABLES;
DROP TABLE t2;
#
......@@ -2256,3 +2256,250 @@ deallocate prepare stmt;
#
drop table t_parent;
set @@global.table_definition_cache=@save_table_definition_cache;
DROP DATABASE IF EXISTS mysql_test1;
CREATE DATABASE mysql_test1;
CREATE TABLE t1 ... DATA DIRECTORY=... INDEX DIRECTORY=...
CREATE TABLE mysql_test1.t2 ... DATA DIRECTORY=... INDEX DIRECTORY=...
CREATE TABLE m1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,mysql_test1.t2)
INSERT_METHOD=LAST;
INSERT INTO t1 VALUES (1);
INSERT INTO mysql_test1.t2 VALUES (2);
SELECT * FROM m1;
c1
1
2
DROP TABLE t1, mysql_test1.t2, m1;
DROP DATABASE mysql_test1;
CREATE TABLE t1 (c1 INT);
CREATE TABLE t2 (c1 INT);
INSERT INTO t1 (c1) VALUES (1);
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2) INSERT_METHOD=FIRST;
CREATE TABLE t3 (c1 INT);
INSERT INTO t3 (c1) VALUES (1);
CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MAX(c1) FROM t3);
CREATE VIEW v1 AS SELECT foo.c1 c1, f1() c2, bar.c1 c3, f1() c4
FROM tm1 foo, tm1 bar, t3;
SELECT * FROM v1;
c1 c2 c3 c4
1 1 1 1
DROP FUNCTION f1;
DROP VIEW v1;
DROP TABLE tm1, t1, t2, t3;
CREATE TEMPORARY TABLE t1 (c1 INT);
CREATE TEMPORARY TABLE t2 (c1 INT);
CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
INSERT_METHOD=FIRST;
CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MAX(c1) FROM tm1);
INSERT INTO tm1 (c1) VALUES (1);
SELECT f1() FROM (SELECT 1) AS c1;
f1()
1
DROP FUNCTION f1;
DROP TABLE tm1, t1, t2;
CREATE FUNCTION f1() RETURNS INT
BEGIN
CREATE TEMPORARY TABLE t1 (c1 INT);
CREATE TEMPORARY TABLE t2 (c1 INT);
CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2);
INSERT INTO t1 (c1) VALUES (1);
RETURN (SELECT MAX(c1) FROM tm1);
END|
SELECT f1() FROM (SELECT 1 UNION SELECT 1) c1;
f1()
1
DROP FUNCTION f1;
DROP TABLE tm1, t1, t2;
CREATE TEMPORARY TABLE t1 (c1 INT);
INSERT INTO t1 (c1) VALUES (1);
CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1);
CREATE FUNCTION f1() RETURNS INT
BEGIN
CREATE TEMPORARY TABLE t2 (c1 INT);
ALTER TEMPORARY TABLE tm1 UNION=(t1,t2);
INSERT INTO t2 (c1) VALUES (2);
RETURN (SELECT MAX(c1) FROM tm1);
END|
ERROR 0A000: ALTER VIEW is not allowed in stored procedures
DROP TABLE tm1, t1;
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1;
c1
1
DROP TABLE tm1, t1;
CREATE FUNCTION f1() RETURNS INT
BEGIN
INSERT INTO tm1 VALUES (1);
RETURN (SELECT MAX(c1) FROM tm1);
END|
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
SELECT f1();
f1()
1
DROP FUNCTION f1;
DROP TABLE tm1, t1;
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
LOCK TABLE tm1 WRITE;
INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1;
c1
1
UNLOCK TABLES;
DROP TABLE tm1, t1;
CREATE FUNCTION f1() RETURNS INT
BEGIN
INSERT INTO tm1 VALUES (1);
RETURN (SELECT MAX(c1) FROM tm1);
END|
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
LOCK TABLE tm1 WRITE;
SELECT f1();
f1()
1
UNLOCK TABLES;
DROP FUNCTION f1;
DROP TABLE tm1, t1;
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TABLE t2 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
CREATE TRIGGER t2_ai AFTER INSERT ON t2
FOR EACH ROW INSERT INTO tm1 VALUES(11);
LOCK TABLE t2 WRITE;
INSERT INTO t2 VALUES (2);
SELECT * FROM tm1;
c1
11
SELECT * FROM t2;
c1
2
UNLOCK TABLES;
DROP TRIGGER t2_ai;
DROP TABLE tm1, t1, t2;
CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
INSERT_METHOD=LAST;
INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1;
c1
1
DROP TABLE tm1, t1;
CREATE FUNCTION f1() RETURNS INT
BEGIN
INSERT INTO tm1 VALUES (1);
RETURN (SELECT MAX(c1) FROM tm1);
END|
CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
INSERT_METHOD=LAST;
SELECT f1();
f1()
1
DROP FUNCTION f1;
DROP TABLE tm1, t1;
CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
INSERT_METHOD=LAST;
CREATE TABLE t9 (c1 INT) ENGINE=MyISAM;
LOCK TABLE t9 WRITE;
INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1;
c1
1
UNLOCK TABLES;
DROP TABLE tm1, t1, t9;
CREATE FUNCTION f1() RETURNS INT
BEGIN
INSERT INTO tm1 VALUES (1);
RETURN (SELECT MAX(c1) FROM tm1);
END|
CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
INSERT_METHOD=LAST;
CREATE TABLE t9 (c1 INT) ENGINE=MyISAM;
LOCK TABLE t9 WRITE;
SELECT f1();
f1()
1
UNLOCK TABLES;
DROP FUNCTION f1;
DROP TABLE tm1, t1, t9;
CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
INSERT_METHOD=LAST;
CREATE TABLE t2 (c1 INT) ENGINE=MyISAM;
CREATE TRIGGER t2_ai AFTER INSERT ON t2
FOR EACH ROW INSERT INTO tm1 VALUES(11);
LOCK TABLE t2 WRITE;
INSERT INTO t2 VALUES (2);
SELECT * FROM tm1;
c1
11
SELECT * FROM t2;
c1
2
UNLOCK TABLES;
DROP TRIGGER t2_ai;
DROP TABLE tm1, t1, t2;
#
# Don't select MERGE child when trying to get prelocked table.
#
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 INSERT INTO t1 VALUES(11);
LOCK TABLE tm1 WRITE, t1 WRITE;
INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1;
c1
1
11
UNLOCK TABLES;
LOCK TABLE t1 WRITE, tm1 WRITE;
INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1;
c1
1
11
1
11
UNLOCK TABLES;
DROP TRIGGER tm1_ai;
DROP TABLE tm1, t1;
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TABLE t2 (c1 INT) ENGINE=MyISAM;
CREATE TABLE t3 (c1 INT) ENGINE=MyISAM;
CREATE TABLE t4 (c1 INT) ENGINE=MyISAM;
CREATE TABLE t5 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2,t3,t4,t5)
INSERT_METHOD=LAST;
CREATE TRIGGER t2_au AFTER UPDATE ON t2
FOR EACH ROW INSERT INTO t3 VALUES(33);
CREATE FUNCTION f1() RETURNS INT
RETURN (SELECT MAX(c1) FROM t4);
LOCK TABLE tm1 WRITE, t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE, t5 WRITE;
INSERT INTO t1 VALUES(1);
INSERT INTO t2 VALUES(2);
INSERT INTO t3 VALUES(3);
INSERT INTO t4 VALUES(4);
INSERT INTO t5 VALUES(5);
UPDATE t2, tm1 SET t2.c1=f1();
FLUSH TABLES;
FLUSH TABLES;
UNLOCK TABLES;
SELECT * FROM tm1;
c1
1
4
3
33
4
5
DROP TRIGGER t2_au;
DROP FUNCTION f1;
DROP TABLE tm1, t1, t2, t3, t4, t5;
End of 6.0 tests
......@@ -15,4 +15,3 @@ partition_innodb_builtin : Bug#32430 2009-09-25 mattiasj Waiting for push of Inn
partition_innodb_plugin : Bug#32430 2009-09-25 mattiasj Waiting for push of Innodb changes
innodb-autoinc : Bug#48482 2009-11-02 svoj innodb-autoinc.test fails with results difference
rpl_killed_ddl : Bug#45520: rpl_killed_ddl fails sporadically in pb2
merge : WL#4144
......@@ -51,7 +51,7 @@ connection default;
#--sleep 8
#SELECT ID,STATE,INFO FROM INFORMATION_SCHEMA.PROCESSLIST;
let $wait_condition= SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST
WHERE ID = $con1_id AND STATE = 'Locked';
WHERE ID = $con1_id AND STATE = 'Table lock';
--source include/wait_condition.inc
#SELECT NOW();
--echo # Kick INSERT out of thr_multi_lock().
......@@ -61,7 +61,7 @@ FLUSH TABLES;
#--sleep 8
#SELECT ID,STATE,INFO FROM INFORMATION_SCHEMA.PROCESSLIST;
let $wait_condition= SELECT 1 FROM INFORMATION_SCHEMA.PROCESSLIST
WHERE ID = $con1_id AND STATE = 'Waiting for table';
WHERE ID = $con1_id AND STATE = 'Table lock';
--source include/wait_condition.inc
#SELECT NOW();
--echo # Unlock and close table and wait for con1 to close too.
......
......@@ -216,20 +216,20 @@ drop table t3,t1,t2;
#
# temporary merge tables
#
create table t1 (a int not null);
create table t2 (a int not null);
insert into t1 values (1);
insert into t2 values (2);
create temporary table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
CREATE TABLE t1 (c1 INT NOT NULL);
CREATE TABLE t2 (c1 INT NOT NULL);
INSERT INTO t1 VALUES (1);
INSERT INTO t2 VALUES (2);
CREATE TEMPORARY TABLE t3 (c1 INT NOT NULL) ENGINE=MRG_MYISAM UNION=(t1,t2);
--error ER_WRONG_MRG_TABLE
select * from t3;
create temporary table t4 (a int not null);
create temporary table t5 (a int not null);
insert into t4 values (1);
insert into t5 values (2);
create temporary table t6 (a int not null) ENGINE=MERGE UNION=(t4,t5);
select * from t6;
drop table t6, t3, t1, t2, t4, t5;
SELECT * FROM t3;
CREATE TEMPORARY TABLE t4 (c1 INT NOT NULL);
CREATE TEMPORARY TABLE t5 (c1 INT NOT NULL);
INSERT INTO t4 VALUES (4);
INSERT INTO t5 VALUES (5);
CREATE TEMPORARY TABLE t6 (c1 INT NOT NULL) ENGINE=MRG_MYISAM UNION=(t4,t5);
SELECT * FROM t6;
DROP TABLE t6, t3, t1, t2, t4, t5;
#
# Bug#19627 - temporary merge table locking
# MERGE table and its children must match in temporary type.
......@@ -556,7 +556,7 @@ CREATE TABLE t1(a INT);
SELECT * FROM tm1;
CHECK TABLE tm1;
CREATE TABLE t2(a BLOB);
--error 1168
--error ER_WRONG_MRG_TABLE
SELECT * FROM tm1;
CHECK TABLE tm1;
ALTER TABLE t2 MODIFY a INT;
......@@ -969,9 +969,9 @@ CREATE TABLE t2 (c1 INT, INDEX(c1)) ENGINE=MRG_MYISAM UNION=(t1)
LOCK TABLES t1 WRITE, t2 WRITE;
INSERT INTO t1 VALUES (1);
DROP TABLE t1;
--error 1168
--error ER_TABLE_NOT_LOCKED
SELECT * FROM t2;
--error ER_NO_SUCH_TABLE
--error ER_TABLE_NOT_LOCKED
SELECT * FROM t1;
UNLOCK TABLES;
DROP TABLE t2;
......@@ -1407,6 +1407,7 @@ FLUSH TABLES m1, t1;
UNLOCK TABLES;
DROP TABLE t1, m1;
#
# Bug#35068 - Assertion fails when reading from i_s.tables
# and there is incorrect merge table
......@@ -1694,3 +1695,296 @@ while ($1)
--enable_query_log
drop table t_parent;
set @@global.table_definition_cache=@save_table_definition_cache;
#
# WL#4144 - Lock MERGE engine children
#
# Test DATA/INDEX DIRECTORY
#
--disable_warnings
DROP DATABASE IF EXISTS mysql_test1;
--enable_warnings
CREATE DATABASE mysql_test1;
--disable_query_log
# data/index directory don't work in HAVE_purify builds. Disable
# build-dependent warnings.
--disable_warnings
--echo CREATE TABLE t1 ... DATA DIRECTORY=... INDEX DIRECTORY=...
eval CREATE TABLE t1 (c1 INT)
DATA DIRECTORY='$MYSQLTEST_VARDIR/tmp'
INDEX DIRECTORY='$MYSQLTEST_VARDIR/tmp';
--echo CREATE TABLE mysql_test1.t2 ... DATA DIRECTORY=... INDEX DIRECTORY=...
eval CREATE TABLE mysql_test1.t2 (c1 INT)
DATA DIRECTORY='$MYSQLTEST_VARDIR/tmp'
INDEX DIRECTORY='$MYSQLTEST_VARDIR/tmp';
--enable_query_log
--enable_warnings
CREATE TABLE m1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,mysql_test1.t2)
INSERT_METHOD=LAST;
INSERT INTO t1 VALUES (1);
INSERT INTO mysql_test1.t2 VALUES (2);
SELECT * FROM m1;
#--copy_file $MYSQLTEST_VARDIR/master-data/test/m1.MRG /tmp/mysql-test-m1.MRG
DROP TABLE t1, mysql_test1.t2, m1;
DROP DATABASE mysql_test1;
#
# Review detected Crash #1. Detaching main tables while in sub statement.
#
CREATE TABLE t1 (c1 INT);
CREATE TABLE t2 (c1 INT);
INSERT INTO t1 (c1) VALUES (1);
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2) INSERT_METHOD=FIRST;
CREATE TABLE t3 (c1 INT);
INSERT INTO t3 (c1) VALUES (1);
CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MAX(c1) FROM t3);
CREATE VIEW v1 AS SELECT foo.c1 c1, f1() c2, bar.c1 c3, f1() c4
FROM tm1 foo, tm1 bar, t3;
SELECT * FROM v1;
DROP FUNCTION f1;
DROP VIEW v1;
DROP TABLE tm1, t1, t2, t3;
#
# Review detected Crash #2. Trying to attach temporary table twice.
#
CREATE TEMPORARY TABLE t1 (c1 INT);
CREATE TEMPORARY TABLE t2 (c1 INT);
CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
INSERT_METHOD=FIRST;
CREATE FUNCTION f1() RETURNS INT RETURN (SELECT MAX(c1) FROM tm1);
INSERT INTO tm1 (c1) VALUES (1);
SELECT f1() FROM (SELECT 1) AS c1;
DROP FUNCTION f1;
DROP TABLE tm1, t1, t2;
#
# Review suggested test. DDL in a stored function.
#
DELIMITER |;
CREATE FUNCTION f1() RETURNS INT
BEGIN
CREATE TEMPORARY TABLE t1 (c1 INT);
CREATE TEMPORARY TABLE t2 (c1 INT);
CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2);
INSERT INTO t1 (c1) VALUES (1);
RETURN (SELECT MAX(c1) FROM tm1);
END|
DELIMITER ;|
SELECT f1() FROM (SELECT 1 UNION SELECT 1) c1;
DROP FUNCTION f1;
DROP TABLE tm1, t1, t2;
#
CREATE TEMPORARY TABLE t1 (c1 INT);
INSERT INTO t1 (c1) VALUES (1);
CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1);
DELIMITER |;
--error ER_SP_BADSTATEMENT
CREATE FUNCTION f1() RETURNS INT
BEGIN
CREATE TEMPORARY TABLE t2 (c1 INT);
ALTER TEMPORARY TABLE tm1 UNION=(t1,t2);
INSERT INTO t2 (c1) VALUES (2);
RETURN (SELECT MAX(c1) FROM tm1);
END|
DELIMITER ;|
DROP TABLE tm1, t1;
#
# Base table. No LOCK TABLES, no functions/triggers.
#
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1;
DROP TABLE tm1, t1;
#
# Base table. No LOCK TABLES, sub-statement that is run inside a function.
#
DELIMITER |;
CREATE FUNCTION f1() RETURNS INT
BEGIN
INSERT INTO tm1 VALUES (1);
RETURN (SELECT MAX(c1) FROM tm1);
END|
DELIMITER ;|
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
SELECT f1();
DROP FUNCTION f1;
DROP TABLE tm1, t1;
#
# Base table. LOCK TABLES, no functions/triggers.
#
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
LOCK TABLE tm1 WRITE;
INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1;
UNLOCK TABLES;
DROP TABLE tm1, t1;
#
# Base table. LOCK TABLES, sub-statement that is run inside a function.
#
DELIMITER |;
CREATE FUNCTION f1() RETURNS INT
BEGIN
INSERT INTO tm1 VALUES (1);
RETURN (SELECT MAX(c1) FROM tm1);
END|
DELIMITER ;|
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
LOCK TABLE tm1 WRITE;
SELECT f1();
UNLOCK TABLES;
DROP FUNCTION f1;
DROP TABLE tm1, t1;
#
# Base table. LOCK TABLES statement that locks a table that has a trigger
# that inserts into a merge table, so an attempt is made to lock tables
# of a sub-statement.
#
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TABLE t2 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
CREATE TRIGGER t2_ai AFTER INSERT ON t2
FOR EACH ROW INSERT INTO tm1 VALUES(11);
LOCK TABLE t2 WRITE;
INSERT INTO t2 VALUES (2);
SELECT * FROM tm1;
SELECT * FROM t2;
UNLOCK TABLES;
DROP TRIGGER t2_ai;
DROP TABLE tm1, t1, t2;
#
# Temporary. No LOCK TABLES, no functions/triggers.
#
CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
INSERT_METHOD=LAST;
INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1;
DROP TABLE tm1, t1;
#
# Temporary. No LOCK TABLES, sub-statement that is run inside a function.
#
DELIMITER |;
CREATE FUNCTION f1() RETURNS INT
BEGIN
INSERT INTO tm1 VALUES (1);
RETURN (SELECT MAX(c1) FROM tm1);
END|
DELIMITER ;|
CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
INSERT_METHOD=LAST;
SELECT f1();
DROP FUNCTION f1;
DROP TABLE tm1, t1;
#
# Temporary. LOCK TABLES, no functions/triggers.
#
CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
INSERT_METHOD=LAST;
CREATE TABLE t9 (c1 INT) ENGINE=MyISAM;
LOCK TABLE t9 WRITE;
INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1;
UNLOCK TABLES;
DROP TABLE tm1, t1, t9;
#
# Temporary. LOCK TABLES, sub-statement that is run inside a function.
#
DELIMITER |;
CREATE FUNCTION f1() RETURNS INT
BEGIN
INSERT INTO tm1 VALUES (1);
RETURN (SELECT MAX(c1) FROM tm1);
END|
DELIMITER ;|
CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
INSERT_METHOD=LAST;
CREATE TABLE t9 (c1 INT) ENGINE=MyISAM;
LOCK TABLE t9 WRITE;
SELECT f1();
UNLOCK TABLES;
DROP FUNCTION f1;
DROP TABLE tm1, t1, t9;
#
# Temporary. LOCK TABLES statement that locks a table that has a trigger
# that inserts into a merge table, so an attempt is made to lock tables
# of a sub-statement.
#
CREATE TEMPORARY TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TEMPORARY TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1)
INSERT_METHOD=LAST;
CREATE TABLE t2 (c1 INT) ENGINE=MyISAM;
CREATE TRIGGER t2_ai AFTER INSERT ON t2
FOR EACH ROW INSERT INTO tm1 VALUES(11);
LOCK TABLE t2 WRITE;
INSERT INTO t2 VALUES (2);
SELECT * FROM tm1;
SELECT * FROM t2;
UNLOCK TABLES;
DROP TRIGGER t2_ai;
DROP TABLE tm1, t1, t2;
--echo #
--echo # Don't select MERGE child when trying to get prelocked table.
--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 INSERT INTO t1 VALUES(11);
LOCK TABLE tm1 WRITE, t1 WRITE;
INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1;
UNLOCK TABLES;
LOCK TABLE t1 WRITE, tm1 WRITE;
INSERT INTO tm1 VALUES (1);
SELECT * FROM tm1;
UNLOCK TABLES;
DROP TRIGGER tm1_ai;
DROP TABLE tm1, t1;
# Don't resurrect chopped off prelocked tables.
# The problem is not visible by test results; only by debugging.
#
CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
CREATE TABLE t2 (c1 INT) ENGINE=MyISAM;
CREATE TABLE t3 (c1 INT) ENGINE=MyISAM;
CREATE TABLE t4 (c1 INT) ENGINE=MyISAM;
CREATE TABLE t5 (c1 INT) ENGINE=MyISAM;
CREATE TABLE tm1 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2,t3,t4,t5)
INSERT_METHOD=LAST;
CREATE TRIGGER t2_au AFTER UPDATE ON t2
FOR EACH ROW INSERT INTO t3 VALUES(33);
CREATE FUNCTION f1() RETURNS INT
RETURN (SELECT MAX(c1) FROM t4);
LOCK TABLE tm1 WRITE, t1 WRITE, t2 WRITE, t3 WRITE, t4 WRITE, t5 WRITE;
INSERT INTO t1 VALUES(1);
INSERT INTO t2 VALUES(2);
INSERT INTO t3 VALUES(3);
INSERT INTO t4 VALUES(4);
INSERT INTO t5 VALUES(5);
connect (con1,localhost,root,,);
send UPDATE t2, tm1 SET t2.c1=f1();
connection default;
# Force reopen in other thread.
#sleep 1;
FLUSH TABLES;
#sleep 1;
FLUSH TABLES;
#sleep 1;
UNLOCK TABLES;
connection con1;
reap;
disconnect con1;
connection default;
SELECT * FROM tm1;
DROP TRIGGER t2_au;
DROP FUNCTION f1;
DROP TABLE tm1, t1, t2, t3, t4, t5;
--echo End of 6.0 tests
......@@ -631,6 +631,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner,
{
if (lock->write.data->type == TL_WRITE_ONLY)
{
/* purecov: begin tested */
/* Allow lock owner to bypass TL_WRITE_ONLY. */
if (!thr_lock_owner_equal(data->owner, lock->write.data->owner))
{
......@@ -639,6 +640,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner,
result= THR_LOCK_ABORTED; /* Can't wait for this one */
goto end;
}
/* purecov: end */
}
/*
......
......@@ -5265,34 +5265,35 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info,
}
/*
General function to prepare handler for certain behavior
/**
General function to prepare handler for certain behavior.
SYNOPSIS
extra()
@param[in] operation operation to execute
operation Operation type for extra call
RETURN VALUE
>0 Error code
0 Success
@return status
@retval 0 success
@retval >0 error code
@detail
DESCRIPTION
extra() is called whenever the server wishes to send a hint to
the storage engine. The MyISAM engine implements the most hints.
We divide the parameters into the following categories:
1) Parameters used by most handlers
2) Parameters used by some non-MyISAM handlers
3) Parameters used only by MyISAM
4) Parameters only used by temporary tables for query processing
5) Parameters only used by MyISAM internally
6) Parameters not used at all
7) Parameters only used by federated tables for query processing
8) Parameters only used by NDB
1) Operations used by most handlers
2) Operations used by some non-MyISAM handlers
3) Operations used only by MyISAM
4) Operations only used by temporary tables for query processing
5) Operations only used by MyISAM internally
6) Operations not used at all
7) Operations only used by federated tables for query processing
8) Operations only used by NDB
9) Operations only used by MERGE
The partition handler need to handle category 1), 2) and 3).
1) Parameters used by most handlers
1) Operations used by most handlers
-----------------------------------
HA_EXTRA_RESET:
This option is used by most handlers and it resets the handler state
......@@ -5331,7 +5332,7 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info,
ensure disk based tables are flushed at end of query execution.
Currently is never used.
2) Parameters used by some non-MyISAM handlers
2) Operations used by some non-MyISAM handlers
----------------------------------------------
HA_EXTRA_KEYREAD_PRESERVE_FIELDS:
This is a strictly InnoDB feature that is more or less undocumented.
......@@ -5350,7 +5351,7 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info,
SQL constructs.
Not used by MyISAM.
3) Parameters used only by MyISAM
3) Operations used only by MyISAM
---------------------------------
HA_EXTRA_NORMAL:
Only used in MyISAM to reset quick mode, not implemented by any other
......@@ -5481,7 +5482,7 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info,
Only used by MyISAM, called when altering table, closing tables to
enforce a reopen of the table files.
4) Parameters only used by temporary tables for query processing
4) Operations only used by temporary tables for query processing
----------------------------------------------------------------
HA_EXTRA_RESET_STATE:
Same as reset() except that buffers are not released. If there is
......@@ -5512,7 +5513,7 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info,
tables used in query processing.
Not handled by partition handler.
5) Parameters only used by MyISAM internally
5) Operations only used by MyISAM internally
--------------------------------------------
HA_EXTRA_REINIT_CACHE:
This call reinitializes the READ CACHE described above if there is one
......@@ -5547,19 +5548,19 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info,
HA_EXTRA_CHANGE_KEY_TO_UNIQUE:
Only used by MyISAM, never called.
6) Parameters not used at all
6) Operations not used at all
-----------------------------
HA_EXTRA_KEY_CACHE:
HA_EXTRA_NO_KEY_CACHE:
This parameters are no longer used and could be removed.
7) Parameters only used by federated tables for query processing
7) Operations only used by federated tables for query processing
----------------------------------------------------------------
HA_EXTRA_INSERT_WITH_UPDATE:
Inform handler that an "INSERT...ON DUPLICATE KEY UPDATE" will be
executed. This condition is unset by HA_EXTRA_NO_IGNORE_DUP_KEY.
8) Parameters only used by NDB
8) Operations only used by NDB
------------------------------
HA_EXTRA_DELETE_CANNOT_BATCH:
HA_EXTRA_UPDATE_CANNOT_BATCH:
......@@ -5567,6 +5568,14 @@ void ha_partition::get_dynamic_partition_info(PARTITION_INFO *stat_info,
and should perform them immediately. This may be needed when table has
AFTER DELETE/UPDATE triggers which access to subject table.
These flags are reset by the handler::extra(HA_EXTRA_RESET) call.
9) Operations only used by MERGE
------------------------------
HA_EXTRA_ADD_CHILDREN_LIST:
HA_EXTRA_ATTACH_CHILDREN:
HA_EXTRA_IS_ATTACHED_CHILDREN:
HA_EXTRA_DETACH_CHILDREN:
Special actions for MERGE tables. Ignore.
*/
int ha_partition::extra(enum ha_extra_function operation)
......@@ -5659,12 +5668,21 @@ int ha_partition::extra(enum ha_extra_function operation)
/* Category 7), used by federated handlers */
case HA_EXTRA_INSERT_WITH_UPDATE:
DBUG_RETURN(loop_extra(operation));
/* Category 8) Parameters only used by NDB */
/* Category 8) Operations only used by NDB */
case HA_EXTRA_DELETE_CANNOT_BATCH:
case HA_EXTRA_UPDATE_CANNOT_BATCH:
{
/* Currently only NDB use the *_CANNOT_BATCH */
break;
}
/* Category 9) Operations only used by MERGE */
case HA_EXTRA_ADD_CHILDREN_LIST:
case HA_EXTRA_ATTACH_CHILDREN:
case HA_EXTRA_IS_ATTACHED_CHILDREN:
case HA_EXTRA_DETACH_CHILDREN:
{
/* Special actions for MERGE tables. Ignore. */
break;
}
/*
http://dev.mysql.com/doc/refman/5.1/en/partitioning-limitations.html
......
......@@ -1228,9 +1228,6 @@ bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias,
TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name);
TABLE *find_write_locked_table(TABLE *list, const char *db,
const char *table_name);
void detach_merge_children(TABLE *table, bool clear_refs);
bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last,
TABLE_LIST *new_child_list, TABLE_LIST **new_last);
thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table);
void execute_init_command(THD *thd, sys_var_str *init_command_var,
rw_lock_t *var_mutex);
......
......@@ -164,6 +164,11 @@ static void check_unused(void)
I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables);
while ((entry= it++))
{
/* We must not have TABLEs in the free list that have their file closed. */
DBUG_ASSERT(entry->db_stat && entry->file);
/* Merge children should be detached from a merge parent */
DBUG_ASSERT(! entry->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
if (entry->in_use)
{
DBUG_PRINT("error",("Used table is in share's list of unused tables")); /* purecov: inspected */
......@@ -372,6 +377,10 @@ static void table_def_use_table(THD *thd, TABLE *table)
/* Add table to list of used tables for this share. */
table->s->used_tables.push_front(table);
table->in_use= thd;
/* The ex-unused table must be fully functional. */
DBUG_ASSERT(table->db_stat && table->file);
/* The children must be detached from the table. */
DBUG_ASSERT(! table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
}
......@@ -813,9 +822,6 @@ static void free_cache_entry(TABLE *table)
{
DBUG_ENTER("free_cache_entry");
/* Assert that MERGE children are not attached before final close. */
DBUG_ASSERT(!table->is_children_attached());
/* This should be done before releasing table share. */
table_def_remove_table(table);
......@@ -1137,14 +1143,11 @@ static void mark_temp_tables_as_free_for_reuse(THD *thd)
{
table->query_id= 0;
table->file->ha_reset();
/*
Detach temporary MERGE children from temporary parent to allow new
attach at next open. Do not do the detach, if close_thread_tables()
is called from a sub-statement. The temporary table might still be
used in the top-level statement.
*/
if (table->child_l || table->parent)
detach_merge_children(table, TRUE);
/* Detach temporary MERGE children from temporary parent. */
DBUG_ASSERT(table->file);
table->file->extra(HA_EXTRA_DETACH_CHILDREN);
/*
Reset temporary table lock type to it's default value (TL_WRITE).
......@@ -1344,6 +1347,20 @@ void close_thread_tables(THD *thd,
table->s->table_name.str, (long) table));
#endif
/* Detach MERGE children after every statement. Even under LOCK TABLES. */
for (table= thd->open_tables; table; table= table->next)
{
/* Table might be in use by some outer statement. */
DBUG_PRINT("tcache", ("table: '%s' query_id: %lu",
table->s->table_name.str, (ulong) table->query_id));
if (thd->locked_tables_mode <= LTM_LOCK_TABLES ||
table->query_id == thd->query_id)
{
DBUG_ASSERT(table->file);
table->file->extra(HA_EXTRA_DETACH_CHILDREN);
}
}
/*
We are assuming here that thd->derived_tables contains ONLY derived
tables for this substatement. i.e. instead of approach which uses
......@@ -1481,12 +1498,6 @@ bool close_thread_table(THD *thd, TABLE **table_ptr)
safe_mutex_assert_owner(&LOCK_open);
*table_ptr=table->next;
/*
When closing a MERGE parent or child table, detach the children first.
Clear child table references to force new assignment at next open.
*/
if (table->child_l || table->parent)
detach_merge_children(table, TRUE);
table->mdl_lock_data= 0;
if (table->needs_reopen() ||
......@@ -1497,8 +1508,9 @@ bool close_thread_table(THD *thd, TABLE **table_ptr)
}
else
{
/* Assert that MERGE children are not attached in unused_tables. */
DBUG_ASSERT(!table->is_children_attached());
/* Avoid to have MERGE tables with attached children in unused_tables. */
DBUG_ASSERT(table->file);
table->file->extra(HA_EXTRA_DETACH_CHILDREN);
/* Free memory and reset for next loop */
free_field_buffers_larger_than(table,MAX_TDC_BLOB_SIZE);
......@@ -1964,19 +1976,6 @@ void close_temporary_table(THD *thd, TABLE *table,
table->s->db.str, table->s->table_name.str,
(long) table, table->alias));
/*
When closing a MERGE parent or child table, detach the children
first. Clear child table references as MERGE table cannot be
reopened after final close of one of its tables.
This is necessary here because it is sometimes called with attached
tables and without prior close_thread_tables(). E.g. in
mysql_alter_table(), mysql_rm_table_part2(), mysql_truncate(),
drop_open_table().
*/
if (table->child_l || table->parent)
detach_merge_children(table, TRUE);
if (table->prev)
{
table->prev->next= table->next;
......@@ -2462,15 +2461,10 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
if (table->s->table_cache_key.length == key_length &&
!memcmp(table->s->table_cache_key.str, key, key_length))
{
/*
When looking for a usable TABLE, ignore MERGE children, as they
belong to their parent and cannot be used explicitly.
*/
if (!my_strcasecmp(system_charset_info, table->alias, alias) &&
table->query_id != thd->query_id && /* skip tables already used */
(thd->locked_tables_mode == LTM_LOCK_TABLES ||
table->query_id == 0) &&
!table->parent)
table->query_id == 0))
{
int distance= ((int) table->reginfo.lock_type -
(int) table_list->lock_type);
......@@ -2622,6 +2616,16 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
if (share->is_view)
{
/*
If parent_l of the table_list is non null then a merge table
has this view as child table, which is not supported.
*/
if (table_list->parent_l)
{
my_error(ER_WRONG_MRG_TABLE, MYF(0));
goto err_unlock;
}
/*
This table is a view. Validate its metadata version: in particular,
that it was a view when the statement was prepared.
......@@ -2826,6 +2830,9 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
table->clear_column_bitmaps();
table_list->table= table;
DBUG_ASSERT(table->key_read == 0);
/* Tables may be reused in a sub statement. */
if (table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN))
table->file->extra(HA_EXTRA_DETACH_CHILDREN);
DBUG_RETURN(FALSE);
err_unlock:
......@@ -3537,340 +3544,6 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table,
}
/**
@brief Add list of MERGE children to a TABLE_LIST list.
@param[in] tlist the parent TABLE_LIST object just opened
@return status
@retval 0 OK
@retval != 0 Error
@detail
When a MERGE parent table has just been opened, insert the
TABLE_LIST chain from the MERGE handle into the table list used for
opening tables for this statement. This lets the children be opened
too.
*/
static int add_merge_table_list(TABLE_LIST *tlist)
{
TABLE *parent= tlist->table;
TABLE_LIST *child_l;
DBUG_ENTER("add_merge_table_list");
DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", parent->s->db.str,
parent->s->table_name.str, (long) parent));
/* Must not call this with attached children. */
DBUG_ASSERT(!parent->children_attached);
/* Must not call this with children list in place. */
DBUG_ASSERT(tlist->next_global != parent->child_l);
/* Prevent inclusion of another MERGE table. Could make infinite recursion. */
if (tlist->parent_l)
{
my_error(ER_ADMIN_WRONG_MRG_TABLE, MYF(0), tlist->alias);
DBUG_RETURN(1);
}
/* Fix children.*/
for (child_l= parent->child_l; ; child_l= child_l->next_global)
{
/*
Note: child_l->table may still be set if this parent was taken
from the unused_tables chain. Ignore this fact here. The
reference will be replaced by the handler in
::extra(HA_EXTRA_ATTACH_CHILDREN).
*/
/* Set lock type. */
child_l->lock_type= tlist->lock_type;
/* Set parent reference. */
child_l->parent_l= tlist;
/* Break when this was the last child. */
if (&child_l->next_global == parent->child_last_l)
break;
}
/* Insert children into the table list. */
*parent->child_last_l= tlist->next_global;
tlist->next_global= parent->child_l;
/*
Do not fix the prev_global pointers. We will remove the
chain soon anyway.
*/
DBUG_RETURN(0);
}
/**
@brief Attach MERGE children to the parent.
@param[in] tlist the child TABLE_LIST object just opened
@return status
@retval 0 OK
@retval != 0 Error
@note
This is called when the last MERGE child has just been opened, let
the handler attach the MyISAM tables to the MERGE table. Remove
MERGE TABLE_LIST chain from the statement list so that it cannot be
changed or freed.
*/
static int attach_merge_children(TABLE_LIST *tlist)
{
TABLE *parent= tlist->parent_l->table;
int error;
DBUG_ENTER("attach_merge_children");
DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", parent->s->db.str,
parent->s->table_name.str, (long) parent));
/* Must not call this with attached children. */
DBUG_ASSERT(!parent->children_attached);
/* Must call this with children list in place. */
DBUG_ASSERT(tlist->parent_l->next_global == parent->child_l);
/* Attach MyISAM tables to MERGE table. */
error= parent->file->extra(HA_EXTRA_ATTACH_CHILDREN);
/*
Remove children from the table list. Even in case of an error.
This should prevent tampering with them.
*/
tlist->parent_l->next_global= *parent->child_last_l;
/*
Do not fix the last childs next_global pointer. It is needed for
stepping to the next table in the enclosing loop in open_tables().
Do not fix prev_global pointers. We did not set them.
*/
if (error)
{
DBUG_PRINT("error", ("attaching MERGE children failed: %d", my_errno));
parent->file->print_error(error, MYF(0));
DBUG_RETURN(1);
}
parent->children_attached= TRUE;
DBUG_PRINT("myrg", ("attached parent: '%s'.'%s' 0x%lx", parent->s->db.str,
parent->s->table_name.str, (long) parent));
/*
Note that we have the cildren in the thd->open_tables list at this
point.
*/
DBUG_RETURN(0);
}
/**
@brief Detach MERGE children from the parent.
@note
Call this before the first table of a MERGE table (parent or child)
is closed.
When closing thread tables at end of statement, both parent and
children are in thd->open_tables and will be closed. In most cases
the children will be closed before the parent. They are opened after
the parent and thus stacked into thd->open_tables before it.
To avoid that we touch a closed children in any way, we must detach
the children from the parent when the first belonging table is
closed (parent or child).
All references to the children should be removed on handler level
and optionally on table level.
@note
Assure that you call it for a MERGE parent or child only.
Either table->child_l or table->parent must be set.
@param[in] table the TABLE object of the parent
@param[in] clear_refs if to clear TABLE references
this must be true when called from
close_thread_tables() to enable fresh
open in open_tables()
it must be false when called in preparation
for reopen_tables()
*/
void detach_merge_children(TABLE *table, bool clear_refs)
{
TABLE_LIST *child_l;
TABLE *parent= table->child_l ? table : table->parent;
bool first_detach;
DBUG_ENTER("detach_merge_children");
/*
Either table->child_l or table->parent must be set. Parent must have
child_l set.
*/
DBUG_ASSERT(parent && parent->child_l);
DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx clear_refs: %d",
table->s->db.str, table->s->table_name.str,
(long) table, clear_refs));
DBUG_PRINT("myrg", ("parent: '%s'.'%s' 0x%lx", parent->s->db.str,
parent->s->table_name.str, (long) parent));
/*
In a open_tables() loop it can happen that not all tables have their
children attached yet. Also this is called for every child and the
parent from close_thread_tables().
*/
if ((first_detach= parent->children_attached))
{
(void) parent->file->extra(HA_EXTRA_DETACH_CHILDREN);
parent->children_attached= FALSE;
DBUG_PRINT("myrg", ("detached parent: '%s'.'%s' 0x%lx", parent->s->db.str,
parent->s->table_name.str, (long) parent));
}
else
DBUG_PRINT("myrg", ("parent is already detached"));
if (clear_refs)
{
/* In any case clear the own parent reference. (***) */
table->parent= NULL;
/*
On the first detach, clear all references. If this table is the
parent, we still may need to clear the child references. The first
detach might not have done this.
*/
if (first_detach || (table == parent))
{
/* Clear TABLE references to force new assignment at next open. */
for (child_l= parent->child_l; ; child_l= child_l->next_global)
{
/*
Do not DBUG_ASSERT(child_l->table); open_tables might be
incomplete.
Clear the parent reference of the children only on the first
detach. The children might already be closed. They will clear
it themseves when this function is called for them with
'clear_refs' true. See above "(***)".
*/
if (first_detach && child_l->table)
child_l->table->parent= NULL;
/* Clear the table reference to force new assignment at next open. */
child_l->table= NULL;
/* Break when this was the last child. */
if (&child_l->next_global == parent->child_last_l)
break;
}
}
}
DBUG_VOID_RETURN;
}
/**
@brief Fix MERGE children after open.
@param[in] old_child_list first list member from original table
@param[in] old_last pointer to &next_global of last list member
@param[in] new_child_list first list member from freshly opened table
@param[in] new_last pointer to &next_global of last list member
@return mismatch
@retval FALSE OK, no mismatch
@retval TRUE Error, lists mismatch
@detail
Main action is to copy TABLE reference for each member of original
child list to new child list. After a fresh open these references
are NULL. Assign the old children to the new table. Some of them
might also be reopened or will be reopened soon.
Other action is to verify that the table definition with respect to
the UNION list did not change.
@note
This function terminates the child list if the respective '*_last'
pointer is non-NULL. Do not call it from a place where the list is
embedded in another list and this would break it.
Terminating the list is required for example in the first
reopen_table() after open_tables(). open_tables() requires the end
of the list not to be terminated because other tables could follow
behind the child list.
If a '*_last' pointer is NULL, the respective list is assumed to be
NULL terminated.
*/
bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last,
TABLE_LIST *new_child_list, TABLE_LIST **new_last)
{
bool mismatch= FALSE;
DBUG_ENTER("fix_merge_after_open");
DBUG_PRINT("myrg", ("old last addr: 0x%lx new last addr: 0x%lx",
(long) old_last, (long) new_last));
/* Terminate the lists for easier check of list end. */
if (old_last)
*old_last= NULL;
if (new_last)
*new_last= NULL;
for (;;)
{
DBUG_PRINT("myrg", ("old list item: 0x%lx new list item: 0x%lx",
(long) old_child_list, (long) new_child_list));
/* Break if one of the list is at its end. */
if (!old_child_list || !new_child_list)
break;
/* Old table has references to child TABLEs. */
DBUG_ASSERT(old_child_list->table);
/* New table does not yet have references to child TABLEs. */
DBUG_ASSERT(!new_child_list->table);
DBUG_PRINT("myrg", ("old table: '%s'.'%s' new table: '%s'.'%s'",
old_child_list->db, old_child_list->table_name,
new_child_list->db, new_child_list->table_name));
/* Child db.table names must match. */
if (strcmp(old_child_list->table_name, new_child_list->table_name) ||
strcmp(old_child_list->db, new_child_list->db))
break;
/*
Copy TABLE reference. Child TABLE objects are still in place
though not necessarily open yet.
*/
DBUG_PRINT("myrg", ("old table ref: 0x%lx replaces new table ref: 0x%lx",
(long) old_child_list->table,
(long) new_child_list->table));
new_child_list->table= old_child_list->table;
/* Step both lists. */
old_child_list= old_child_list->next_global;
new_child_list= new_child_list->next_global;
}
DBUG_PRINT("myrg", ("end of list, mismatch: %d", mismatch));
/*
If the list pointers are not both NULL after the loop, then the
lists differ. If the are both identical, but not NULL, then they
have at least one table in common and hence the rest of the list
would be identical too. But in this case the loop woul run until the
list end, where both pointers would become NULL.
*/
if (old_child_list != new_child_list)
mismatch= TRUE;
if (mismatch)
my_error(ER_TABLE_DEF_CHANGED, MYF(0));
DBUG_RETURN(mismatch);
}
/*
Return a appropriate read lock type given a table object.
......@@ -4079,19 +3752,6 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
if (error)
{
/*
If in a MERGE table open, we need to remove the children list
from statement table list before restarting. Otherwise the list
will be inserted another time.
*/
if (tables->parent_l)
{
TABLE_LIST *parent_l= tables->parent_l;
/* The parent table should be correctly open at this point. */
DBUG_ASSERT(parent_l->table);
parent_l->next_global= *parent_l->table->child_last_l;
}
if (action)
{
/*
......@@ -4132,7 +3792,7 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
}
result= -1; // Fatal error
break;
goto err;
}
/*
......@@ -4222,16 +3882,15 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
goto err;
}
/* Attach MERGE children if not locked already. */
DBUG_PRINT("tcache", ("is parent: %d is child: %d",
test(tables->table->child_l),
test(tables->parent_l)));
if ((!thd->locked_tables_mode || tables->table->s->tmp_table) &&
((tables->table->child_l &&
add_merge_table_list(tables)) ||
(tables->parent_l &&
(&tables->next_global == tables->parent_l->table->child_last_l) &&
attach_merge_children(tables))))
/*
After opening a MERGE table add the children to the query list of
tables, so that they are opened too.
Note that placeholders don't have the handler open.
*/
/* MERGE tables need to access parent and child TABLE_LISTs. */
DBUG_ASSERT(tables->table->pos_in_table_list == tables);
/* Non-MERGE tables ignore this call. */
if (tables->table->file->extra(HA_EXTRA_ADD_CHILDREN_LIST))
{
result= -1;
goto err;
......@@ -4263,6 +3922,29 @@ process_view_routines:
}
}
/*
After successful open of all tables, including MERGE parents and
children, attach the children to their parents. At end of statement,
the children are detached. Attaching and detaching are always done,
even under LOCK TABLES.
*/
for (tables= *start; tables; tables= tables->next_global)
{
TABLE *tbl= tables->table;
/* Schema tables may not have a TABLE object here. */
if (tbl && tbl->file->ht->db_type == DB_TYPE_MRG_MYISAM)
{
/* MERGE tables need to access parent and child TABLE_LISTs. */
DBUG_ASSERT(tbl->pos_in_table_list == tables);
if (tbl->file->extra(HA_EXTRA_ATTACH_CHILDREN))
{
result= -1;
goto err;
}
}
}
err:
thd_proc_info(thd, 0);
free_root(&new_frm_mem, MYF(0)); // Free pre-alloced block
......@@ -4272,17 +3954,6 @@ process_view_routines:
if (result && tables)
{
/*
Some functions determine success as (tables->table != NULL).
tables->table is in thd->open_tables. It won't go lost. If the
error happens on a MERGE child, clear the parents TABLE reference.
*/
if (tables->parent_l)
{
if (tables->parent_l->next_global == tables->parent_l->table->child_l)
tables->parent_l->next_global= *tables->parent_l->table->child_last_l;
tables->parent_l->table= NULL;
}
tables->table= NULL;
}
DBUG_PRINT("tcache", ("returning: %d", result));
......@@ -4447,7 +4118,7 @@ retry:
*/
DBUG_ASSERT(table_list->table);
table= table_list->table;
if (table->child_l)
if (table->file->ht->db_type == DB_TYPE_MRG_MYISAM)
{
/* A MERGE table must not come here. */
/* purecov: begin tested */
......@@ -4896,7 +4567,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count,
and was marked as occupied during open_tables() as free for reuse.
*/
mark_real_tables_as_free_for_reuse(first_not_own);
DBUG_PRINT("info",("locked_tables_mode= PRELOCKED"));
DBUG_PRINT("info",("locked_tables_mode= LTM_PRELOCKED"));
thd->locked_tables_mode= LTM_PRELOCKED;
}
}
......@@ -8481,9 +8152,7 @@ void close_performance_schema_table(THD *thd, Open_tables_state *backup)
/*
Note that we need to hold LOCK_open while changing the
open_tables list. Another thread may work on it.
(See: mysql_notify_thread_having_shared_lock())
Closing a MERGE child before the parent would be fatal if the
other thread tries to abort the MERGE lock in between.
(See: notify_thread_having_shared_lock())
*/
while (thd->open_tables)
found_old_table|= close_thread_table(thd, &thd->open_tables);
......
......@@ -4396,7 +4396,8 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table,
"Failed to open partially restored table"));
}
/* A MERGE table must not come here. */
DBUG_ASSERT(!table->table || !table->table->child_l);
DBUG_ASSERT(!table->table ||
table->table->file->ht->db_type != DB_TYPE_MRG_MYISAM);
DBUG_RETURN(0);
}
......@@ -4463,7 +4464,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
}
/* A MERGE table must not come here. */
DBUG_ASSERT(!table->child_l);
DBUG_ASSERT(table->file->ht->db_type != DB_TYPE_MRG_MYISAM);
/*
REPAIR TABLE ... USE_FRM for temporary tables makes little sense.
......@@ -7270,7 +7271,8 @@ view_err:
}
else
{
if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
if (!table->s->tmp_table &&
wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
goto err_new_table_cleanup;
thd_proc_info(thd, "manage keys");
alter_table_manage_keys(table, table->file->indexes_are_disabled(),
......
......@@ -4555,24 +4555,6 @@ void TABLE::mark_columns_needed_for_insert()
}
/**
@brief Check if this is part of a MERGE table with attached children.
@return status
@retval TRUE children are attached
@retval FALSE no MERGE part or children not attached
@detail
A MERGE table consists of a parent TABLE and zero or more child
TABLEs. Each of these TABLEs is called a part of a MERGE table.
*/
bool TABLE::is_children_attached(void)
{
return((child_l && children_attached) ||
(parent && parent->children_attached));
}
/*
Cleanup this table for re-execution.
......
......@@ -635,11 +635,6 @@ private:
public:
/* For the below MERGE related members see top comment in ha_myisammrg.cc */
TABLE *parent; /* Set in MERGE child. Ptr to parent */
TABLE_LIST *child_l; /* Set in MERGE parent. List of children */
TABLE_LIST **child_last_l; /* Set in MERGE parent. End of list */
THD *in_use; /* Which thread uses this */
Field **field; /* Pointer to fields */
......@@ -809,8 +804,6 @@ public:
my_bool insert_or_update; /* Can be used by the handler */
my_bool alias_name_used; /* true if table_name is alias */
my_bool get_fields_in_item_tree; /* Signal to fix_field */
/* If MERGE children attached to parent. See top comment in ha_myisammrg.cc */
my_bool children_attached;
REGINFO reginfo; /* field connections */
MEM_ROOT mem_root;
......@@ -861,7 +854,6 @@ public:
*/
inline bool needs_reopen()
{ return s->version != refresh_version; }
bool is_children_attached(void);
};
......
......@@ -33,40 +33,29 @@
and hence through open_tables(). When the parent appears in the list
of tables to open, the initial open of the handler does nothing but
read the meta file and collect a list of TABLE_LIST objects for the
children. This list is attached to the parent TABLE object as
TABLE::child_l. The end of the children list is saved in
TABLE::child_last_l.
Back in open_tables(), add_merge_table_list() is called. It updates
each list member with the lock type and a back pointer to the parent
TABLE_LIST object TABLE_LIST::parent_l. The list is then inserted in
the list of tables to open, right behind the parent. Consequently,
open_tables() opens the children, one after the other. The TABLE
references of the TABLE_LIST objects are implicitly set to the open
tables. The children are opened as independent MyISAM tables, right as
if they are used by the SQL statement.
TABLE_LIST::parent_l is required to find the parent 1. when the last
child has been opened and children are to be attached, and 2. when an
error happens during child open and the child list must be removed
from the queuery list. In these cases the current child does not have
TABLE::parent set or does not have a TABLE at all respectively.
When the last child is open, attach_merge_children() is called. It
removes the list of children from the open list. Then the children are
"attached" to the parent. All required references between parent and
children. This list is attached to the handler object as
ha_myisammrg::children_l. The end of the children list is saved in
ha_myisammrg::children_last_l.
Back in open_tables(), handler::extra(HA_EXTRA_ADD_CHILDREN_LIST) is
called. It updates each list member with the lock type and a back
pointer to the parent TABLE_LIST object TABLE_LIST::parent_l. The list
is then inserted in the list of tables to open, right behind the
parent. Consequently, open_tables() opens the children, one after the
other. The TABLE references of the TABLE_LIST objects are implicitly
set to the open tables by open_table(). The children are opened as
independent MyISAM tables, right as if they are used by the SQL
statement.
When all tables from the statement query list are open,
handler::extra(HA_EXTRA_ATTACH_CHILDREN) is called. It "attaches" the
children to the parent. All required references between parent and
children are set up.
The MERGE storage engine sets up an array with references to the
low-level MyISAM table objects (MI_INFO). It remembers the state of
the table in MYRG_INFO::children_attached.
Every child TABLE::parent references the parent TABLE object. That way
TABLE objects belonging to a MERGE table can be identified.
TABLE::parent is required because the parent and child TABLE objects
can live longer than the parent TABLE_LIST object. So the path
child->pos_in_table_list->parent_l->table can be broken.
If necessary, the compatibility of parent and children is checked.
This check is necessary when any of the objects are reopend. This is
detected by comparing the current table def version against the
......@@ -80,14 +69,20 @@
myisammrg_attach_children_callback() sets it ot TRUE if a table
def version mismatches the remembered child def version.
Finally the parent TABLE::children_attached is set.
The children chain remains in the statement query list until the table
is closed or the children are detached. This is done so that the
children are locked by lock_tables().
At statement end the children are detached. At the next statement
begin the open-add-attach sequence repeats. There is no exception for
LOCK TABLES. The fresh establishment of the parent-child relationship
before every statement catches numerous cases of ALTER/FLUSH/DROP/etc
of parent or children during LOCK TABLES.
---
On parent open the storage engine structures are allocated and initialized.
They stay with the open table until its final close.
*/
#ifdef USE_PRAGMA_IMPLEMENTATION
......@@ -118,7 +113,10 @@ static handler *myisammrg_create_handler(handlerton *hton,
ha_myisammrg::ha_myisammrg(handlerton *hton, TABLE_SHARE *table_arg)
:handler(hton, table_arg), file(0), is_cloned(0)
{}
{
init_sql_alloc(&children_mem_root, max(4 * sizeof(TABLE_LIST), FN_REFLEN) +
ALLOC_ROOT_MIN_BLOCK_SIZE, 0);
}
/**
......@@ -126,7 +124,9 @@ ha_myisammrg::ha_myisammrg(handlerton *hton, TABLE_SHARE *table_arg)
*/
ha_myisammrg::~ha_myisammrg(void)
{}
{
free_root(&children_mem_root, MYF(0));
}
static const char *ha_myisammrg_exts[] = {
......@@ -178,45 +178,48 @@ const char *ha_myisammrg::index_type(uint key_number)
/**
@brief Callback function for open of a MERGE parent table.
@detail This function adds a TABLE_LIST object for a MERGE child table
to a list of tables of the parent TABLE object. It is called for
each child table.
The list of child TABLE_LIST objects is kept in the TABLE object of
the parent for the whole life time of the MERGE table. It is
inserted in the statement list behind the MERGE parent TABLE_LIST
object when the MERGE table is opened. It is removed from the
statement list after the last child is opened.
All memeory used for the child TABLE_LIST objects and the strings
referred by it are taken from the parent TABLE::mem_root. Thus they
are all freed implicitly at the final close of the table.
TABLE::child_l -> TABLE_LIST::next_global -> TABLE_LIST::next_global
# # ^ # ^
# # | # |
# # +--------- TABLE_LIST::prev_global
# # |
# |<--- TABLE_LIST::prev_global |
# |
TABLE::child_last_l -----------------------------------------+
Callback function for open of a MERGE parent table.
@param[in] callback_param data pointer as given to myrg_parent_open()
this is used to pass the handler handle
@param[in] filename file name of MyISAM table
without extension.
@return status
@retval 0 OK
@retval != 0 Error
@detail
This function adds a TABLE_LIST object for a MERGE child table to a
list of tables in the parent handler object. It is called for each
child table.
The list of child TABLE_LIST objects is kept in the handler object
of the parent for the whole life time of the MERGE table. It is
inserted in the statement query list behind the MERGE parent
TABLE_LIST object when the MERGE table is opened. It is removed from
the statement query list at end of statement or at children detach.
All memory used for the child TABLE_LIST objects and the strings
referred by it are taken from the parent
ha_myisammrg::children_mem_root. Thus they are all freed implicitly at
the final close of the table.
children_l -> TABLE_LIST::next_global -> TABLE_LIST::next_global
# # ^ # ^
# # | # |
# # +--------- TABLE_LIST::prev_global
# # |
# |<--- TABLE_LIST::prev_global |
# |
children_last_l -----------------------------------------+
*/
static int myisammrg_parent_open_callback(void *callback_param,
const char *filename)
{
ha_myisammrg *ha_myrg;
TABLE *parent;
ha_myisammrg *ha_myrg= (ha_myisammrg*) callback_param;
TABLE_LIST *child_l;
const char *db;
const char *table_name;
......@@ -242,11 +245,8 @@ static int myisammrg_parent_open_callback(void *callback_param,
dirlen-= db - dir_path; /* This is now the length of 'db'. */
DBUG_PRINT("myrg", ("open: '%s'.'%s'", db, table_name));
ha_myrg= (ha_myisammrg*) callback_param;
parent= ha_myrg->table_ptr();
/* Get a TABLE_LIST object. */
if (!(child_l= (TABLE_LIST*) alloc_root(&parent->mem_root,
if (!(child_l= (TABLE_LIST*) alloc_root(&ha_myrg->children_mem_root,
sizeof(TABLE_LIST))))
{
/* purecov: begin inspected */
......@@ -258,68 +258,231 @@ static int myisammrg_parent_open_callback(void *callback_param,
/* Set database (schema) name. */
child_l->db_length= dirlen;
child_l->db= strmake_root(&parent->mem_root, db, dirlen);
child_l->db= strmake_root(&ha_myrg->children_mem_root, db, dirlen);
/* Set table name. */
child_l->table_name_length= strlen(table_name);
child_l->table_name= strmake_root(&parent->mem_root, table_name,
child_l->table_name= strmake_root(&ha_myrg->children_mem_root, table_name,
child_l->table_name_length);
/* Convert to lowercase if required. */
if (lower_case_table_names && child_l->table_name_length)
{
/* purecov: begin tested */
child_l->table_name_length= my_casedn_str(files_charset_info,
child_l->table_name);
/* purecov: end */
}
/* Set alias. */
child_l->alias= child_l->table_name;
/*
FIXME: Actually we should use some other mem-root here.
To be fixed once Ingo pushes his patch for WL4144.
*/
alloc_mdl_locks(child_l, &parent->mem_root);
/* Initialize table map to 'undefined'. */
child_l->init_child_def_version();
/* Link TABLE_LIST object into the parent list. */
if (!parent->child_last_l)
/* Link TABLE_LIST object into the children list. */
if (ha_myrg->children_last_l)
child_l->prev_global= ha_myrg->children_last_l;
else
{
/* Initialize parent->child_last_l when handling first child. */
parent->child_last_l= &parent->child_l;
/* Initialize ha_myrg->children_last_l when handling first child. */
ha_myrg->children_last_l= &ha_myrg->children_l;
}
*parent->child_last_l= child_l;
child_l->prev_global= parent->child_last_l;
parent->child_last_l= &child_l->next_global;
*ha_myrg->children_last_l= child_l;
ha_myrg->children_last_l= &child_l->next_global;
DBUG_RETURN(0);
}
/**
@brief Callback function for attaching a MERGE child table.
Open a MERGE parent table, but not its children.
@detail This function retrieves the MyISAM table handle from the
next child table. It is called for each child table.
@param[in] name MERGE table path name
@param[in] mode read/write mode, unused
@param[in] test_if_locked open flags
@return status
@retval 0 OK
@retval -1 Error, my_errno gives reason
@detail
This function initializes the MERGE storage engine structures
and adds a child list of TABLE_LIST to the parent handler.
*/
int ha_myisammrg::open(const char *name, int mode __attribute__((unused)),
uint test_if_locked)
{
DBUG_ENTER("ha_myisammrg::open");
DBUG_PRINT("myrg", ("name: '%s' table: 0x%lx", name, (long) table));
DBUG_PRINT("myrg", ("test_if_locked: %u", test_if_locked));
/* Must not be used when table is open. */
DBUG_ASSERT(!this->file);
/* Save for later use. */
this->test_if_locked= test_if_locked;
/* In case this handler was open and closed before, free old data. */
free_root(&this->children_mem_root, MYF(MY_MARK_BLOCKS_FREE));
@param[in] callback_param data pointer as given to
myrg_attach_children()
/*
Initialize variables that are used, modified, and/or set by
myisammrg_parent_open_callback().
'children_l' is the head of the children chain.
'children_last_l' points to the end of the children chain.
'my_errno' is set by myisammrg_parent_open_callback() in
case of an error.
*/
children_l= NULL;
children_last_l= NULL;
my_errno= 0;
/* retrieve children table list. */
if (is_cloned)
{
/*
Open and attaches the MyISAM tables,that are under the MERGE table
parent, on the MyISAM storage engine interface directly within the
MERGE engine. The new MyISAM table instances, as well as the MERGE
clone itself, are not visible in the table cache. This is not a
problem because all locking is handled by the original MERGE table
from which this is cloned of.
*/
if (!(file= myrg_open(name, table->db_stat, HA_OPEN_IGNORE_IF_LOCKED)))
{
DBUG_PRINT("error", ("my_errno %d", my_errno));
DBUG_RETURN(my_errno ? my_errno : -1);
}
file->children_attached= TRUE;
info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST);
}
else if (!(file= myrg_parent_open(name, myisammrg_parent_open_callback, this)))
{
/* purecov: begin inspected */
DBUG_PRINT("error", ("my_errno %d", my_errno));
DBUG_RETURN(my_errno ? my_errno : -1);
/* purecov: end */
}
DBUG_PRINT("myrg", ("MYRG_INFO: 0x%lx child tables: %u",
(long) file, file->tables));
DBUG_RETURN(0);
}
/**
Add list of MERGE children to a TABLE_LIST chain.
@return status
@retval 0 OK
@retval != 0 Error
@detail
When a MERGE parent table has just been opened, insert the
TABLE_LIST chain from the MERGE handle into the table list used for
opening tables for this statement. This lets the children be opened
too.
*/
int ha_myisammrg::add_children_list(void)
{
TABLE_LIST *parent_l= this->table->pos_in_table_list;
TABLE_LIST *child_l;
THD *thd= current_thd;
DBUG_ENTER("ha_myisammrg::add_children_list");
DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", this->table->s->db.str,
this->table->s->table_name.str, (long) this->table));
/* Must call this with open table. */
DBUG_ASSERT(this->file);
/* Ignore this for empty MERGE tables (UNION=()). */
if (!this->file->tables)
{
DBUG_PRINT("myrg", ("empty merge table union"));
goto end;
}
/* Must not call this with attached children. */
DBUG_ASSERT(!this->file->children_attached);
/* Must not call this with children list in place. */
DBUG_ASSERT(parent_l->next_global != this->children_l);
/*
Prevent inclusion of another MERGE table, which could make infinite
recursion.
*/
if (parent_l->parent_l)
{
my_error(ER_ADMIN_WRONG_MRG_TABLE, MYF(0), parent_l->alias);
DBUG_RETURN(1);
}
/* Fix children. */
DBUG_ASSERT(this->children_l);
for (child_l= this->children_l; ; child_l= child_l->next_global)
{
DBUG_ASSERT(!child_l->table);
/* Set lock type. */
child_l->lock_type= parent_l->lock_type;
/* Set parent reference. Used to detect MERGE in children list. */
child_l->parent_l= parent_l;
/* Copy select_lex. Used in unique_table() at least. */
child_l->select_lex= parent_l->select_lex;
child_l->mdl_lock_data= NULL; /* Safety, if alloc_mdl_locks fails. */
/* Break when this was the last child. */
if (&child_l->next_global == this->children_last_l)
break;
}
alloc_mdl_locks(children_l,
thd->locked_tables_root ? thd->locked_tables_root :
thd->mem_root);
/* Insert children into the table list. */
if (parent_l->next_global)
parent_l->next_global->prev_global= this->children_last_l;
*this->children_last_l= parent_l->next_global;
parent_l->next_global= this->children_l;
this->children_l->prev_global= &parent_l->next_global;
end:
DBUG_RETURN(0);
}
/**
Callback function for attaching a MERGE child table.
@param[in] callback_param data pointer as given to myrg_attach_children()
this is used to pass the handler handle
@return pointer to open MyISAM table structure
@retval !=NULL OK, returning pointer
@retval NULL, my_errno == 0 Ok, no more child tables
@retval NULL, my_errno != 0 error
@detail
This function retrieves the MyISAM table handle from the
next child table. It is called for each child table.
*/
static MI_INFO *myisammrg_attach_children_callback(void *callback_param)
{
ha_myisammrg *ha_myrg;
TABLE *parent;
ha_myisammrg *ha_myrg= (ha_myisammrg*) callback_param;
TABLE *parent= ha_myrg->table_ptr();
TABLE *child;
TABLE_LIST *child_l;
MI_INFO *myisam;
DBUG_ENTER("myisammrg_attach_children_callback");
my_errno= 0;
ha_myrg= (ha_myisammrg*) callback_param;
parent= ha_myrg->table_ptr();
/* Get child list item. */
child_l= ha_myrg->next_child_attach;
......@@ -329,13 +492,11 @@ static MI_INFO *myisammrg_attach_children_callback(void *callback_param)
DBUG_RETURN(NULL);
}
child= child_l->table;
DBUG_PRINT("myrg", ("child table: '%s'.'%s' 0x%lx", child->s->db.str,
child->s->table_name.str, (long) child));
/*
Prepare for next child. Used as child_l in next call to this function.
We cannot rely on a NULL-terminated chain.
*/
if (&child_l->next_global == parent->child_last_l)
if (&child_l->next_global == ha_myrg->children_last_l)
{
DBUG_PRINT("myrg", ("attaching last child"));
ha_myrg->next_child_attach= NULL;
......@@ -343,9 +504,6 @@ static MI_INFO *myisammrg_attach_children_callback(void *callback_param)
else
ha_myrg->next_child_attach= child_l->next_global;
/* Set parent reference. */
child->parent= parent;
/*
Do a quick compatibility check. The table def version is set when
the table share is created. The child def version is copied
......@@ -394,63 +552,6 @@ static MI_INFO *myisammrg_attach_children_callback(void *callback_param)
}
/**
@brief Open a MERGE parent table, not its children.
@detail This function initializes the MERGE storage engine structures
and adds a child list of TABLE_LIST to the parent TABLE.
@param[in] name MERGE table path name
@param[in] mode read/write mode, unused
@param[in] test_if_locked open flags
@return status
@retval 0 OK
@retval -1 Error, my_errno gives reason
*/
int ha_myisammrg::open(const char *name, int mode __attribute__((unused)),
uint test_if_locked)
{
DBUG_ENTER("ha_myisammrg::open");
DBUG_PRINT("myrg", ("name: '%s' table: 0x%lx", name, (long) table));
DBUG_PRINT("myrg", ("test_if_locked: %u", test_if_locked));
/* Save for later use. */
this->test_if_locked= test_if_locked;
/* retrieve children table list. */
my_errno= 0;
if (is_cloned)
{
/*
Open and attaches the MyISAM tables,that are under the MERGE table
parent, on the MyISAM storage engine interface directly within the
MERGE engine. The new MyISAM table instances, as well as the MERGE
clone itself, are not visible in the table cache. This is not a
problem because all locking is handled by the original MERGE table
from which this is cloned of.
*/
if (!(file= myrg_open(table->s->normalized_path.str, table->db_stat,
HA_OPEN_IGNORE_IF_LOCKED)))
{
DBUG_PRINT("error", ("my_errno %d", my_errno));
DBUG_RETURN(my_errno ? my_errno : -1);
}
file->children_attached= TRUE;
info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST);
}
else if (!(file= myrg_parent_open(name, myisammrg_parent_open_callback, this)))
{
DBUG_PRINT("error", ("my_errno %d", my_errno));
DBUG_RETURN(my_errno ? my_errno : -1);
}
DBUG_PRINT("myrg", ("MYRG_INFO: 0x%lx", (long) file));
DBUG_RETURN(0);
}
/**
Returns a cloned instance of the current handler.
......@@ -502,22 +603,24 @@ handler *ha_myisammrg::clone(MEM_ROOT *mem_root)
/**
@brief Attach children to a MERGE table.
Attach children to a MERGE table.
@return status
@retval 0 OK
@retval != 0 Error, my_errno gives reason
@detail Let the storage engine attach its children through a callback
@detail
Let the storage engine attach its children through a callback
function. Check table definitions for consistency.
@note Special thd->open_options may be in effect. We can make use of
@note
Special thd->open_options may be in effect. We can make use of
them in attach. I.e. we use HA_OPEN_FOR_REPAIR to report the names
of mismatching child tables. We cannot transport these options in
ha_myisammrg::test_if_locked because they may change after the
parent is opened. The parent is kept open in the table cache over
multiple statements and can be used by other threads. Open options
can change over time.
@return status
@retval 0 OK
@retval != 0 Error, my_errno gives reason
*/
int ha_myisammrg::attach_children(void)
......@@ -532,8 +635,27 @@ int ha_myisammrg::attach_children(void)
DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
table->s->table_name.str, (long) table));
DBUG_PRINT("myrg", ("test_if_locked: %u", this->test_if_locked));
/* Must call this with open table. */
DBUG_ASSERT(this->file);
/*
A MERGE table with no children (empty union) is always seen as
attached internally.
*/
if (!this->file->tables)
{
DBUG_PRINT("myrg", ("empty merge table union"));
goto end;
}
DBUG_PRINT("myrg", ("child tables: %u", this->file->tables));
/* Must not call this with attached children. */
DBUG_ASSERT(!this->file->children_attached);
/* Must call this with children list in place. */
DBUG_ASSERT(this->table->pos_in_table_list->next_global == this->children_l);
/*
Initialize variables that are used, modified, and/or set by
myisammrg_attach_children_callback().
......@@ -546,7 +668,7 @@ int ha_myisammrg::attach_children(void)
'my_errno' is set by myisammrg_attach_children_callback() in
case of an error.
*/
next_child_attach= table->child_l;
next_child_attach= this->children_l;
need_compat_check= FALSE;
my_errno= 0;
......@@ -555,8 +677,8 @@ int ha_myisammrg::attach_children(void)
myisammrg_attach_children_callback, this,
(my_bool *) &need_compat_check))
{
DBUG_PRINT("error", ("my_errno %d", my_errno));
DBUG_RETURN(my_errno ? my_errno : -1);
error= my_errno;
goto err;
}
DBUG_PRINT("myrg", ("calling myrg_extrafunc"));
myrg_extrafunc(file, query_cache_invalidate_by_MyISAM_filename_ref);
......@@ -583,7 +705,11 @@ int ha_myisammrg::attach_children(void)
DBUG_PRINT("error",("reclength: %lu mean_rec_length: %lu",
table->s->reclength, stats.mean_rec_length));
if (test_if_locked & HA_OPEN_FOR_REPAIR)
{
/* purecov: begin inspected */
myrg_print_wrong_table(file->open_tables->table->filename);
/* purecov: end */
}
error= HA_ERR_WRONG_MRG_TABLE_DEF;
goto err;
}
......@@ -614,20 +740,23 @@ int ha_myisammrg::attach_children(void)
my_free((uchar*) recinfo, MYF(0));
goto err;
}
/* purecov: begin inspected */
myrg_print_wrong_table(u_table->table->filename);
/* purecov: end */
}
}
my_free((uchar*) recinfo, MYF(0));
if (error == HA_ERR_WRONG_MRG_TABLE_DEF)
goto err;
goto err; /* purecov: inspected */
/* All checks passed so far. Now update child def version. */
for (child_l= table->child_l; ; child_l= child_l->next_global)
DBUG_ASSERT(this->children_l);
for (child_l= this->children_l; ; child_l= child_l->next_global)
{
child_l->set_child_def_version(
child_l->table->s->get_table_def_version());
if (&child_l->next_global == table->child_last_l)
if (&child_l->next_global == this->children_last_l)
break;
}
}
......@@ -640,50 +769,115 @@ int ha_myisammrg::attach_children(void)
goto err;
}
#endif
end:
DBUG_RETURN(0);
err:
myrg_detach_children(file);
DBUG_PRINT("error", ("attaching MERGE children failed: %d", error));
print_error(error, MYF(0));
detach_children();
DBUG_RETURN(my_errno= error);
}
/**
@brief Detach all children from a MERGE table.
@note Detach must not touch the children in any way.
They may have been closed at ths point already.
All references to the children should be removed.
Detach all children from a MERGE table and from the query list of tables.
@return status
@retval 0 OK
@retval != 0 Error, my_errno gives reason
@note
Detach must not touch the child TABLE objects in any way.
They may have been closed at ths point already.
All references to the children should be removed.
*/
int ha_myisammrg::detach_children(void)
{
TABLE_LIST *child_l;
DBUG_ENTER("ha_myisammrg::detach_children");
DBUG_ASSERT(this->file && this->file->children_attached);
/* Must call this with open table. */
DBUG_ASSERT(this->file);
/* A MERGE table with no children (empty union) cannot be detached. */
if (!this->file->tables)
{
DBUG_PRINT("myrg", ("empty merge table union"));
goto end;
}
/* Clear TABLE references. */
DBUG_ASSERT(this->children_l);
for (child_l= this->children_l; ; child_l= child_l->next_global)
{
/*
Do not DBUG_ASSERT(child_l->table); open_tables might be
incomplete.
Clear the table reference.
*/
child_l->table= NULL;
/* Break when this was the last child. */
if (&child_l->next_global == this->children_last_l)
break;
}
/*
Remove children from the table list. This won't fail if called
twice. The list is terminated after removal.
If the parent is LEX::query_tables_own_last and pre-locked tables
follow (tables used by stored functions or triggers), the children
are inserted behind the parent and before the pre-locked tables. But
we do not adjust LEX::query_tables_own_last. The pre-locked tables
could have chopped off the list by clearing
*LEX::query_tables_own_last. This did also chop off the children. If
we would copy the reference from *this->children_last_l in this
case, we would put the chopped off pre-locked tables back to the
list. So we refrain from copying it back, if the destination has
been set to NULL meanwhile.
*/
if (this->children_l->prev_global && *this->children_l->prev_global)
*this->children_l->prev_global= *this->children_last_l;
if (*this->children_last_l)
(*this->children_last_l)->prev_global= this->children_l->prev_global;
/* Terminate child list. So it cannot be tried to remove again. */
*this->children_last_l= NULL;
this->children_l->prev_global= NULL;
if (!this->file->children_attached)
{
DBUG_PRINT("myrg", ("merge children are already detached"));
goto end;
}
if (myrg_detach_children(this->file))
{
/* purecov: begin inspected */
DBUG_PRINT("error", ("my_errno %d", my_errno));
print_error(my_errno, MYF(0));
DBUG_RETURN(my_errno ? my_errno : -1);
/* purecov: end */
}
end:
DBUG_RETURN(0);
}
/**
@brief Close a MERGE parent table, not its children.
@note The children are expected to be closed separately by the caller.
Close a MERGE parent table, but not its children.
@return status
@retval 0 OK
@retval != 0 Error, my_errno gives reason
@note
The children are expected to be closed separately by the caller.
*/
int ha_myisammrg::close(void)
......@@ -691,11 +885,12 @@ int ha_myisammrg::close(void)
int rc;
DBUG_ENTER("ha_myisammrg::close");
/*
Children must not be attached here. Unless the MERGE table has no
children or the handler instance has been cloned. In these cases
children_attached is always true.
There are cases where children are not explicitly detached before
close. detach_children() protects itself against double detach.
*/
DBUG_ASSERT(!this->file->children_attached || !this->file->tables || this->is_cloned);
if (!is_cloned)
detach_children();
rc= myrg_close(file);
file= 0;
DBUG_RETURN(rc);
......@@ -973,13 +1168,23 @@ int ha_myisammrg::info(uint flag)
int ha_myisammrg::extra(enum ha_extra_function operation)
{
if (operation == HA_EXTRA_ATTACH_CHILDREN)
if (operation == HA_EXTRA_ADD_CHILDREN_LIST)
{
int rc= add_children_list();
return(rc);
}
else if (operation == HA_EXTRA_ATTACH_CHILDREN)
{
int rc= attach_children();
if (!rc)
(void) extra(HA_EXTRA_NO_READCHECK); // Not needed in SQL
return(rc);
}
else if (operation == HA_EXTRA_IS_ATTACHED_CHILDREN)
{
/* For the upper layer pretend empty MERGE union is never attached. */
return(file && file->tables && file->children_attached);
}
else if (operation == HA_EXTRA_DETACH_CHILDREN)
{
/*
......@@ -1000,6 +1205,7 @@ int ha_myisammrg::extra(enum ha_extra_function operation)
int ha_myisammrg::reset(void)
{
/* This is normally called with detached children. */
return myrg_reset(file);
}
......@@ -1015,21 +1221,24 @@ int ha_myisammrg::extra_opt(enum ha_extra_function operation, ulong cache_size)
int ha_myisammrg::external_lock(THD *thd, int lock_type)
{
DBUG_ASSERT(this->file->children_attached);
return myrg_lock_database(file,lock_type);
/*
This can be called with no children attached. E.g. FLUSH TABLES
unlocks and re-locks tables under LOCK TABLES, but it does not open
them first. So they are detached all the time. But locking of the
children should work anyway because thd->open_tables is not changed
during FLUSH TABLES.
If this handler instance has been cloned, we still must call
myrg_lock_database().
*/
if (is_cloned)
return myrg_lock_database(file, lock_type);
return 0;
}
uint ha_myisammrg::lock_count(void) const
{
/*
Return the real lock count even if the children are not attached.
This method is used for allocating memory. If we would return 0
to another thread (e.g. doing FLUSH TABLE), and attach the children
before the other thread calls store_lock(), then we would return
more locks in store_lock() than we claimed by lock_count(). The
other tread would overrun its memory.
*/
return file->tables;
return 0;
}
......@@ -1037,37 +1246,6 @@ THR_LOCK_DATA **ha_myisammrg::store_lock(THD *thd,
THR_LOCK_DATA **to,
enum thr_lock_type lock_type)
{
MYRG_TABLE *open_table;
/*
This method can be called while another thread is attaching the
children. If the processor reorders instructions or write to memory,
'children_attached' could be set before 'open_tables' has all the
pointers to the children. Use of a mutex here and in
myrg_attach_children() forces consistent data.
*/
pthread_mutex_lock(&this->file->mutex);
/*
When MERGE table is open, but not yet attached, other threads
could flush it, which means call mysql_lock_abort_for_thread()
on this threads TABLE. 'children_attached' is FALSE in this
situaton. Since the table is not locked, return no lock data.
*/
if (!this->file->children_attached)
goto end; /* purecov: tested */
for (open_table=file->open_tables ;
open_table != file->end_table ;
open_table++)
{
*(to++)= &open_table->table->lock;
if (lock_type != TL_IGNORE && open_table->table->lock.type == TL_UNLOCK)
open_table->table->lock.type=lock_type;
}
end:
pthread_mutex_unlock(&this->file->mutex);
return to;
}
......@@ -1156,7 +1334,7 @@ int ha_myisammrg::create(const char *name, register TABLE *form,
/* Allocate a table_names array in thread mem_root. */
if (!(table_names= (const char**)
thd->alloc((create_info->merge_list.elements+1) * sizeof(char*))))
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
DBUG_RETURN(HA_ERR_OUT_OF_MEM); /* purecov: inspected */
/* Create child path names. */
for (pos= table_names; tables; tables= tables->next_local)
......@@ -1266,7 +1444,7 @@ bool ha_myisammrg::check_if_incompatible_data(HA_CREATE_INFO *info,
int ha_myisammrg::check(THD* thd, HA_CHECK_OPT* check_opt)
{
return HA_ADMIN_OK;
return this->file->children_attached ? HA_ADMIN_OK : HA_ADMIN_CORRUPT;
}
......
......@@ -28,6 +28,9 @@ class ha_myisammrg: public handler
my_bool is_cloned; /* This instance has been cloned */
public:
MEM_ROOT children_mem_root; /* mem root for children list */
TABLE_LIST *children_l; /* children list */
TABLE_LIST **children_last_l; /* children list end */
TABLE_LIST *next_child_attach; /* next child to attach */
uint test_if_locked; /* flags from ::open() */
bool need_compat_check; /* if need compatibility check */
......@@ -60,6 +63,7 @@ class ha_myisammrg: public handler
{ return ulonglong2double(stats.data_file_length) / IO_SIZE + file->tables; }
int open(const char *name, int mode, uint test_if_locked);
int add_children_list(void);
int attach_children(void);
int detach_children(void);
virtual handler *clone(MEM_ROOT *mem_root);
......
......@@ -75,12 +75,17 @@ int myrg_reset(MYRG_INFO *info)
MYRG_TABLE *file;
DBUG_ENTER("myrg_reset");
if (!info->children_attached)
DBUG_RETURN(1);
info->cache_in_use=0;
info->current_table=0;
info->last_used_table= info->open_tables;
/*
This is normally called with detached children.
Return OK as this is the normal case.
*/
if (!info->children_attached)
DBUG_RETURN(0);
for (file=info->open_tables ; file != info->end_table ; file++)
{
int error;
......
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