summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.bzrignore1
-rw-r--r--libmysqld/CMakeLists.txt1
-rw-r--r--libmysqld/Makefile.am2
-rw-r--r--mysql-test/include/handler.inc33
-rw-r--r--mysql-test/r/create.result7
-rw-r--r--mysql-test/r/flush.result5
-rw-r--r--mysql-test/r/flush_table.result17
-rw-r--r--mysql-test/r/handler_innodb.result2
-rw-r--r--mysql-test/r/handler_myisam.result2
-rw-r--r--mysql-test/r/information_schema.result77
-rw-r--r--mysql-test/r/kill.result103
-rw-r--r--mysql-test/r/lock.result3
-rw-r--r--mysql-test/r/partition_column_prune.result6
-rw-r--r--mysql-test/r/partition_pruning.result2
-rw-r--r--mysql-test/r/ps_ddl.result4
-rw-r--r--mysql-test/r/sp.result6
-rw-r--r--mysql-test/r/view.result4
-rw-r--r--mysql-test/r/view_grant.result4
-rw-r--r--mysql-test/r/view_multi.result48
-rw-r--r--mysql-test/suite/rpl/t/disabled.def3
-rw-r--r--mysql-test/t/create.test8
-rw-r--r--mysql-test/t/disabled.def1
-rw-r--r--mysql-test/t/flush.test14
-rw-r--r--mysql-test/t/flush_table.test25
-rw-r--r--mysql-test/t/information_schema.test93
-rw-r--r--mysql-test/t/kill.test226
-rw-r--r--mysql-test/t/lock.test3
-rw-r--r--mysql-test/t/lock_multi.test34
-rw-r--r--mysql-test/t/ps_ddl.test4
-rw-r--r--mysql-test/t/sp.test4
-rw-r--r--mysql-test/t/trigger_notembedded.test2
-rw-r--r--mysql-test/t/view.test4
-rw-r--r--mysql-test/t/view_grant.test4
-rw-r--r--mysql-test/t/view_multi.test110
-rwxr-xr-xsql/CMakeLists.txt2
-rw-r--r--sql/Makefile.am5
-rw-r--r--sql/event_db_repository.cc4
-rw-r--r--sql/ha_ndbcluster.cc23
-rw-r--r--sql/ha_ndbcluster_binlog.cc33
-rw-r--r--sql/handler.cc9
-rw-r--r--sql/lock.cc382
-rw-r--r--sql/log_event.cc22
-rw-r--r--sql/log_event_old.cc36
-rw-r--r--sql/mdl.cc1342
-rw-r--r--sql/mdl.h260
-rw-r--r--sql/mysql_priv.h53
-rw-r--r--sql/mysqld.cc5
-rw-r--r--sql/rpl_rli.cc21
-rw-r--r--sql/rpl_rli.h1
-rw-r--r--sql/set_var.cc2
-rw-r--r--sql/sp_head.cc7
-rw-r--r--sql/sql_acl.cc16
-rw-r--r--sql/sql_base.cc2465
-rw-r--r--sql/sql_binlog.cc2
-rw-r--r--sql/sql_class.cc20
-rw-r--r--sql/sql_class.h13
-rw-r--r--sql/sql_db.cc4
-rw-r--r--sql/sql_delete.cc27
-rw-r--r--sql/sql_handler.cc136
-rw-r--r--sql/sql_insert.cc13
-rw-r--r--sql/sql_parse.cc118
-rw-r--r--sql/sql_partition.cc10
-rw-r--r--sql/sql_plist.h125
-rw-r--r--sql/sql_plugin.cc4
-rw-r--r--sql/sql_prepare.cc2
-rw-r--r--sql/sql_rename.cc17
-rw-r--r--sql/sql_servers.cc11
-rw-r--r--sql/sql_show.cc52
-rw-r--r--sql/sql_table.cc508
-rw-r--r--sql/sql_test.cc40
-rw-r--r--sql/sql_trigger.cc46
-rw-r--r--sql/sql_udf.cc3
-rw-r--r--sql/sql_update.cc4
-rw-r--r--sql/sql_view.cc92
-rw-r--r--sql/table.cc20
-rw-r--r--sql/table.h80
-rw-r--r--storage/myisammrg/ha_myisammrg.cc6
77 files changed, 4786 insertions, 2117 deletions
diff --git a/.bzrignore b/.bzrignore
index 351417a4353..bc3ca7b0c24 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -3070,3 +3070,4 @@ libmysqld/rpl_handler.cc
libmysqld/debug_sync.cc
libmysqld/rpl_handler.cc
dbug/tests
+libmysqld/mdl.cc
diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt
index 65b8e12bc26..655082f0304 100644
--- a/libmysqld/CMakeLists.txt
+++ b/libmysqld/CMakeLists.txt
@@ -133,6 +133,7 @@ SET(LIBMYSQLD_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc
../sql/partition_info.cc ../sql/sql_connect.cc
../sql/scheduler.cc ../sql/event_parse_data.cc
../sql/sql_signal.cc ../sql/rpl_handler.cc
+ ../sql/mdl.cc
${GEN_SOURCES}
${LIB_SOURCES})
diff --git a/libmysqld/Makefile.am b/libmysqld/Makefile.am
index ec73741eaaf..3a7fa934778 100644
--- a/libmysqld/Makefile.am
+++ b/libmysqld/Makefile.am
@@ -79,7 +79,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \
sql_tablespace.cc \
rpl_injector.cc my_user.c partition_info.cc \
sql_servers.cc event_parse_data.cc sql_signal.cc \
- rpl_handler.cc
+ rpl_handler.cc mdl.cc
libmysqld_int_a_SOURCES= $(libmysqld_sources)
nodist_libmysqld_int_a_SOURCES= $(libmysqlsources) $(sqlsources)
diff --git a/mysql-test/include/handler.inc b/mysql-test/include/handler.inc
index 6e7f53ba9b2..8ff38c7e7a1 100644
--- a/mysql-test/include/handler.inc
+++ b/mysql-test/include/handler.inc
@@ -518,12 +518,15 @@ connect (flush,localhost,root,,);
connection flush;
--echo connection: flush
--send flush tables;
-connection default;
---echo connection: default
+connect (waiter,localhost,root,,);
+connection waiter;
+--echo connection: waiter
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Flushing tables";
--source include/wait_condition.inc
+connection default;
+--echo connection: default
handler t2 open;
handler t2 read first;
handler t1 read next;
@@ -550,12 +553,14 @@ connect (flush,localhost,root,,);
connection flush;
--echo connection: flush
--send rename table t1 to t2;
-connection default;
---echo connection: default
+connection waiter;
+--echo connection: waiter
let $wait_condition=
select count(*) = 1 from information_schema.processlist
where state = "Waiting for table" and info = "rename table t1 to t2";
--source include/wait_condition.inc
+connection default;
+--echo connection: default
handler t2 open;
handler t2 read first;
--error ER_NO_SUCH_TABLE
@@ -566,7 +571,13 @@ connection flush;
reap;
connection default;
drop table t2;
+connection flush;
disconnect flush;
+--source include/wait_until_disconnected.inc
+connection waiter;
+disconnect waiter;
+--source include/wait_until_disconnected.inc
+connection default;
#
# Bug#30882 Dropping a temporary table inside a stored function may cause a server crash
@@ -699,19 +710,24 @@ handler t1 read a next;
# Bug#41112: crash in mysql_ha_close_table/get_lock_data with alter table
#
+connect(con1,localhost,root,,);
+connect(con2,localhost,root,,);
+
+connection default;
--disable_warnings
drop table if exists t1;
--enable_warnings
create table t1 (a int);
insert into t1 values (1);
handler t1 open;
-connect(con1,localhost,root,,);
+connection con1;
send alter table t1 engine=memory;
-connection default;
+connection con2;
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "rename result table" and info = "alter table t1 engine=memory";
+ where state = "Waiting for table" and info = "alter table t1 engine=memory";
--source include/wait_condition.inc
+connection default;
--error ER_ILLEGAL_HA
handler t1 read a next;
handler t1 close;
@@ -720,6 +736,9 @@ connection con1;
drop table t1;
disconnect con1;
--source include/wait_until_disconnected.inc
+connection con2;
+disconnect con2;
+--source include/wait_until_disconnected.inc
connection default;
#
diff --git a/mysql-test/r/create.result b/mysql-test/r/create.result
index 471cc6e9a3d..cf424b8b058 100644
--- a/mysql-test/r/create.result
+++ b/mysql-test/r/create.result
@@ -785,7 +785,7 @@ drop table t1;
create table t1 select * from t2;
ERROR 42S02: Table 'test.t2' doesn't exist
create table t1 select * from t1;
-ERROR HY000: You can't specify target table 't1' for update in FROM clause
+ERROR 42S02: Table 'test.t1' doesn't exist
create table t1 select coalesce('a' collate latin1_swedish_ci,'b' collate latin1_bin);
ERROR HY000: Illegal mix of collations (latin1_swedish_ci,EXPLICIT) and (latin1_bin,EXPLICIT) for operation 'coalesce'
create table t1 (primary key(a)) select "b" as b;
@@ -805,6 +805,11 @@ Note 1050 Table 't1' already exists
select * from t1;
i
1
+create table if not exists t1 select * from t1;
+ERROR HY000: You can't specify target table 't1' for update in FROM clause
+select * from t1;
+i
+1
create table t1 select coalesce('a' collate latin1_swedish_ci,'b' collate latin1_bin);
ERROR HY000: Illegal mix of collations (latin1_swedish_ci,EXPLICIT) and (latin1_bin,EXPLICIT) for operation 'coalesce'
select * from t1;
diff --git a/mysql-test/r/flush.result b/mysql-test/r/flush.result
index b978304f59d..2be426d3a4a 100644
--- a/mysql-test/r/flush.result
+++ b/mysql-test/r/flush.result
@@ -33,6 +33,9 @@ flush tables with read lock;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
lock table t1 read;
flush tables with read lock;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
+unlock tables;
+flush tables with read lock;
lock table t1 write;
ERROR HY000: Can't execute the query because you have a conflicting read lock
lock table t1 read;
@@ -46,6 +49,7 @@ flush tables with read lock;
ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
lock table t1 read, t2 read, t3 read;
flush tables with read lock;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
unlock tables;
drop table t1, t2, t3;
create table t1 (c1 int);
@@ -69,6 +73,7 @@ ERROR HY000: Can't execute the given command because you have active locked tabl
unlock tables;
lock tables t1 read;
flush tables with read lock;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
unlock tables;
drop table t1, t2;
set session low_priority_updates=default;
diff --git a/mysql-test/r/flush_table.result b/mysql-test/r/flush_table.result
index 8821bade6b4..2b0ee1cb205 100644
--- a/mysql-test/r/flush_table.result
+++ b/mysql-test/r/flush_table.result
@@ -3,21 +3,14 @@ create table t1 (a int not null auto_increment primary key);
insert into t1 values(0);
lock table t1 read;
flush table t1;
+ERROR HY000: Table 't1' was locked with a READ lock and can't be updated
+unlock tables;
+lock table t1 write;
+flush table t1;
check table t1;
Table Op Msg_type Msg_text
test.t1 check status OK
unlock tables;
-lock table t1 read;
-lock table t1 read;
-flush table t1;
-select * from t1;
-a
-1
-unlock tables;
-select * from t1;
-a
-1
-unlock tables;
lock table t1 write;
lock table t1 read;
flush table t1;
@@ -26,7 +19,7 @@ a
1
unlock tables;
unlock tables;
-lock table t1 read;
+lock table t1 write;
lock table t1 write;
flush table t1;
select * from t1;
diff --git a/mysql-test/r/handler_innodb.result b/mysql-test/r/handler_innodb.result
index 957fc30acef..5990b19062b 100644
--- a/mysql-test/r/handler_innodb.result
+++ b/mysql-test/r/handler_innodb.result
@@ -548,6 +548,7 @@ c1
1
connection: flush
flush tables;;
+connection: waiter
connection: default
handler t2 open;
handler t2 read first;
@@ -567,6 +568,7 @@ handler t1 read first;
c1
connection: flush
rename table t1 to t2;;
+connection: waiter
connection: default
handler t2 open;
handler t2 read first;
diff --git a/mysql-test/r/handler_myisam.result b/mysql-test/r/handler_myisam.result
index 90a1bdfe6be..f7b0ff2e04e 100644
--- a/mysql-test/r/handler_myisam.result
+++ b/mysql-test/r/handler_myisam.result
@@ -547,6 +547,7 @@ c1
1
connection: flush
flush tables;;
+connection: waiter
connection: default
handler t2 open;
handler t2 read first;
@@ -566,6 +567,7 @@ handler t1 read first;
c1
connection: flush
rename table t1 to t2;;
+connection: waiter
connection: default
handler t2 open;
handler t2 read first;
diff --git a/mysql-test/r/information_schema.result b/mysql-test/r/information_schema.result
index 04234eb3cc4..a66e494dcd2 100644
--- a/mysql-test/r/information_schema.result
+++ b/mysql-test/r/information_schema.result
@@ -1644,6 +1644,57 @@ TEST_RESULT
OK
SET TIMESTAMP=DEFAULT;
End of 5.1 tests.
+#
+# Additional test for WL#3726 "DDL locking for all metadata objects"
+# To avoid possible deadlocks process of filling of I_S tables should
+# use high-priority metadata lock requests when opening tables.
+# Below we just test that we really use high-priority lock request
+# since reproducing a deadlock will require much more complex test.
+#
+drop tables if exists t1, t2, t3;
+create table t1 (i int);
+create table t2 (j int primary key auto_increment);
+# Switching to connection 'con3726_1'
+lock table t2 read;
+# Switching to connection 'con3726_2'
+# RENAME below will be blocked by 'lock table t2 read' above but
+# will add two pending requests for exclusive metadata locks.
+rename table t2 to t3;
+# Switching to connection 'default'
+# These statements should not be blocked by pending lock requests
+select table_name, column_name, data_type from information_schema.columns
+where table_schema = 'test' and table_name in ('t1', 't2');
+table_name column_name data_type
+t1 i int
+t2 j int
+select table_name, auto_increment from information_schema.tables
+where table_schema = 'test' and table_name in ('t1', 't2');
+table_name auto_increment
+t1 NULL
+t2 1
+# Switching to connection 'con3726_1'
+unlock tables;
+# Switching to connection 'con3726_2'
+# Switching to connection 'default'
+drop tables t1, t3;
+EXPLAIN SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE KEY_COLUMN_USAGE ALL NULL NULL NULL NULL NULL Open_full_table; Scanned all databases
+EXPLAIN SELECT * FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME='t1';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE PARTITIONS ALL NULL TABLE_NAME NULL NULL NULL Using where; Open_full_table; Scanned 1 database
+EXPLAIN SELECT * FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
+WHERE CONSTRAINT_SCHEMA='test';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE REFERENTIAL_CONSTRAINTS ALL NULL CONSTRAINT_SCHEMA NULL NULL NULL Using where; Open_full_table; Scanned 1 database
+EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
+WHERE TABLE_NAME='t1' and TABLE_SCHEMA='test';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE TABLE_CONSTRAINTS ALL NULL TABLE_SCHEMA,TABLE_NAME NULL NULL NULL Using where; Open_full_table; Scanned 0 databases
+EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TRIGGERS
+WHERE EVENT_OBJECT_SCHEMA='test';
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE TRIGGERS ALL NULL EVENT_OBJECT_SCHEMA NULL NULL NULL Using where; Open_full_table; Scanned 1 database
create table information_schema.t1 (f1 INT);
ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_schema'
drop table information_schema.t1;
@@ -1682,28 +1733,10 @@ ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_
LOCK TABLES t1 READ, information_schema.tables READ;
ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_schema'
DROP TABLE t1;
-EXPLAIN SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE;
-id select_type table type possible_keys key key_len ref rows Extra
-1 SIMPLE KEY_COLUMN_USAGE ALL NULL NULL NULL NULL NULL Open_full_table; Scanned all databases
-EXPLAIN SELECT * FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME='t1';
-id select_type table type possible_keys key key_len ref rows Extra
-1 SIMPLE PARTITIONS ALL NULL TABLE_NAME NULL NULL NULL Using where; Open_full_table; Scanned 1 database
-EXPLAIN SELECT * FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
-WHERE CONSTRAINT_SCHEMA='test';
-id select_type table type possible_keys key key_len ref rows Extra
-1 SIMPLE REFERENTIAL_CONSTRAINTS ALL NULL CONSTRAINT_SCHEMA NULL NULL NULL Using where; Open_full_table; Scanned 1 database
-EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
-WHERE TABLE_NAME='t1' and TABLE_SCHEMA='test';
-id select_type table type possible_keys key key_len ref rows Extra
-1 SIMPLE TABLE_CONSTRAINTS ALL NULL TABLE_SCHEMA,TABLE_NAME NULL NULL NULL Using where; Open_full_table; Scanned 0 databases
-EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TRIGGERS
-WHERE EVENT_OBJECT_SCHEMA='test';
-id select_type table type possible_keys key key_len ref rows Extra
-1 SIMPLE TRIGGERS ALL NULL EVENT_OBJECT_SCHEMA NULL NULL NULL Using where; Open_full_table; Scanned 1 database
SELECT *
-FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
-LEFT JOIN INFORMATION_SCHEMA.COLUMNS
-USING (TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME)
-WHERE COLUMNS.TABLE_SCHEMA = 'test'
+FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
+LEFT JOIN INFORMATION_SCHEMA.COLUMNS
+USING (TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME)
+WHERE COLUMNS.TABLE_SCHEMA = 'test'
AND COLUMNS.TABLE_NAME = 't1';
TABLE_SCHEMA TABLE_NAME COLUMN_NAME CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME TABLE_CATALOG ORDINAL_POSITION POSITION_IN_UNIQUE_CONSTRAINT REFERENCED_TABLE_SCHEMA REFERENCED_TABLE_NAME REFERENCED_COLUMN_NAME TABLE_CATALOG ORDINAL_POSITION COLUMN_DEFAULT IS_NULLABLE DATA_TYPE CHARACTER_MAXIMUM_LENGTH CHARACTER_OCTET_LENGTH NUMERIC_PRECISION NUMERIC_SCALE CHARACTER_SET_NAME COLLATION_NAME COLUMN_TYPE COLUMN_KEY EXTRA PRIVILEGES COLUMN_COMMENT
diff --git a/mysql-test/r/kill.result b/mysql-test/r/kill.result
index 8b6830d4798..1f4f4bb32eb 100644
--- a/mysql-test/r/kill.result
+++ b/mysql-test/r/kill.result
@@ -138,4 +138,107 @@ KILL CONNECTION_ID();
# of close of the connection socket
SELECT 1;
Got one of the listed errors
+#
+# Additional test for WL#3726 "DDL locking for all metadata objects"
+# Check that DDL and DML statements waiting for metadata locks can
+# be killed. Note that we don't cover all situations here since it
+# can be tricky to write test case for some of them (e.g. REPAIR or
+# ALTER and other statements under LOCK TABLES).
+#
+drop tables if exists t1, t2, t3;
+create table t1 (i int primary key);
+# Test for RENAME TABLE
+# Switching to connection 'blocker'
+lock table t1 read;
+# Switching to connection 'ddl'
+rename table t1 to t2;
+# Switching to connection 'default'
+kill query ID;
+# Switching to connection 'ddl'
+ERROR 70100: Query execution was interrupted
+# Test for DROP TABLE
+drop table t1;
+# Switching to connection 'default'
+kill query ID;
+# Switching to connection 'ddl'
+ERROR 70100: Query execution was interrupted
+# Test for CREATE TRIGGER
+create trigger t1_bi before insert on t1 for each row set @a:=1;
+# Switching to connection 'default'
+kill query ID;
+# Switching to connection 'ddl'
+ERROR 70100: Query execution was interrupted
+#
+# Tests for various kinds of ALTER TABLE
+#
+# Full-blown ALTER which should copy table
+alter table t1 add column j int;
+# Switching to connection 'default'
+kill query ID;
+# Switching to connection 'ddl'
+ERROR 70100: Query execution was interrupted
+# Two kinds of simple ALTER
+alter table t1 rename to t2;
+# Switching to connection 'default'
+kill query ID;
+# Switching to connection 'ddl'
+ERROR 70100: Query execution was interrupted
+alter table t1 disable keys;
+# Switching to connection 'default'
+kill query ID;
+# Switching to connection 'ddl'
+ERROR 70100: Query execution was interrupted
+# Fast ALTER
+alter table t1 alter column i set default 100;
+# Switching to connection 'default'
+kill query ID;
+# Switching to connection 'ddl'
+ERROR 70100: Query execution was interrupted
+# Special case which is triggered only for MERGE tables.
+# Switching to connection 'blocker'
+unlock tables;
+create table t2 (i int primary key) engine=merge union=(t1);
+lock tables t2 read;
+# Switching to connection 'ddl'
+alter table t2 alter column i set default 100;
+# Switching to connection 'default'
+kill query ID;
+# Switching to connection 'ddl'
+ERROR 70100: Query execution was interrupted
+# Test for DML waiting for meta-data lock
+# Switching to connection 'blocker'
+unlock tables;
+drop table t2;
+create table t2 (k int);
+lock tables t1 read;
+# Switching to connection 'ddl'
+rename tables t1 to t3, t2 to t1;
+# Switching to connection 'dml'
+insert into t2 values (1);
+# Switching to connection 'default'
+kill query ID2;
+# Switching to connection 'dml'
+ERROR 70100: Query execution was interrupted
+# Switching to connection 'blocker'
+unlock tables;
+# Switching to connection 'ddl'
+# Test for DML waiting for tables to be flushed
+# Switching to connection 'blocker'
+lock tables t1 read;
+# Switching to connection 'ddl'
+# Let us mark locked table t1 as old
+flush tables;
+# Switching to connection 'dml'
+select * from t1;
+# Switching to connection 'default'
+kill query ID2;
+# Switching to connection 'dml'
+ERROR 70100: Query execution was interrupted
+# Switching to connection 'blocker'
+unlock tables;
+# Switching to connection 'ddl'
+# Cleanup.
+# Switching to connection 'default'
+drop table t3;
+drop table t1;
set @@global.concurrent_insert= @old_concurrent_insert;
diff --git a/mysql-test/r/lock.result b/mysql-test/r/lock.result
index 1f8f6aa04ae..a60c5c8383f 100644
--- a/mysql-test/r/lock.result
+++ b/mysql-test/r/lock.result
@@ -128,13 +128,14 @@ select * from v_bug5719;
1
1
drop view v_bug5719;
+ERROR HY000: Can't execute the given command because you have active locked tables or an active transaction
sic: did not left LOCK TABLES mode automatically
select * from t1;
ERROR HY000: Table 't1' was not locked with LOCK TABLES
unlock tables;
-create view v_bug5719 as select * from t1;
+create or replace view v_bug5719 as select * from t1;
lock tables v_bug5719 write;
select * from v_bug5719;
a
diff --git a/mysql-test/r/partition_column_prune.result b/mysql-test/r/partition_column_prune.result
index 82c49453d43..844429d24a6 100644
--- a/mysql-test/r/partition_column_prune.result
+++ b/mysql-test/r/partition_column_prune.result
@@ -56,11 +56,11 @@ insert into t1 values (2,5), (2,15), (2,25),
insert into t1 select * from t1;
explain partitions select * from t1 where a=2;
id select_type table partitions type possible_keys key key_len ref rows Extra
-1 SIMPLE t1 p01,p02,p03,p11 ALL NULL NULL NULL NULL 13 Using where
+1 SIMPLE t1 p01,p02,p03,p11 ALL NULL NULL NULL NULL 8 Using where
explain partitions select * from t1 where a=4;
id select_type table partitions type possible_keys key key_len ref rows Extra
-1 SIMPLE t1 p11,p12,p13,p21 ALL NULL NULL NULL NULL 16 Using where
+1 SIMPLE t1 p11,p12,p13,p21 ALL NULL NULL NULL NULL 14 Using where
explain partitions select * from t1 where a=2 and b < 22;
id select_type table partitions type possible_keys key key_len ref rows Extra
-1 SIMPLE t1 p01,p02,p03 ALL NULL NULL NULL NULL 16 Using where
+1 SIMPLE t1 p01,p02,p03 ALL NULL NULL NULL NULL 14 Using where
drop table t1;
diff --git a/mysql-test/r/partition_pruning.result b/mysql-test/r/partition_pruning.result
index d7790cd8075..4c22c25ddb9 100644
--- a/mysql-test/r/partition_pruning.result
+++ b/mysql-test/r/partition_pruning.result
@@ -1960,7 +1960,7 @@ id select_type table partitions type possible_keys key key_len ref rows Extra
explain partitions
select * from t1 X, t1 Y where X.a = Y.a and (X.a=1 or X.a=2);
id select_type table partitions type possible_keys key key_len ref rows Extra
-1 SIMPLE X p1,p2 ALL a NULL NULL NULL 4 Using where
+1 SIMPLE X p1,p2 ALL a NULL NULL NULL 2 Using where
1 SIMPLE Y p1,p2 ref a a 4 test.X.a 2
drop table t1;
create table t1 (a int) partition by hash(a) partitions 20;
diff --git a/mysql-test/r/ps_ddl.result b/mysql-test/r/ps_ddl.result
index c7e8812320c..af0ffaf4ae5 100644
--- a/mysql-test/r/ps_ddl.result
+++ b/mysql-test/r/ps_ddl.result
@@ -850,7 +850,7 @@ flush table t1;
execute stmt;
f1()
6
-call p_verify_reprepare_count(0);
+call p_verify_reprepare_count(1);
SUCCESS
execute stmt;
@@ -1706,7 +1706,7 @@ SUCCESS
drop temporary table t2;
execute stmt;
-call p_verify_reprepare_count(1);
+call p_verify_reprepare_count(0);
SUCCESS
drop table t2;
diff --git a/mysql-test/r/sp.result b/mysql-test/r/sp.result
index a73abf787d8..47d441ee182 100644
--- a/mysql-test/r/sp.result
+++ b/mysql-test/r/sp.result
@@ -1083,11 +1083,9 @@ select f0()|
f0()
100
select * from v0|
-f0()
-100
+ERROR HY000: Table 'v0' was not locked with LOCK TABLES
select *, f0() from v0, (select 123) as d1|
-f0() 123 f0()
-100 123 100
+ERROR HY000: Table 'v0' was not locked with LOCK TABLES
select id, f3() from t1|
ERROR HY000: Table 't1' was not locked with LOCK TABLES
select f4()|
diff --git a/mysql-test/r/view.result b/mysql-test/r/view.result
index 2df2b0bafa6..5f16d88a0dc 100644
--- a/mysql-test/r/view.result
+++ b/mysql-test/r/view.result
@@ -1102,10 +1102,8 @@ select * from v1;
a
select * from t2;
ERROR HY000: Table 't2' was not locked with LOCK TABLES
-drop view v1;
-drop table t1, t2;
-ERROR HY000: Table 't1' was locked with a READ lock and can't be updated
unlock tables;
+drop view v1;
drop table t1, t2;
create table t1 (a int);
create view v1 as select * from t1 where a < 2 with check option;
diff --git a/mysql-test/r/view_grant.result b/mysql-test/r/view_grant.result
index 65fbf2d87b6..0c74d8ed91b 100644
--- a/mysql-test/r/view_grant.result
+++ b/mysql-test/r/view_grant.result
@@ -779,9 +779,9 @@ GRANT CREATE VIEW ON db26813.v2 TO u26813@localhost;
GRANT DROP, CREATE VIEW ON db26813.v3 TO u26813@localhost;
GRANT SELECT ON db26813.t1 TO u26813@localhost;
ALTER VIEW v1 AS SELECT f2 FROM t1;
-ERROR 42000: Access denied; you need (at least one of) the SUPER privilege(s) for this operation
+ERROR 42000: CREATE VIEW command denied to user 'u26813'@'localhost' for table 'v1'
ALTER VIEW v2 AS SELECT f2 FROM t1;
-ERROR 42000: Access denied; you need (at least one of) the SUPER privilege(s) for this operation
+ERROR 42000: DROP command denied to user 'u26813'@'localhost' for table 'v2'
ALTER VIEW v3 AS SELECT f2 FROM t1;
ERROR 42000: Access denied; you need (at least one of) the SUPER privilege(s) for this operation
SHOW CREATE VIEW v3;
diff --git a/mysql-test/r/view_multi.result b/mysql-test/r/view_multi.result
new file mode 100644
index 00000000000..95a8d572be4
--- /dev/null
+++ b/mysql-test/r/view_multi.result
@@ -0,0 +1,48 @@
+reset master;
+create table t1 (i int);
+create table t2 (i int);
+create view v1 as select * from t1;
+select get_lock("lock_bg25144", 1);
+get_lock("lock_bg25144", 1)
+1
+insert into v1 values (get_lock("lock_bg25144", 100));;
+drop view v1;;
+select release_lock("lock_bg25144");
+release_lock("lock_bg25144")
+1
+select release_lock("lock_bg25144");
+release_lock("lock_bg25144")
+1
+select * from t1;
+i
+1
+create view v1 as select * from t1;
+select get_lock("lock_bg25144", 1);
+get_lock("lock_bg25144", 1)
+1
+insert into v1 values (get_lock("lock_bg25144", 100));;
+alter view v1 as select * from t2;;
+select release_lock("lock_bg25144");
+release_lock("lock_bg25144")
+1
+select release_lock("lock_bg25144");
+release_lock("lock_bg25144")
+1
+select * from t1;
+i
+1
+1
+select * from t2;
+i
+show binlog events in 'master-bin.000001' from 107;
+Log_name Pos Event_type Server_id End_log_pos Info
+master-bin.000001 # Query 1 # use `test`; create table t1 (i int)
+master-bin.000001 # Query 1 # use `test`; create table t2 (i int)
+master-bin.000001 # Query 1 # use `test`; CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select * from t1
+master-bin.000001 # Query 1 # use `test`; insert into v1 values (get_lock("lock_bg25144", 100))
+master-bin.000001 # Query 1 # use `test`; drop view v1
+master-bin.000001 # Query 1 # use `test`; CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select * from t1
+master-bin.000001 # Query 1 # use `test`; insert into v1 values (get_lock("lock_bg25144", 100))
+master-bin.000001 # Query 1 # use `test`; ALTER ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select * from t2
+drop table t1, t2;
+drop view v1;
diff --git a/mysql-test/suite/rpl/t/disabled.def b/mysql-test/suite/rpl/t/disabled.def
index bed019f9c79..da962a7fb9d 100644
--- a/mysql-test/suite/rpl/t/disabled.def
+++ b/mysql-test/suite/rpl/t/disabled.def
@@ -13,3 +13,6 @@
rpl_get_master_version_and_clock: # Bug#46931 2009-10-17 joro rpl.rpl_get_master_version_and_clock fails
rpl_cross_version : BUG#43913 2009-10-22 luis rpl_cross_version fails with symptom in described in bug report
rpl_spec_variables : BUG#47661 2009-10-27 jasonh rpl_spec_variables fails on PB2 hpux
+rpl_empty_master_crash : BUG#48048
+rpl_load_from_master : BUG#48048
+rpl_load_table_from_master : BUG#48048
diff --git a/mysql-test/t/create.test b/mysql-test/t/create.test
index 9f3c3a88151..c07014bfc19 100644
--- a/mysql-test/t/create.test
+++ b/mysql-test/t/create.test
@@ -683,8 +683,8 @@ drop table t1;
# Error during open_and_lock_tables() of tables
--error ER_NO_SUCH_TABLE
create table t1 select * from t2;
-# Rather special error which also caught during open tables pahse
---error ER_UPDATE_TABLE_USED
+# A special case which is also caught during open tables pahse
+--error ER_NO_SUCH_TABLE
create table t1 select * from t1;
# Error which happens before select_create::prepare()
--error ER_CANT_AGGREGATE_2COLLATIONS
@@ -706,6 +706,10 @@ create table t1 (i int);
create table t1 select 1 as i;
create table if not exists t1 select 1 as i;
select * from t1;
+# Error which is detected after successfull table open.
+--error ER_UPDATE_TABLE_USED
+create table if not exists t1 select * from t1;
+select * from t1;
# Error before select_create::prepare()
--error ER_CANT_AGGREGATE_2COLLATIONS
create table t1 select coalesce('a' collate latin1_swedish_ci,'b' collate latin1_bin);
diff --git a/mysql-test/t/disabled.def b/mysql-test/t/disabled.def
index ad7617b9403..bb2749e902b 100644
--- a/mysql-test/t/disabled.def
+++ b/mysql-test/t/disabled.def
@@ -15,3 +15,4 @@ 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
diff --git a/mysql-test/t/flush.test b/mysql-test/t/flush.test
index f27d4cf2fad..4172230a54d 100644
--- a/mysql-test/t/flush.test
+++ b/mysql-test/t/flush.test
@@ -68,10 +68,13 @@ drop table t1;
create table t1 (c1 int);
lock table t1 write;
# Cannot get the global read lock with write locked tables.
---error 1192
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
flush tables with read lock;
lock table t1 read;
-# Can get the global read lock with read locked tables.
+# Cannot get the global read lock with read locked tables.
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
+flush tables with read lock;
+unlock tables;
flush tables with read lock;
--error 1223
lock table t1 write;
@@ -84,12 +87,12 @@ create table t2 (c1 int);
create table t3 (c1 int);
lock table t1 read, t2 read, t3 write;
# Cannot get the global read lock with write locked tables.
---error 1192
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
flush tables with read lock;
lock table t1 read, t2 read, t3 read;
-# Can get the global read lock with read locked tables.
+# Cannot get the global read lock with read locked tables.
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
flush tables with read lock;
-# Release all table locks and the global read lock.
unlock tables;
drop table t1, t2, t3;
@@ -157,6 +160,7 @@ flush tables with read lock;
unlock tables;
lock tables t1 read;
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
flush tables with read lock;
unlock tables;
diff --git a/mysql-test/t/flush_table.test b/mysql-test/t/flush_table.test
index 50e7e91419a..e4fc1b0c39f 100644
--- a/mysql-test/t/flush_table.test
+++ b/mysql-test/t/flush_table.test
@@ -15,30 +15,21 @@ insert into t1 values(0);
# Test for with read lock + flush
lock table t1 read;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
flush table t1;
-check table t1;
unlock tables;
-# Test for with 2 read lock in different thread + flush
+# Test for with write lock + flush
-lock table t1 read;
-connect (locker,localhost,root,,test);
-connection locker;
-lock table t1 read;
-connection default;
-send flush table t1;
-connection locker;
---sleep 2
-select * from t1;
-unlock tables;
-connection default;
-reap;
-select * from t1;
+lock table t1 write;
+flush table t1;
+check table t1;
unlock tables;
# Test for with a write lock and a waiting read lock + flush
lock table t1 write;
+connect (locker,localhost,root,,test);
connection locker;
send lock table t1 read;
connection default;
@@ -51,9 +42,9 @@ reap;
unlock tables;
connection default;
-# Test for with a read lock and a waiting write lock + flush
+# Test for with a write lock and a waiting write lock + flush
-lock table t1 read;
+lock table t1 write;
connection locker;
send lock table t1 write;
connection default;
diff --git a/mysql-test/t/information_schema.test b/mysql-test/t/information_schema.test
index 9da7cc1042d..c6cb28a4a30 100644
--- a/mysql-test/t/information_schema.test
+++ b/mysql-test/t/information_schema.test
@@ -544,6 +544,7 @@ AND table_name not like 'ndb%' AND table_name not like 'innodb_%'
GROUP BY TABLE_SCHEMA;
+
#
# TRIGGERS table test
#
@@ -914,8 +915,8 @@ DROP PROCEDURE p1;
DROP USER mysql_bug20230@localhost;
#
-# Bug#2123 query with a simple non-correlated subquery over
-# INFORMARTION_SCHEMA.TABLES
+# Bug#21231 query with a simple non-correlated subquery over
+# INFORMARTION_SCHEMA.TABLES
#
SELECT MAX(table_name) FROM information_schema.tables WHERE table_schema IN ('mysql', 'INFORMATION_SCHEMA', 'test');
@@ -1391,9 +1392,66 @@ SET TIMESTAMP=DEFAULT;
--echo End of 5.1 tests.
+--echo #
+--echo # Additional test for WL#3726 "DDL locking for all metadata objects"
+--echo # To avoid possible deadlocks process of filling of I_S tables should
+--echo # use high-priority metadata lock requests when opening tables.
+--echo # Below we just test that we really use high-priority lock request
+--echo # since reproducing a deadlock will require much more complex test.
+--echo #
+--disable_warnings
+drop tables if exists t1, t2, t3;
+--enable_warnings
+create table t1 (i int);
+create table t2 (j int primary key auto_increment);
+connect (con3726_1,localhost,root,,test);
+--echo # Switching to connection 'con3726_1'
+connection con3726_1;
+lock table t2 read;
+connect (con3726_2,localhost,root,,test);
+--echo # Switching to connection 'con3726_2'
+connection con3726_2;
+--echo # RENAME below will be blocked by 'lock table t2 read' above but
+--echo # will add two pending requests for exclusive metadata locks.
+--send rename table t2 to t3
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info like "rename table t2 to t3";
+--source include/wait_condition.inc
+--echo # These statements should not be blocked by pending lock requests
+select table_name, column_name, data_type from information_schema.columns
+ where table_schema = 'test' and table_name in ('t1', 't2');
+select table_name, auto_increment from information_schema.tables
+ where table_schema = 'test' and table_name in ('t1', 't2');
+--echo # Switching to connection 'con3726_1'
+connection con3726_1;
+unlock tables;
+--echo # Switching to connection 'con3726_2'
+connection con3726_2;
+--reap
+--echo # Switching to connection 'default'
+connection default;
+disconnect con3726_1;
+disconnect con3726_2;
+drop tables t1, t3;
+
+#
+# Bug#39270 I_S optimization algorithm does not work properly in some cases
+#
+EXPLAIN SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE;
+EXPLAIN SELECT * FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME='t1';
+EXPLAIN SELECT * FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
+ WHERE CONSTRAINT_SCHEMA='test';
+EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
+ WHERE TABLE_NAME='t1' and TABLE_SCHEMA='test';
+EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TRIGGERS
+ WHERE EVENT_OBJECT_SCHEMA='test';
+
#
# Bug#24062 Incorrect error msg after execute DROP TABLE IF EXISTS on information_schema
-#
+#
--error ER_DBACCESS_DENIED_ERROR
create table information_schema.t1 (f1 INT);
--error ER_DBACCESS_DENIED_ERROR
@@ -1429,27 +1487,18 @@ DROP TABLE t1, information_schema.tables;
LOCK TABLES t1 READ, information_schema.tables READ;
DROP TABLE t1;
-#
-# Bug#39270 I_S optimization algorithm does not work properly in some cases
-#
-EXPLAIN SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE;
-EXPLAIN SELECT * FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME='t1';
-EXPLAIN SELECT * FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
- WHERE CONSTRAINT_SCHEMA='test';
-EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
- WHERE TABLE_NAME='t1' and TABLE_SCHEMA='test';
-EXPLAIN SELECT * FROM INFORMATION_SCHEMA.TRIGGERS
- WHERE EVENT_OBJECT_SCHEMA='test';
+# Wait till all disconnects are completed
+--source include/wait_until_count_sessions.inc
#
-# Bug #43834 Assertion in Natural_join_column::db_name() on an I_S query
+# Bug #43834 Assertion in Natural_join_column::db_name() on an I_S query
#
+
SELECT *
-FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
-LEFT JOIN INFORMATION_SCHEMA.COLUMNS
-USING (TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME)
-WHERE COLUMNS.TABLE_SCHEMA = 'test'
-AND COLUMNS.TABLE_NAME = 't1';
+FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
+LEFT JOIN INFORMATION_SCHEMA.COLUMNS
+USING (TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME)
+WHERE COLUMNS.TABLE_SCHEMA = 'test'
+AND COLUMNS.TABLE_NAME = 't1';
+
-# Wait till all disconnects are completed
---source include/wait_until_count_sessions.inc
diff --git a/mysql-test/t/kill.test b/mysql-test/t/kill.test
index 02b033df2e5..98cb9f64d98 100644
--- a/mysql-test/t/kill.test
+++ b/mysql-test/t/kill.test
@@ -329,6 +329,232 @@ KILL CONNECTION_ID();
SELECT 1;
--connection default
+--echo #
+--echo # Additional test for WL#3726 "DDL locking for all metadata objects"
+--echo # Check that DDL and DML statements waiting for metadata locks can
+--echo # be killed. Note that we don't cover all situations here since it
+--echo # can be tricky to write test case for some of them (e.g. REPAIR or
+--echo # ALTER and other statements under LOCK TABLES).
+--echo #
+--disable_warnings
+drop tables if exists t1, t2, t3;
+--enable_warnings
+
+create table t1 (i int primary key);
+connect (blocker, localhost, root, , );
+connect (dml, localhost, root, , );
+connect (ddl, localhost, root, , );
+
+--echo # Test for RENAME TABLE
+--echo # Switching to connection 'blocker'
+connection blocker;
+lock table t1 read;
+--echo # Switching to connection 'ddl'
+connection ddl;
+let $ID= `select connection_id()`;
+--send rename table t1 to t2
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "rename table t1 to t2";
+--source include/wait_condition.inc
+--replace_result $ID ID
+eval kill query $ID;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+--echo # Test for DROP TABLE
+--send drop table t1
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "drop table t1";
+--replace_result $ID ID
+eval kill query $ID;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+--echo # Test for CREATE TRIGGER
+--send create trigger t1_bi before insert on t1 for each row set @a:=1
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "create trigger t1_bi before insert on t1 for each row set @a:=1";
+--replace_result $ID ID
+eval kill query $ID;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+--echo #
+--echo # Tests for various kinds of ALTER TABLE
+--echo #
+--echo # Full-blown ALTER which should copy table
+--send alter table t1 add column j int
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "alter table t1 add column j int";
+--replace_result $ID ID
+eval kill query $ID;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+--echo # Two kinds of simple ALTER
+--send alter table t1 rename to t2
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "alter table t1 rename to t2";
+--replace_result $ID ID
+eval kill query $ID;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+--send alter table t1 disable keys
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "alter table t1 disable keys";
+--replace_result $ID ID
+eval kill query $ID;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+--echo # Fast ALTER
+--send alter table t1 alter column i set default 100
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "alter table t1 alter column i set default 100";
+--replace_result $ID ID
+eval kill query $ID;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+--echo # Special case which is triggered only for MERGE tables.
+--echo # Switching to connection 'blocker'
+connection blocker;
+unlock tables;
+create table t2 (i int primary key) engine=merge union=(t1);
+lock tables t2 read;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--send alter table t2 alter column i set default 100
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "alter table t2 alter column i set default 100";
+--replace_result $ID ID
+eval kill query $ID;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--error ER_QUERY_INTERRUPTED
+--reap
+
+--echo # Test for DML waiting for meta-data lock
+--echo # Switching to connection 'blocker'
+connection blocker;
+unlock tables;
+drop table t2;
+create table t2 (k int);
+lock tables t1 read;
+--echo # Switching to connection 'ddl'
+connection ddl;
+# Let us add pending exclusive metadata lock on t2
+--send rename tables t1 to t3, t2 to t1
+--echo # Switching to connection 'dml'
+connection dml;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "rename tables t1 to t3, t2 to t1";
+let $ID2= `select connection_id()`;
+--send insert into t2 values (1)
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "insert into t2 values (1)";
+--replace_result $ID2 ID2
+eval kill query $ID2;
+--echo # Switching to connection 'dml'
+connection dml;
+--error ER_QUERY_INTERRUPTED
+--reap
+--echo # Switching to connection 'blocker'
+connection blocker;
+unlock tables;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--reap
+
+--echo # Test for DML waiting for tables to be flushed
+--echo # Switching to connection 'blocker'
+connection blocker;
+lock tables t1 read;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--echo # Let us mark locked table t1 as old
+--send flush tables
+--echo # Switching to connection 'dml'
+connection dml;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Flushing tables" and
+ info = "flush tables";
+--send select * from t1
+--echo # Switching to connection 'default'
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "select * from t1";
+--replace_result $ID2 ID2
+eval kill query $ID2;
+--echo # Switching to connection 'dml'
+connection dml;
+--error ER_QUERY_INTERRUPTED
+--reap
+--echo # Switching to connection 'blocker'
+connection blocker;
+unlock tables;
+--echo # Switching to connection 'ddl'
+connection ddl;
+--reap
+
+--echo # Cleanup.
+--echo # Switching to connection 'default'
+connection default;
+drop table t3;
+drop table t1;
+
###########################################################################
# Restore global concurrent_insert value. Keep in the end of the test file.
diff --git a/mysql-test/t/lock.test b/mysql-test/t/lock.test
index 04994e3e48f..856ae020492 100644
--- a/mysql-test/t/lock.test
+++ b/mysql-test/t/lock.test
@@ -178,6 +178,7 @@ select * from t2;
--error ER_TABLE_NOT_LOCKED
select * from t3;
select * from v_bug5719;
+--error ER_LOCK_OR_ACTIVE_TRANSACTION
drop view v_bug5719;
--echo
--echo sic: did not left LOCK TABLES mode automatically
@@ -185,7 +186,7 @@ drop view v_bug5719;
--error ER_TABLE_NOT_LOCKED
select * from t1;
unlock tables;
-create view v_bug5719 as select * from t1;
+create or replace view v_bug5719 as select * from t1;
lock tables v_bug5719 write;
select * from v_bug5719;
--echo
diff --git a/mysql-test/t/lock_multi.test b/mysql-test/t/lock_multi.test
index 75ee6d07723..58f46941920 100644
--- a/mysql-test/t/lock_multi.test
+++ b/mysql-test/t/lock_multi.test
@@ -164,7 +164,7 @@ connection locker;
# Sleep a bit till the select of connection reader is in work and hangs
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Waiting for table" and info =
+ where state = "Locked" and info =
"SELECT user.Select_priv FROM user, db WHERE user.user = db.user LIMIT 1";
--source include/wait_condition.inc
# Make test case independent from earlier grants.
@@ -196,7 +196,7 @@ connection writer;
# Sleep a bit till the flush of connection locker is in work and hangs
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables" and info = "FLUSH TABLES WITH READ LOCK";
+ where state = "Waiting for table" and info = "FLUSH TABLES WITH READ LOCK";
--source include/wait_condition.inc
# This must not block.
CREATE TABLE t2 (c1 int);
@@ -226,7 +226,7 @@ connection writer;
# Sleep a bit till the flush of connection locker is in work and hangs
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables" and info = "FLUSH TABLES WITH READ LOCK";
+ where state = "Waiting for table" and info = "FLUSH TABLES WITH READ LOCK";
--source include/wait_condition.inc
--error ER_TABLE_NOT_LOCKED
CREATE TABLE t2 AS SELECT * FROM t1;
@@ -337,10 +337,10 @@ connection con2;
send flush tables with read lock;
connection con5;
--echo # con5
-let $show_statement= SHOW PROCESSLIST;
-let $field= State;
-let $condition= = 'Flushing tables';
---source include/wait_show_condition.inc
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "flush tables with read lock";
+--source include/wait_condition.inc
--echo # global read lock is taken
connection con3;
--echo # con3
@@ -528,7 +528,7 @@ connection default;
--echo connection: default
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables";
+ where state = "Waiting for table";
--source include/wait_condition.inc
alter table t1 add column j int;
connect (insert,localhost,root,,test,,);
@@ -536,7 +536,7 @@ connection insert;
--echo connection: insert
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables";
+ where state = "Waiting for table";
--source include/wait_condition.inc
--send insert into t1 values (1,2);
--echo connection: default
@@ -586,18 +586,14 @@ connection default;
--echo connection: default
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables";
+ where state = "Waiting for table";
--source include/wait_condition.inc
flush tables;
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables";
+ where state = "Waiting for table";
--source include/wait_condition.inc
unlock tables;
-let $wait_condition=
- select count(*) = 0 from information_schema.processlist
- where state = "Flushing tables";
---source include/wait_condition.inc
connection flush;
--reap
connection default;
@@ -656,18 +652,14 @@ connection default;
--echo connection: default
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables";
+ where state = "Waiting for table";
--source include/wait_condition.inc
flush tables;
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables";
+ where state = "Waiting for table";
--source include/wait_condition.inc
drop table t1;
-let $wait_condition=
- select count(*) = 0 from information_schema.processlist
- where state = "Flushing tables";
---source include/wait_condition.inc
connection flush;
--reap
connection default;
diff --git a/mysql-test/t/ps_ddl.test b/mysql-test/t/ps_ddl.test
index fee235cd36c..6e771d44b3f 100644
--- a/mysql-test/t/ps_ddl.test
+++ b/mysql-test/t/ps_ddl.test
@@ -745,7 +745,7 @@ execute stmt;
call p_verify_reprepare_count(1);
flush table t1;
execute stmt;
-call p_verify_reprepare_count(0);
+call p_verify_reprepare_count(1);
execute stmt;
--echo # Test 18-c: dependent VIEW has changed
@@ -1453,7 +1453,7 @@ execute stmt;
call p_verify_reprepare_count(0);
drop temporary table t2;
execute stmt;
-call p_verify_reprepare_count(1);
+call p_verify_reprepare_count(0);
drop table t2;
execute stmt;
call p_verify_reprepare_count(0);
diff --git a/mysql-test/t/sp.test b/mysql-test/t/sp.test
index b0342491a34..b477b5a3e24 100644
--- a/mysql-test/t/sp.test
+++ b/mysql-test/t/sp.test
@@ -1308,9 +1308,11 @@ select f3()|
select id, f3() from t1 as t11 order by id|
# Degenerate cases work too :)
select f0()|
+# But these should not (particularly views should be locked explicitly).
+--error ER_TABLE_NOT_LOCKED
select * from v0|
+--error ER_TABLE_NOT_LOCKED
select *, f0() from v0, (select 123) as d1|
-# But these should not !
--error ER_TABLE_NOT_LOCKED
select id, f3() from t1|
--error ER_TABLE_NOT_LOCKED
diff --git a/mysql-test/t/trigger_notembedded.test b/mysql-test/t/trigger_notembedded.test
index 7a7e6c6bc85..8a570a7e87d 100644
--- a/mysql-test/t/trigger_notembedded.test
+++ b/mysql-test/t/trigger_notembedded.test
@@ -896,7 +896,7 @@ connection default;
--echo connection: default
let $wait_condition=
select count(*) = 1 from information_schema.processlist
- where state = "Flushing tables";
+ where state = "Waiting for table";
--source include/wait_condition.inc
create trigger t1_bi before insert on t1 for each row begin end;
unlock tables;
diff --git a/mysql-test/t/view.test b/mysql-test/t/view.test
index abf8dac2870..c3ff58880c9 100644
--- a/mysql-test/t/view.test
+++ b/mysql-test/t/view.test
@@ -1016,10 +1016,8 @@ lock tables t1 read, v1 read;
select * from v1;
-- error ER_TABLE_NOT_LOCKED
select * from t2;
-drop view v1;
---error ER_TABLE_NOT_LOCKED_FOR_WRITE
-drop table t1, t2;
unlock tables;
+drop view v1;
drop table t1, t2;
#
diff --git a/mysql-test/t/view_grant.test b/mysql-test/t/view_grant.test
index f01edb1e499..d94bcfc29ed 100644
--- a/mysql-test/t/view_grant.test
+++ b/mysql-test/t/view_grant.test
@@ -1047,9 +1047,9 @@ GRANT SELECT ON db26813.t1 TO u26813@localhost;
connect (u1,localhost,u26813,,db26813);
connection u1;
---error ER_SPECIFIC_ACCESS_DENIED_ERROR
+--error ER_TABLEACCESS_DENIED_ERROR
ALTER VIEW v1 AS SELECT f2 FROM t1;
---error ER_SPECIFIC_ACCESS_DENIED_ERROR
+--error ER_TABLEACCESS_DENIED_ERROR
ALTER VIEW v2 AS SELECT f2 FROM t1;
--error ER_SPECIFIC_ACCESS_DENIED_ERROR
ALTER VIEW v3 AS SELECT f2 FROM t1;
diff --git a/mysql-test/t/view_multi.test b/mysql-test/t/view_multi.test
new file mode 100644
index 00000000000..a61e7738095
--- /dev/null
+++ b/mysql-test/t/view_multi.test
@@ -0,0 +1,110 @@
+#
+# QQ: Should we find a better place for this test?
+# May be binlog or rpl suites ?
+#
+--source include/have_log_bin.inc
+--source include/have_binlog_format_mixed_or_statement.inc
+
+#
+# Bug #25144 "replication / binlog with view breaks".
+# Statements that used views didn't ensure that view were not modified
+# during their execution. Indeed this led to incorrect binary log with
+# statement based logging.
+#
+--disable_parsing
+drop table if not exists t1, t2;
+drop view if exists v1;
+--enable_parsing
+
+# We are going to use binary log later to check that statements are
+# logged in proper order, so it is good idea to reset it here.
+reset master;
+
+connect (addconn1,localhost,root,,);
+connect (addconn2,localhost,root,,);
+connection default;
+
+create table t1 (i int);
+create table t2 (i int);
+create view v1 as select * from t1;
+
+# First we try to concurrently execute statement that uses view
+# and statement that drops it. We use "user" locks as means to
+# suspend execution of first statement once it opens our view.
+select get_lock("lock_bg25144", 1);
+
+connection addconn1;
+--send insert into v1 values (get_lock("lock_bg25144", 100));
+
+connection addconn2;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "User lock" and info like "insert into v1 %lock_bg25144%";
+--source include/wait_condition.inc
+--send drop view v1;
+
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and info = "drop view v1";
+--source include/wait_condition.inc
+
+select release_lock("lock_bg25144");
+
+connection addconn1;
+--reap
+select release_lock("lock_bg25144");
+
+connection addconn2;
+--reap
+
+connection default;
+# Check that insertion through view did happen.
+select * from t1;
+# At the end of test we will check that statements were
+# logged in proper order.
+
+# Now we will repeat the test by trying concurrently execute
+# statement that uses a view and statement that alters it.
+create view v1 as select * from t1;
+
+select get_lock("lock_bg25144", 1);
+
+connection addconn1;
+--send insert into v1 values (get_lock("lock_bg25144", 100));
+
+connection addconn2;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "User lock" and info like "insert into v1 %lock_bg25144%";
+--source include/wait_condition.inc
+--send alter view v1 as select * from t2;
+
+connection default;
+let $wait_condition=
+ select count(*) = 1 from information_schema.processlist
+ where state = "Waiting for table" and
+ info = "alter view v1 as select * from t2";
+--source include/wait_condition.inc
+
+select release_lock("lock_bg25144");
+
+connection addconn1;
+--reap
+select release_lock("lock_bg25144");
+
+connection addconn2;
+--reap
+
+connection default;
+
+# Second insertion should go to t1 as well.
+select * from t1;
+select * from t2;
+
+# Now let us check that statements were logged in proper order
+--replace_column 2 # 5 #
+show binlog events in 'master-bin.000001' from 107;
+
+drop table t1, t2;
+drop view v1;
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt
index 15c2d950ff9..a40cf04f37c 100755
--- a/sql/CMakeLists.txt
+++ b/sql/CMakeLists.txt
@@ -76,7 +76,7 @@ SET (SQL_SOURCE
rpl_rli.cc rpl_mi.cc sql_servers.cc
sql_connect.cc scheduler.cc
sql_profile.cc event_parse_data.cc
- sql_signal.cc rpl_handler.cc
+ sql_signal.cc rpl_handler.cc mdl.cc
${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc
${PROJECT_SOURCE_DIR}/sql/sql_yacc.h
${PROJECT_SOURCE_DIR}/include/mysqld_error.h
diff --git a/sql/Makefile.am b/sql/Makefile.am
index 15ee0d588c4..95864dfbb1b 100644
--- a/sql/Makefile.am
+++ b/sql/Makefile.am
@@ -112,7 +112,8 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \
event_data_objects.h event_scheduler.h \
sql_partition.h partition_info.h partition_element.h \
contributors.h sql_servers.h sql_signal.h records.h \
- sql_prepare.h rpl_handler.h replication.h
+ sql_prepare.h rpl_handler.h replication.h mdl.h \
+ sql_plist.h
mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
item.cc item_sum.cc item_buff.cc item_func.cc \
@@ -158,7 +159,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
sql_plugin.cc sql_binlog.cc \
sql_builtin.cc sql_tablespace.cc partition_info.cc \
sql_servers.cc event_parse_data.cc sql_signal.cc \
- rpl_handler.cc
+ rpl_handler.cc mdl.cc
nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c
diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc
index 8faab5023da..0cf16e3a8a4 100644
--- a/sql/event_db_repository.cc
+++ b/sql/event_db_repository.cc
@@ -555,6 +555,7 @@ Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type,
DBUG_ENTER("Event_db_repository::open_event_table");
tables.init_one_table("mysql", "event", lock_type);
+ alloc_mdl_locks(&tables, thd->mem_root);
if (simple_open_n_lock_tables(thd, &tables))
{
@@ -1109,6 +1110,7 @@ Event_db_repository::check_system_tables(THD *thd)
/* Check mysql.db */
tables.init_one_table("mysql", "db", TL_READ);
+ alloc_mdl_locks(&tables, thd->mem_root);
if (simple_open_n_lock_tables(thd, &tables))
{
@@ -1126,6 +1128,7 @@ Event_db_repository::check_system_tables(THD *thd)
}
/* Check mysql.user */
tables.init_one_table("mysql", "user", TL_READ);
+ alloc_mdl_locks(&tables, thd->mem_root);
if (simple_open_n_lock_tables(thd, &tables))
{
@@ -1146,6 +1149,7 @@ Event_db_repository::check_system_tables(THD *thd)
}
/* Check mysql.event */
tables.init_one_table("mysql", "event", TL_READ);
+ alloc_mdl_locks(&tables, thd->mem_root);
if (simple_open_n_lock_tables(thd, &tables))
{
diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc
index b71a7f602ab..a7c4cbf5d20 100644
--- a/sql/ha_ndbcluster.cc
+++ b/sql/ha_ndbcluster.cc
@@ -565,7 +565,7 @@ int ha_ndbcluster::ndb_err(NdbTransaction *trans)
bzero((char*) &table_list,sizeof(table_list));
table_list.db= m_dbname;
table_list.alias= table_list.table_name= m_tabname;
- close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, FALSE, FALSE);
break;
}
default:
@@ -7276,6 +7276,20 @@ int ndbcluster_find_files(handlerton *hton, THD *thd,
}
}
+ /*
+ ndbcluster_find_files() may be called from I_S code and ndbcluster_binlog
+ thread in situations when some tables are already open. This means that
+ code below will try to obtain exclusive metadata lock on some table
+ while holding shared meta-data lock on other tables. This might lead to
+ a deadlock, and therefore is disallowed by assertions of the metadata
+ locking subsystem. In order to temporarily make the code work, we must
+ reset and backup the open tables state, thus hide the existing locks
+ from MDL asserts. But in the essence this is violation of metadata
+ locking protocol which has to be closed ASAP.
+ */
+ Open_tables_state open_tables_state_backup;
+ thd->reset_n_backup_open_tables_state(&open_tables_state_backup);
+
if (!global_read_lock)
{
// Delete old files
@@ -7299,8 +7313,11 @@ int ndbcluster_find_files(handlerton *hton, THD *thd,
}
}
+ thd->restore_backup_open_tables_state(&open_tables_state_backup);
+
+ /* Lock mutex before creating .FRM files. */
pthread_mutex_lock(&LOCK_open);
- // Create new files
+ /* Create new files. */
List_iterator_fast<char> it2(create_list);
while ((file_name_str=it2++))
{
@@ -8215,7 +8232,7 @@ int handle_trailing_share(NDB_SHARE *share)
table_list.db= share->db;
table_list.alias= table_list.table_name= share->table_name;
safe_mutex_assert_owner(&LOCK_open);
- close_cached_tables(thd, &table_list, TRUE, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, TRUE, FALSE);
pthread_mutex_lock(&ndbcluster_mutex);
/* ndb_share reference temporary free */
diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc
index e34a22cf9f4..bf76960201d 100644
--- a/sql/ha_ndbcluster_binlog.cc
+++ b/sql/ha_ndbcluster_binlog.cc
@@ -140,6 +140,8 @@ static Uint64 *p_latest_trans_gci= 0;
*/
static TABLE *ndb_binlog_index= 0;
static TABLE_LIST binlog_tables;
+static MDL_LOCK binlog_mdl_lock;
+static char binlog_mdlkey[MAX_DBKEY_LENGTH];
/*
Helper functions
@@ -282,7 +284,13 @@ static void run_query(THD *thd, char *buf, char *end,
thd_ndb->m_error_code,
(int) thd->is_error(), thd->is_slave_error);
}
+
+ /*
+ After executing statement we should unlock and close tables open
+ by it as well as release meta-data locks obtained by it.
+ */
close_thread_tables(thd);
+
/*
XXX: this code is broken. mysql_parse()/mysql_reset_thd_for_next_command()
can not be called from within a statement, and
@@ -921,7 +929,7 @@ int ndbcluster_setup_binlog_table_shares(THD *thd)
ndb_binlog_tables_inited= TRUE;
if (ndb_extra_logging)
sql_print_information("NDB Binlog: ndb tables writable");
- close_cached_tables(NULL, NULL, TRUE, FALSE, FALSE);
+ close_cached_tables(NULL, NULL, TRUE, FALSE);
pthread_mutex_unlock(&LOCK_open);
/* Signal injector thread that all is setup */
pthread_cond_signal(&injector_cond);
@@ -1735,7 +1743,7 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp,
bzero((char*) &table_list,sizeof(table_list));
table_list.db= (char *)dbname;
table_list.alias= table_list.table_name= (char *)tabname;
- close_cached_tables(thd, &table_list, TRUE, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, TRUE, FALSE);
if ((error= ndbcluster_binlog_open_table(thd, share,
table_share, table, 1)))
@@ -1841,7 +1849,7 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp,
bzero((char*) &table_list,sizeof(table_list));
table_list.db= (char *)dbname;
table_list.alias= table_list.table_name= (char *)tabname;
- close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, FALSE, FALSE);
/* ndb_share reference create free */
DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u",
share->key, share->use_count));
@@ -1962,7 +1970,7 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb,
bzero((char*) &table_list,sizeof(table_list));
table_list.db= schema->db;
table_list.alias= table_list.table_name= schema->name;
- close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, FALSE, FALSE);
}
/* ndb_share reference temporary free */
if (share)
@@ -2079,7 +2087,7 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb,
pthread_mutex_unlock(&ndb_schema_share_mutex);
/* end protect ndb_schema_share */
- close_cached_tables(NULL, NULL, FALSE, FALSE, FALSE);
+ close_cached_tables(NULL, NULL, FALSE, FALSE);
// fall through
case NDBEVENT::TE_ALTER:
ndb_handle_schema_change(thd, ndb, pOp, tmp_share);
@@ -2236,7 +2244,7 @@ ndb_binlog_thread_handle_schema_event_post_epoch(THD *thd,
bzero((char*) &table_list,sizeof(table_list));
table_list.db= schema->db;
table_list.alias= table_list.table_name= schema->name;
- close_cached_tables(thd, &table_list, FALSE, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, FALSE, FALSE);
}
if (schema_type != SOT_ALTER_TABLE)
break;
@@ -2323,18 +2331,21 @@ struct ndb_binlog_index_row {
/*
Open the ndb_binlog_index table
*/
-static int open_ndb_binlog_index(THD *thd, TABLE_LIST *tables,
- TABLE **ndb_binlog_index)
+static int open_ndb_binlog_index(THD *thd, TABLE **ndb_binlog_index)
{
static char repdb[]= NDB_REP_DB;
static char reptable[]= NDB_REP_TABLE;
const char *save_proc_info= thd->proc_info;
+ TABLE_LIST *tables= &binlog_tables;
bzero((char*) tables, sizeof(*tables));
tables->db= repdb;
tables->alias= tables->table_name= reptable;
tables->lock_type= TL_WRITE;
thd->proc_info= "Opening " NDB_REP_DB "." NDB_REP_TABLE;
+ mdl_init_lock(&binlog_mdl_lock, binlog_mdlkey, 0, tables->db,
+ tables->table_name);
+ tables->mdl_lock= &binlog_mdl_lock;
tables->required_type= FRMTYPE_TABLE;
uint counter;
thd->clear_error();
@@ -2374,7 +2385,7 @@ int ndb_add_ndb_binlog_index(THD *thd, void *_row)
for ( ; ; ) /* loop for need_reopen */
{
- if (!ndb_binlog_index && open_ndb_binlog_index(thd, &binlog_tables, &ndb_binlog_index))
+ if (!ndb_binlog_index && open_ndb_binlog_index(thd, &ndb_binlog_index))
{
error= -1;
goto add_ndb_binlog_index_err;
@@ -2385,7 +2396,7 @@ int ndb_add_ndb_binlog_index(THD *thd, void *_row)
if (need_reopen)
{
TABLE_LIST *p_binlog_tables= &binlog_tables;
- close_tables_for_reopen(thd, &p_binlog_tables);
+ close_tables_for_reopen(thd, &p_binlog_tables, FALSE);
ndb_binlog_index= 0;
continue;
}
@@ -3893,7 +3904,7 @@ restart:
static char db[]= "";
thd->db= db;
if (ndb_binlog_running)
- open_ndb_binlog_index(thd, &binlog_tables, &ndb_binlog_index);
+ open_ndb_binlog_index(thd, &ndb_binlog_index);
thd->db= db;
}
do_ndbcluster_binlog_close_connection= BCCC_running;
diff --git a/sql/handler.cc b/sql/handler.cc
index 17a92b00b4f..9c32171eefd 100644
--- a/sql/handler.cc
+++ b/sql/handler.cc
@@ -2997,20 +2997,13 @@ static bool update_frm_version(TABLE *table)
if ((file= my_open(path, O_RDWR|O_BINARY, MYF(MY_WME))) >= 0)
{
uchar version[4];
- char *key= table->s->table_cache_key.str;
- uint key_length= table->s->table_cache_key.length;
- TABLE *entry;
- HASH_SEARCH_STATE state;
int4store(version, MYSQL_VERSION_ID);
if ((result= my_pwrite(file,(uchar*) version,4,51L,MYF_RW)))
goto err;
- for (entry=(TABLE*) my_hash_first(&open_cache,(uchar*) key,key_length, &state);
- entry;
- entry= (TABLE*) my_hash_next(&open_cache,(uchar*) key,key_length, &state))
- entry->s->mysql_version= MYSQL_VERSION_ID;
+ table->s->mysql_version= MYSQL_VERSION_ID;
}
err:
if (file >= 0)
diff --git a/sql/lock.cc b/sql/lock.cc
index 56ae94ddc39..0c8c3095844 100644
--- a/sql/lock.cc
+++ b/sql/lock.cc
@@ -955,361 +955,56 @@ static MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count,
*****************************************************************************/
/**
- Lock and wait for the named lock.
+ Obtain exclusive metadata locks on the list of tables.
- @param thd Thread handler
- @param table_list Lock first table in this list
+ @param thd Thread handle
+ @param table_list List of tables to lock
+ @note This function assumes that no metadata locks were acquired
+ before calling it. Also it cannot be called while holding
+ LOCK_open mutex. Both these invariants are enforced by asserts
+ in mdl_acquire_exclusive_locks() functions.
- @note
- Works together with global read lock.
-
- @retval
- 0 ok
- @retval
- 1 error
-*/
-
-int lock_and_wait_for_table_name(THD *thd, TABLE_LIST *table_list)
-{
- int lock_retcode;
- int error= -1;
- DBUG_ENTER("lock_and_wait_for_table_name");
-
- if (wait_if_global_read_lock(thd, 0, 1))
- DBUG_RETURN(1);
- pthread_mutex_lock(&LOCK_open);
- if ((lock_retcode = lock_table_name(thd, table_list, TRUE)) < 0)
- goto end;
- if (lock_retcode && wait_for_locked_table_names(thd, table_list))
- {
- unlock_table_name(thd, table_list);
- goto end;
- }
- error=0;
-
-end:
- pthread_mutex_unlock(&LOCK_open);
- start_waiting_global_read_lock(thd);
- DBUG_RETURN(error);
-}
-
-
-/**
- Put a not open table with an old refresh version in the table cache.
-
- @param thd Thread handler
- @param table_list Lock first table in this list
- @param check_in_use Do we need to check if table already in use by us
-
- @note
- One must have a lock on LOCK_open!
-
- @warning
- If you are going to update the table, you should use
- lock_and_wait_for_table_name instead of this function as this works
- together with 'FLUSH TABLES WITH READ LOCK'
-
- @note
- This will force any other threads that uses the table to release it
- as soon as possible.
-
- @return
- < 0 error
- @return
- == 0 table locked
- @return
- > 0 table locked, but someone is using it
-*/
-
-int lock_table_name(THD *thd, TABLE_LIST *table_list, bool check_in_use)
-{
- TABLE *table;
- char key[MAX_DBKEY_LENGTH];
- char *db= table_list->db;
- uint key_length;
- bool found_locked_table= FALSE;
- HASH_SEARCH_STATE state;
- DBUG_ENTER("lock_table_name");
- DBUG_PRINT("enter",("db: %s name: %s", db, table_list->table_name));
-
- key_length= create_table_def_key(thd, key, table_list, 0);
-
- if (check_in_use)
- {
- /* Only insert the table if we haven't insert it already */
- for (table=(TABLE*) my_hash_first(&open_cache, (uchar*)key,
- key_length, &state);
- table ;
- table = (TABLE*) my_hash_next(&open_cache,(uchar*) key,
- key_length, &state))
- {
- if (table->reginfo.lock_type < TL_WRITE)
- {
- if (table->in_use == thd)
- found_locked_table= TRUE;
- continue;
- }
-
- if (table->in_use == thd)
- {
- DBUG_PRINT("info", ("Table is in use"));
- table->s->version= 0; // Ensure no one can use this
- table->locked_by_name= 1;
- DBUG_RETURN(0);
- }
- }
- }
-
- if (thd->locked_tables && thd->locked_tables->table_count &&
- ! find_temporary_table(thd, table_list->db, table_list->table_name))
- {
- if (found_locked_table)
- my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_list->alias);
- else
- my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_list->alias);
-
- DBUG_RETURN(-1);
- }
-
- if (!(table= table_cache_insert_placeholder(thd, key, key_length)))
- DBUG_RETURN(-1);
-
- table_list->table=table;
-
- /* Return 1 if table is in use */
- DBUG_RETURN(test(remove_table_from_cache(thd, db, table_list->table_name,
- check_in_use ? RTFC_NO_FLAG : RTFC_WAIT_OTHER_THREAD_FLAG)));
-}
-
-
-void unlock_table_name(THD *thd, TABLE_LIST *table_list)
-{
- if (table_list->table)
- {
- my_hash_delete(&open_cache, (uchar*) table_list->table);
- broadcast_refresh();
- }
-}
-
-
-static bool locked_named_table(THD *thd, TABLE_LIST *table_list)
-{
- for (; table_list ; table_list=table_list->next_local)
- {
- TABLE *table= table_list->table;
- if (table)
- {
- TABLE *save_next= table->next;
- bool result;
- table->next= 0;
- result= table_is_used(table_list->table, 0);
- table->next= save_next;
- if (result)
- return 1;
- }
- }
- return 0; // All tables are locked
-}
-
-
-bool wait_for_locked_table_names(THD *thd, TABLE_LIST *table_list)
-{
- bool result=0;
- DBUG_ENTER("wait_for_locked_table_names");
-
- safe_mutex_assert_owner(&LOCK_open);
-
- while (locked_named_table(thd,table_list))
- {
- if (thd->killed)
- {
- result=1;
- break;
- }
- wait_for_condition(thd, &LOCK_open, &COND_refresh);
- pthread_mutex_lock(&LOCK_open);
- }
- DBUG_RETURN(result);
-}
-
-
-/**
- Lock all tables in list with a name lock.
-
- REQUIREMENTS
- - One must have a lock on LOCK_open when calling this
-
- @param thd Thread handle
- @param table_list Names of tables to lock
-
- @note
- If you are just locking one table, you should use
- lock_and_wait_for_table_name().
-
- @retval
- 0 ok
- @retval
- 1 Fatal error (end of memory ?)
+ @retval FALSE Success.
+ @retval TRUE Failure (OOM or thread was killed).
*/
bool lock_table_names(THD *thd, TABLE_LIST *table_list)
{
- bool got_all_locks=1;
TABLE_LIST *lock_table;
+ MDL_LOCK *mdl_lock;
for (lock_table= table_list; lock_table; lock_table= lock_table->next_local)
{
- int got_lock;
- if ((got_lock=lock_table_name(thd,lock_table, TRUE)) < 0)
- goto end; // Fatal error
- if (got_lock)
- got_all_locks=0; // Someone is using table
+ if (!(mdl_lock= mdl_alloc_lock(0, lock_table->db, lock_table->table_name,
+ thd->mem_root)))
+ goto end;
+ mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE);
+ mdl_add_lock(&thd->mdl_context, mdl_lock);
}
-
- /* If some table was in use, wait until we got the lock */
- if (!got_all_locks && wait_for_locked_table_names(thd, table_list))
- goto end;
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+ return 1;
return 0;
end:
- unlock_table_names(thd, table_list, lock_table);
+ mdl_remove_all_locks(&thd->mdl_context);
return 1;
}
/**
- Unlock all tables in list with a name lock.
-
- @param thd Thread handle.
- @param table_list Names of tables to lock.
-
- @note
- This function needs to be protected by LOCK_open. If we're
- under LOCK TABLES, this function does not work as advertised. Namely,
- it does not exclude other threads from using this table and does not
- put an exclusive name lock on this table into the table cache.
-
- @see lock_table_names
- @see unlock_table_names
-
- @retval TRUE An error occured.
- @retval FALSE Name lock successfully acquired.
-*/
-
-bool lock_table_names_exclusively(THD *thd, TABLE_LIST *table_list)
-{
- if (lock_table_names(thd, table_list))
- return TRUE;
-
- /*
- Upgrade the table name locks from semi-exclusive to exclusive locks.
- */
- for (TABLE_LIST *table= table_list; table; table= table->next_global)
- {
- if (table->table)
- table->table->open_placeholder= 1;
- }
- return FALSE;
-}
-
-
-/**
- Test is 'table' is protected by an exclusive name lock.
-
- @param[in] thd The current thread handler
- @param[in] table_list Table container containing the single table to be
- tested
-
- @note Needs to be protected by LOCK_open mutex.
-
- @return Error status code
- @retval TRUE Table is protected
- @retval FALSE Table is not protected
-*/
-
-bool
-is_table_name_exclusively_locked_by_this_thread(THD *thd,
- TABLE_LIST *table_list)
-{
- char key[MAX_DBKEY_LENGTH];
- uint key_length;
-
- key_length= create_table_def_key(thd, key, table_list, 0);
-
- return is_table_name_exclusively_locked_by_this_thread(thd, (uchar *)key,
- key_length);
-}
-
-
-/**
- Test is 'table key' is protected by an exclusive name lock.
-
- @param[in] thd The current thread handler.
- @param[in] key
- @param[in] key_length
-
- @note Needs to be protected by LOCK_open mutex
+ Release all metadata locks previously obtained by lock_table_names().
- @retval TRUE Table is protected
- @retval FALSE Table is not protected
- */
-
-bool
-is_table_name_exclusively_locked_by_this_thread(THD *thd, uchar *key,
- int key_length)
-{
- HASH_SEARCH_STATE state;
- TABLE *table;
-
- for (table= (TABLE*) my_hash_first(&open_cache, key,
- key_length, &state);
- table ;
- table= (TABLE*) my_hash_next(&open_cache, key,
- key_length, &state))
- {
- if (table->in_use == thd &&
- table->open_placeholder == 1 &&
- table->s->version == 0)
- return TRUE;
- }
-
- return FALSE;
-}
+ @param thd Thread handle.
-/**
- Unlock all tables in list with a name lock.
-
- @param
- thd Thread handle
- @param
- table_list Names of tables to unlock
- @param
- last_table Don't unlock any tables after this one.
- (default 0, which will unlock all tables)
-
- @note
- One must have a lock on LOCK_open when calling this.
-
- @note
- This function will broadcast refresh signals to inform other threads
- that the name locks are removed.
-
- @retval
- 0 ok
- @retval
- 1 Fatal error (end of memory ?)
+ @note Cannot be called while holding LOCK_open mutex.
*/
-void unlock_table_names(THD *thd, TABLE_LIST *table_list,
- TABLE_LIST *last_table)
+void unlock_table_names(THD *thd)
{
DBUG_ENTER("unlock_table_names");
- for (TABLE_LIST *table= table_list;
- table != last_table;
- table= table->next_local)
- unlock_table_name(thd,table);
- broadcast_refresh();
+ mdl_release_locks(&thd->mdl_context);
+ mdl_remove_all_locks(&thd->mdl_context);
DBUG_VOID_RETURN;
}
@@ -1455,6 +1150,33 @@ bool lock_global_read_lock(THD *thd)
thd->global_read_lock= GOT_GLOBAL_READ_LOCK;
global_read_lock++;
thd->exit_cond(old_message); // this unlocks LOCK_global_read_lock
+ /*
+ When we perform FLUSH TABLES or ALTER TABLE under LOCK TABLES,
+ tables being reopened are protected only by meta-data locks at
+ some point. To avoid sneaking in with our global read lock at
+ this moment we have to take global shared meta data lock.
+
+ TODO: We should change this code to acquire global shared metadata
+ lock before acquiring global read lock. But in order to do
+ this we have to get rid of all those places in which
+ wait_if_global_read_lock() is called before acquiring
+ metadata locks first. Also long-term we should get rid of
+ redundancy between metadata locks, global read lock and DDL
+ blocker (see WL#4399 and WL#4400).
+ */
+ if (mdl_acquire_global_shared_lock(&thd->mdl_context))
+ {
+ /* Our thread was killed -- return back to initial state. */
+ pthread_mutex_lock(&LOCK_global_read_lock);
+ if (!(--global_read_lock))
+ {
+ DBUG_PRINT("signal", ("Broadcasting COND_global_read_lock"));
+ pthread_cond_broadcast(&COND_global_read_lock);
+ }
+ pthread_mutex_unlock(&LOCK_global_read_lock);
+ thd->global_read_lock= 0;
+ DBUG_RETURN(1);
+ }
}
/*
We DON'T set global_read_lock_blocks_commit now, it will be set after
@@ -1476,6 +1198,8 @@ void unlock_global_read_lock(THD *thd)
("global_read_lock: %u global_read_lock_blocks_commit: %u",
global_read_lock, global_read_lock_blocks_commit));
+ mdl_release_global_shared_lock(&thd->mdl_context);
+
pthread_mutex_lock(&LOCK_global_read_lock);
tmp= --global_read_lock;
if (thd->global_read_lock == MADE_GLOBAL_READ_LOCK_BLOCK_COMMIT)
diff --git a/sql/log_event.cc b/sql/log_event.cc
index 3d35dec4fb0..fd0e20d690d 100644
--- a/sql/log_event.cc
+++ b/sql/log_event.cc
@@ -3036,7 +3036,7 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli,
}
else
{
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
}
/*
@@ -7238,8 +7238,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli)
*/
DBUG_ASSERT(get_flags(STMT_END_F));
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
- close_thread_tables(thd);
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
thd->clear_error();
DBUG_RETURN(0);
}
@@ -7322,7 +7321,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli)
"unexpected success or fatal error"));
thd->is_slave_error= 1;
}
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
DBUG_RETURN(actual_error);
}
@@ -7347,7 +7346,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli)
mysql_unlock_tables(thd, thd->lock);
thd->lock= 0;
thd->is_slave_error= 1;
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
DBUG_RETURN(ERR_BAD_TABLE_DEF);
}
}
@@ -7530,12 +7529,6 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli)
}
} // if (table)
- /*
- We need to delay this clear until here bacause unpack_current_row() uses
- master-side table definitions stored in rli.
- */
- if (rli->tables_to_lock && get_flags(STMT_END_F))
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
if (error)
{
@@ -8064,7 +8057,8 @@ Table_map_log_event::~Table_map_log_event()
int Table_map_log_event::do_apply_event(Relay_log_info const *rli)
{
RPL_TABLE_LIST *table_list;
- char *db_mem, *tname_mem;
+ char *db_mem, *tname_mem, *mdlkey;
+ MDL_LOCK *mdl_lock;
size_t dummy_len;
void *memory;
DBUG_ENTER("Table_map_log_event::do_apply_event(Relay_log_info*)");
@@ -8079,6 +8073,8 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli)
&table_list, (uint) sizeof(RPL_TABLE_LIST),
&db_mem, (uint) NAME_LEN + 1,
&tname_mem, (uint) NAME_LEN + 1,
+ &mdl_lock, sizeof(MDL_LOCK),
+ &mdlkey, MAX_DBKEY_LENGTH,
NullS)))
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
@@ -8091,6 +8087,8 @@ int Table_map_log_event::do_apply_event(Relay_log_info const *rli)
table_list->updating= 1;
strmov(table_list->db, rpl_filter->get_rewrite_db(m_dbnam, &dummy_len));
strmov(table_list->table_name, m_tblnam);
+ mdl_init_lock(mdl_lock, mdlkey, 0, table_list->db, table_list->table_name);
+ table_list->mdl_lock= mdl_lock;
int error= 0;
diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc
index fbcbb388236..030d51b3618 100644
--- a/sql/log_event_old.cc
+++ b/sql/log_event_old.cc
@@ -32,8 +32,7 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info
*/
DBUG_ASSERT(ev->get_flags(Old_rows_log_event::STMT_END_F));
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
- close_thread_tables(thd);
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
thd->clear_error();
DBUG_RETURN(0);
}
@@ -91,7 +90,7 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info
"unexpected success or fatal error"));
thd->is_slave_error= 1;
}
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
DBUG_RETURN(actual_error);
}
@@ -109,10 +108,8 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info
{
if (ptr->m_tabledef.compatible_with(rli, ptr->table))
{
- mysql_unlock_tables(thd, thd->lock);
- thd->lock= 0;
thd->is_slave_error= 1;
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
DBUG_RETURN(Old_rows_log_event::ERR_BAD_TABLE_DEF);
}
}
@@ -236,13 +233,6 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, const Relay_log_info
}
}
- /*
- We need to delay this clear until the table def is no longer needed.
- The table def is needed in unpack_row().
- */
- if (rli->tables_to_lock && ev->get_flags(Old_rows_log_event::STMT_END_F))
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
-
if (error)
{ /* error has occured during the transaction */
rli->report(ERROR_LEVEL, thd->stmt_da->sql_errno(),
@@ -1440,8 +1430,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli)
*/
DBUG_ASSERT(get_flags(STMT_END_F));
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
- close_thread_tables(thd);
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
thd->clear_error();
DBUG_RETURN(0);
}
@@ -1496,7 +1485,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli)
"Error in %s event: when locking tables",
get_type_str());
}
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
DBUG_RETURN(error);
}
@@ -1515,7 +1504,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli)
*/
thd->binlog_flush_pending_rows_event(false);
TABLE_LIST *tables= rli->tables_to_lock;
- close_tables_for_reopen(thd, &tables);
+ close_tables_for_reopen(thd, &tables, FALSE);
uint tables_count= rli->tables_to_lock_count;
if ((error= open_tables(thd, &tables, &tables_count, 0)))
@@ -1533,7 +1522,7 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli)
"unexpected success or fatal error"));
thd->is_slave_error= 1;
}
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
DBUG_RETURN(error);
}
}
@@ -1552,10 +1541,8 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli)
{
if (ptr->m_tabledef.compatible_with(rli, ptr->table))
{
- mysql_unlock_tables(thd, thd->lock);
- thd->lock= 0;
thd->is_slave_error= 1;
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
+ const_cast<Relay_log_info*>(rli)->slave_close_thread_tables(thd);
DBUG_RETURN(ERR_BAD_TABLE_DEF);
}
}
@@ -1726,13 +1713,6 @@ int Old_rows_log_event::do_apply_event(Relay_log_info const *rli)
}
} // if (table)
- /*
- We need to delay this clear until here bacause unpack_current_row() uses
- master-side table definitions stored in rli.
- */
- if (rli->tables_to_lock && get_flags(STMT_END_F))
- const_cast<Relay_log_info*>(rli)->clear_tables_to_lock();
-
if (error)
{ /* error has occured during the transaction */
rli->report(ERROR_LEVEL, thd->net.last_errno,
diff --git a/sql/mdl.cc b/sql/mdl.cc
new file mode 100644
index 00000000000..00dcc12cdc8
--- /dev/null
+++ b/sql/mdl.cc
@@ -0,0 +1,1342 @@
+/* Copyright (C) 2007-2008 MySQL AB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+
+/*
+ TODO: Remove this dependency on mysql_priv.h. It's not
+ trivial step at the moment since currently we access to
+ some of THD members and use some of its methods here.
+*/
+#include "mysql_priv.h"
+#include "mdl.h"
+
+
+/**
+ The lock context. Created internally for an acquired lock.
+ For a given name, there exists only one MDL_LOCK_DATA instance,
+ and it exists only when the lock has been granted.
+ Can be seen as an MDL subsystem's version of TABLE_SHARE.
+*/
+
+struct MDL_LOCK_DATA
+{
+ I_P_List<MDL_LOCK, MDL_LOCK_lock> active_shared;
+ /*
+ There can be several upgraders and active exclusive
+ belonging to the same context.
+ */
+ I_P_List<MDL_LOCK, MDL_LOCK_lock> active_shared_waiting_upgrade;
+ I_P_List<MDL_LOCK, MDL_LOCK_lock> active_exclusive;
+ I_P_List<MDL_LOCK, MDL_LOCK_lock> waiting_exclusive;
+ uint users;
+ void *cached_object;
+ mdl_cached_object_release_hook cached_object_release_hook;
+
+ MDL_LOCK_DATA() : cached_object(0), cached_object_release_hook(0) {}
+
+ MDL_LOCK *get_key_owner()
+ {
+ return !active_shared.is_empty() ?
+ active_shared.head() :
+ (!active_shared_waiting_upgrade.is_empty() ?
+ active_shared_waiting_upgrade.head() :
+ (!active_exclusive.is_empty() ?
+ active_exclusive.head() : waiting_exclusive.head()));
+ }
+
+ bool has_no_other_users()
+ {
+ return (users == 1);
+ }
+};
+
+
+pthread_mutex_t LOCK_mdl;
+pthread_cond_t COND_mdl;
+HASH mdl_locks;
+uint global_shared_locks_pending;
+uint global_shared_locks_acquired;
+uint global_intention_exclusive_locks_acquired;
+
+
+
+extern "C" uchar *mdl_locks_key(const uchar *record, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ MDL_LOCK_DATA *entry=(MDL_LOCK_DATA*) record;
+ *length= entry->get_key_owner()->key_length;
+ return (uchar*) entry->get_key_owner()->key;
+}
+
+
+/**
+ Initialize the metadata locking subsystem.
+
+ This function is called at server startup.
+
+ In particular, initializes the new global mutex and
+ the associated condition variable: LOCK_mdl and COND_mdl.
+ These locking primitives are implementation details of the MDL
+ subsystem and are private to it.
+
+ Note, that even though the new implementation adds acquisition
+ of a new global mutex to the execution flow of almost every SQL
+ statement, the design capitalizes on that to later save on
+ look ups in the table definition cache. This leads to reduced
+ contention overall and on LOCK_open in particular.
+ Please see the description of mdl_acquire_shared_lock() for details.
+*/
+
+void mdl_init()
+{
+ pthread_mutex_init(&LOCK_mdl, NULL);
+ pthread_cond_init(&COND_mdl, NULL);
+ my_hash_init(&mdl_locks, &my_charset_bin, 16 /* FIXME */, 0, 0,
+ mdl_locks_key, 0, 0);
+ global_shared_locks_pending= global_shared_locks_acquired= 0;
+ global_intention_exclusive_locks_acquired= 0;
+}
+
+
+/**
+ Release resources of metadata locking subsystem.
+
+ Destroys the global mutex and the condition variable.
+ Called at server shutdown.
+*/
+
+void mdl_destroy()
+{
+ DBUG_ASSERT(!mdl_locks.records);
+ pthread_mutex_destroy(&LOCK_mdl);
+ pthread_cond_destroy(&COND_mdl);
+ my_hash_free(&mdl_locks);
+}
+
+
+/**
+ Initialize a metadata locking context.
+
+ This is to be called when a new server connection is created.
+*/
+
+void mdl_context_init(MDL_CONTEXT *context, THD *thd)
+{
+ context->locks.empty();
+ context->thd= thd;
+ context->has_global_shared_lock= FALSE;
+}
+
+
+/**
+ Destroy metadata locking context.
+
+ Assumes and asserts that there are no active or pending locks
+ associated with this context at the time of the destruction.
+
+ Currently does nothing. Asserts that there are no pending
+ or satisfied lock requests. The pending locks must be released
+ prior to destruction. This is a new way to express the assertion
+ that all tables are closed before a connection is destroyed.
+*/
+
+void mdl_context_destroy(MDL_CONTEXT *context)
+{
+ DBUG_ASSERT(context->locks.is_empty());
+ DBUG_ASSERT(!context->has_global_shared_lock);
+}
+
+
+/**
+ Backup and reset state of meta-data locking context.
+
+ mdl_context_backup_and_reset(), mdl_context_restore() and
+ mdl_context_merge() are used by HANDLER implementation which
+ needs to open table for new HANDLER independently of already
+ open HANDLERs and add this table/metadata lock to the set of
+ tables open/metadata locks for HANDLERs afterwards.
+*/
+
+void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup)
+{
+ backup->locks.empty();
+ ctx->locks.swap(backup->locks);
+}
+
+
+/**
+ Restore state of meta-data locking context from backup.
+*/
+
+void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup)
+{
+ DBUG_ASSERT(ctx->locks.is_empty());
+ ctx->locks.swap(backup->locks);
+}
+
+
+/**
+ Merge meta-data locks from one context into another.
+*/
+
+void mdl_context_merge(MDL_CONTEXT *dst, MDL_CONTEXT *src)
+{
+ MDL_LOCK *l;
+
+ DBUG_ASSERT(dst->thd == src->thd);
+
+ if (!src->locks.is_empty())
+ {
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(src->locks);
+ while ((l= it++))
+ {
+ DBUG_ASSERT(l->ctx);
+ l->ctx= dst;
+ dst->locks.push_front(l);
+ }
+ src->locks.empty();
+ }
+}
+
+
+/**
+ Initialize a lock request.
+
+ This is to be used for every lock request.
+
+ Note that initialization and allocation are split
+ into two calls. This is to allow flexible memory management
+ of lock requests. Normally a lock request is stored
+ in statement memory (e.g. is a member of struct TABLE_LIST),
+ but we would also like to allow allocation of lock
+ requests in other memory roots, for example in the grant
+ subsystem, to lock privilege tables.
+
+ The MDL subsystem does not own or manage memory of lock
+ requests. Instead it assumes that the life time of every lock
+ request encloses calls to mdl_acquire_shared_lock() and
+ mdl_release_locks().
+
+ @param mdl Pointer to an MDL_LOCK object to initialize
+ @param key_buff Pointer to the buffer for key for the lock request
+ (should be at least strlen(db) + strlen(name)
+ + 2 bytes, or, if the lengths are not known, MAX_DBNAME_LENGTH)
+ @param type Id of type of object to be locked
+ @param db Name of database to which the object belongs
+ @param name Name of of the object
+
+ Stores the database name, object name and the type in the key
+ buffer. Initializes mdl_el to point to the key.
+ We can't simply initialize mdl_el with type, db and name
+ by-pointer because of the underlying HASH implementation
+ requires the key to be a contiguous buffer.
+
+ The initialized lock request will have MDL_SHARED type and
+ normal priority.
+
+ Suggested lock types: TABLE - 0 PROCEDURE - 1 FUNCTION - 2
+ Note that tables and views have the same lock type, since
+ they share the same name space in the SQL standard.
+*/
+
+void mdl_init_lock(MDL_LOCK *mdl, char *key, int type, const char *db,
+ const char *name)
+{
+ int4store(key, type);
+ mdl->key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
+ mdl->key= key;
+ mdl->type= MDL_SHARED;
+ mdl->state= MDL_PENDING;
+ mdl->prio= MDL_NORMAL_PRIO;
+ mdl->upgradable= FALSE;
+#ifndef DBUG_OFF
+ mdl->ctx= 0;
+ mdl->lock_data= 0;
+#endif
+}
+
+
+/**
+ Allocate and initialize one lock request.
+
+ Same as mdl_init_lock(), but allocates the lock and the key buffer
+ on a memory root. Necessary to lock ad-hoc tables, e.g.
+ mysql.* tables of grant and data dictionary subsystems.
+
+ @param type Id of type of object to be locked
+ @param db Name of database to which object belongs
+ @param name Name of of object
+ @param root MEM_ROOT on which object should be allocated
+
+ @note The allocated lock request will have MDL_SHARED type and
+ normal priority.
+
+ @retval 0 Error
+ @retval non-0 Pointer to an object representing a lock request
+*/
+
+MDL_LOCK *mdl_alloc_lock(int type, const char *db, const char *name,
+ MEM_ROOT *root)
+{
+ MDL_LOCK *lock;
+ char *key;
+
+ if (!multi_alloc_root(root, &lock, sizeof(MDL_LOCK), &key,
+ MAX_DBKEY_LENGTH, NULL))
+ return NULL;
+
+ mdl_init_lock(lock, key, type, db, name);
+
+ return lock;
+}
+
+
+/**
+ Add a lock request to the list of lock requests of the context.
+
+ The procedure to acquire metadata locks is:
+ - allocate and initialize lock requests (mdl_alloc_lock())
+ - associate them with a context (mdl_add_lock())
+ - call mdl_acquire_shared_lock()/mdl_release_lock() (maybe repeatedly).
+
+ Associates a lock request with the given context.
+
+ @param context The MDL context to associate the lock with.
+ There should be no more than one context per
+ connection, to avoid deadlocks.
+ @param lock The lock request to be added.
+*/
+
+void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK *lock)
+{
+ DBUG_ENTER("mdl_add_lock");
+ DBUG_ASSERT(lock->state == MDL_PENDING);
+ DBUG_ASSERT(!lock->ctx);
+ lock->ctx= context;
+ context->locks.push_front(lock);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Clear all lock requests in the context (clear the context).
+
+ Disassociates lock requests from the context.
+ All granted locks must be released prior to calling this
+ function.
+
+ In other words, the expected procedure to release locks is:
+ - mdl_release_locks();
+ - mdl_remove_all_locks();
+
+ We could possibly merge mdl_remove_all_locks() and mdl_release_locks(),
+ but this function comes in handy when we need to back off: in that case
+ we release all the locks acquired so-far but do not free them, since
+ we know that the respective lock requests will be used again.
+
+ Also resets lock requests back to their initial state (i.e.
+ sets type and priority to MDL_SHARED and MDL_NORMAL_PRIO).
+
+ @param context Context to be cleared.
+*/
+
+void mdl_remove_all_locks(MDL_CONTEXT *context)
+{
+ MDL_LOCK *l;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+ while ((l= it++))
+ {
+ /* Reset lock request back to its initial state. */
+ l->type= MDL_SHARED;
+ l->prio= MDL_NORMAL_PRIO;
+ l->upgradable= FALSE;
+#ifndef DBUG_OFF
+ l->ctx= 0;
+#endif
+ }
+ context->locks.empty();
+}
+
+
+/**
+ Auxiliary functions needed for creation/destruction of MDL_LOCK_DATA
+ objects.
+
+ @todo This naive implementation should be replaced with one that saves
+ on memory allocation by reusing released objects.
+*/
+
+static MDL_LOCK_DATA* get_lock_data_object(void)
+{
+ return new MDL_LOCK_DATA();
+}
+
+
+static void release_lock_data_object(MDL_LOCK_DATA *lock)
+{
+ delete lock;
+}
+
+
+/**
+ Try to acquire one shared lock.
+
+ Unlike exclusive locks, shared locks are acquired one by
+ one. This is interface is chosen to simplify introduction of
+ the new locking API to the system. mdl_acquire_shared_lock()
+ is currently used from open_table(), and there we have only one
+ table to work with.
+
+ In future we may consider allocating multiple shared locks at once.
+
+ This function must be called after the lock is added to a context.
+
+ @param lock [in] Lock request object for lock to be acquired
+ @param retry [out] Indicates that conflicting lock exists and another
+ attempt should be made after releasing all current
+ locks and waiting for conflicting lock go away
+ (using mdl_wait_for_locks()).
+
+ @retval FALSE Success.
+ @retval TRUE Failure. Either error occured or conflicting lock exists.
+ In the latter case "retry" parameter is set to TRUE.
+*/
+
+bool mdl_acquire_shared_lock(MDL_LOCK *l, bool *retry)
+{
+ MDL_LOCK_DATA *lock_data;
+ *retry= FALSE;
+
+ DBUG_ASSERT(l->type == MDL_SHARED && l->state == MDL_PENDING);
+
+ safe_mutex_assert_not_owner(&LOCK_open);
+
+ if (l->ctx->has_global_shared_lock && l->upgradable)
+ {
+ my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0));
+ return TRUE;
+ }
+
+ pthread_mutex_lock(&LOCK_mdl);
+
+ if (l->upgradable &&
+ (global_shared_locks_acquired || global_shared_locks_pending))
+ {
+ pthread_mutex_unlock(&LOCK_mdl);
+ *retry= TRUE;
+ return TRUE;
+ }
+
+ if (!(lock_data= (MDL_LOCK_DATA *)my_hash_search(&mdl_locks, (uchar*)l->key,
+ l->key_length)))
+ {
+ lock_data= get_lock_data_object();
+ lock_data->active_shared.push_front(l);
+ lock_data->users= 1;
+ my_hash_insert(&mdl_locks, (uchar*)lock_data);
+ l->state= MDL_ACQUIRED;
+ l->lock_data= lock_data;
+ if (l->upgradable)
+ global_intention_exclusive_locks_acquired++;
+ }
+ else
+ {
+ if ((lock_data->active_exclusive.is_empty() &&
+ (l->prio == MDL_HIGH_PRIO ||
+ lock_data->waiting_exclusive.is_empty() &&
+ lock_data->active_shared_waiting_upgrade.is_empty())) ||
+ (!lock_data->active_exclusive.is_empty() &&
+ lock_data->active_exclusive.head()->ctx == l->ctx))
+ {
+ /*
+ When exclusive lock comes from the same context we can satisfy our
+ shared lock. This is required for CREATE TABLE ... SELECT ... and
+ ALTER VIEW ... AS ....
+ */
+ lock_data->active_shared.push_front(l);
+ lock_data->users++;
+ l->state= MDL_ACQUIRED;
+ l->lock_data= lock_data;
+ if (l->upgradable)
+ global_intention_exclusive_locks_acquired++;
+ }
+ else
+ *retry= TRUE;
+ }
+ pthread_mutex_unlock(&LOCK_mdl);
+
+ return *retry;
+}
+
+
+static void release_lock(MDL_LOCK *l);
+
+
+/**
+ Acquire exclusive locks. The context must contain the list of
+ locks to be acquired. There must be no granted locks in the
+ context.
+
+ This is a replacement of lock_table_names(). It is used in
+ RENAME, DROP and other DDL SQL statements.
+
+ @param context A context containing requests for exclusive locks
+ The context may not have other lock requests.
+
+ @note In case of failure (for example, if our thread was killed)
+ resets lock requests back to their initial state (MDL_SHARED
+ and MDL_NORMAL_PRIO).
+
+ @retval FALSE Success
+ @retval TRUE Failure
+*/
+
+bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context)
+{
+ MDL_LOCK *l, *lh;
+ MDL_LOCK_DATA *lock_data;
+ bool signalled= FALSE;
+ const char *old_msg;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+ THD *thd= context->thd;
+
+ DBUG_ASSERT(thd == current_thd);
+
+ safe_mutex_assert_not_owner(&LOCK_open);
+
+ if (context->has_global_shared_lock)
+ {
+ my_error(ER_CANT_UPDATE_WITH_READLOCK, MYF(0));
+ return TRUE;
+ }
+
+ pthread_mutex_lock(&LOCK_mdl);
+
+ old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table");
+
+ while ((l= it++))
+ {
+ DBUG_ASSERT(l->type == MDL_EXCLUSIVE && l->state == MDL_PENDING);
+ if (!(lock_data= (MDL_LOCK_DATA *)my_hash_search(&mdl_locks, (uchar*)l->key,
+ l->key_length)))
+ {
+ lock_data= get_lock_data_object();
+ lock_data->waiting_exclusive.push_front(l);
+ lock_data->users= 1;
+ my_hash_insert(&mdl_locks, (uchar*)lock_data);
+ l->lock_data= lock_data;
+ }
+ else
+ {
+ lock_data->waiting_exclusive.push_front(l);
+ lock_data->users++;
+ l->lock_data= lock_data;
+ }
+ }
+
+ while (1)
+ {
+ it.rewind();
+ while ((l= it++))
+ {
+ lock_data= l->lock_data;
+
+ if (global_shared_locks_acquired || global_shared_locks_pending)
+ {
+ /*
+ There is active or pending global shared lock we have
+ to wait until it goes away.
+ */
+ signalled= TRUE;
+ break;
+ }
+ else if (!lock_data->active_exclusive.is_empty() ||
+ !lock_data->active_shared_waiting_upgrade.is_empty())
+ {
+ /*
+ Exclusive MDL owner won't wait on table-level lock the same
+ applies to shared lock waiting upgrade (in this cases we already
+ have some table-level lock).
+ */
+ signalled= TRUE;
+ break;
+ }
+ else if ((lh= lock_data->active_shared.head()))
+ {
+ signalled= notify_thread_having_shared_lock(thd, lh->ctx->thd);
+ break;
+ }
+ }
+ if (!l)
+ break;
+ if (signalled)
+ pthread_cond_wait(&COND_mdl, &LOCK_mdl);
+ else
+ {
+ /*
+ Another thread obtained shared MDL-lock on some table but
+ has not yet opened it and/or tried to obtain data lock on
+ it. In this case we need to wait until this happens and try
+ to abort this thread once again.
+ */
+ struct timespec abstime;
+ set_timespec(abstime, 10);
+ pthread_cond_timedwait(&COND_mdl, &LOCK_mdl, &abstime);
+ }
+ if (thd->killed)
+ {
+ /* Remove our pending lock requests from the locks. */
+ it.rewind();
+ while ((l= it++))
+ {
+ DBUG_ASSERT(l->type == MDL_EXCLUSIVE && l->state == MDL_PENDING);
+ release_lock(l);
+ /* Return lock request to its initial state. */
+ l->type= MDL_SHARED;
+ l->prio= MDL_NORMAL_PRIO;
+ l->upgradable= FALSE;
+ context->locks.remove(l);
+ }
+ /* Pending requests for shared locks can be satisfied now. */
+ pthread_cond_broadcast(&COND_mdl);
+ thd->exit_cond(old_msg);
+ return TRUE;
+ }
+ }
+ it.rewind();
+ while ((l= it++))
+ {
+ global_intention_exclusive_locks_acquired++;
+ lock_data= l->lock_data;
+ lock_data->waiting_exclusive.remove(l);
+ lock_data->active_exclusive.push_front(l);
+ l->state= MDL_ACQUIRED;
+ if (lock_data->cached_object)
+ (*lock_data->cached_object_release_hook)(lock_data->cached_object);
+ lock_data->cached_object= NULL;
+ }
+ /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+ thd->exit_cond(old_msg);
+ return FALSE;
+}
+
+
+/**
+ Upgrade a shared metadata lock to exclusive.
+
+ Used in ALTER TABLE, when a copy of the table with the
+ new definition has been constructed.
+
+ @param context Context to which shared long belongs
+ @param type Id of object type
+ @param db Name of the database
+ @param name Name of the object
+
+ @note In case of failure to upgrade locks (e.g. because upgrader
+ was killed) leaves locks in their original state (locked
+ in shared mode).
+
+ @retval FALSE Success
+ @retval TRUE Failure (thread was killed)
+*/
+
+bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type,
+ const char *db, const char *name)
+{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
+ bool signalled= FALSE;
+ MDL_LOCK *l, *lh;
+ MDL_LOCK_DATA *lock_data;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+ const char *old_msg;
+ THD *thd= context->thd;
+
+ DBUG_ENTER("mdl_upgrade_shared_lock_to_exclusive");
+ DBUG_PRINT("enter", ("db=%s name=%s", db, name));
+
+ DBUG_ASSERT(thd == current_thd);
+
+ int4store(key, type);
+ key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
+
+ safe_mutex_assert_not_owner(&LOCK_open);
+
+ pthread_mutex_lock(&LOCK_mdl);
+
+ old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table");
+
+ while ((l= it++))
+ if (l->key_length == key_length && !memcmp(l->key, key, key_length) &&
+ l->type == MDL_SHARED)
+ {
+ DBUG_PRINT("info", ("found shared lock for upgrade"));
+ DBUG_ASSERT(l->state == MDL_ACQUIRED);
+ DBUG_ASSERT(l->upgradable);
+ l->state= MDL_PENDING_UPGRADE;
+ lock_data= l->lock_data;
+ lock_data->active_shared.remove(l);
+ lock_data->active_shared_waiting_upgrade.push_front(l);
+ }
+
+ while (1)
+ {
+ DBUG_PRINT("info", ("looking at conflicting locks"));
+ it.rewind();
+ while ((l= it++))
+ {
+ if (l->state == MDL_PENDING_UPGRADE)
+ {
+ DBUG_ASSERT(l->type == MDL_SHARED);
+
+ lock_data= l->lock_data;
+
+ DBUG_ASSERT(global_shared_locks_acquired == 0 &&
+ global_intention_exclusive_locks_acquired);
+
+ if ((lh= lock_data->active_shared.head()))
+ {
+ DBUG_PRINT("info", ("found active shared locks"));
+ signalled= notify_thread_having_shared_lock(thd, lh->ctx->thd);
+ break;
+ }
+ else if (!lock_data->active_exclusive.is_empty())
+ {
+ DBUG_PRINT("info", ("found active exclusive locks"));
+ signalled= TRUE;
+ break;
+ }
+ }
+ }
+ if (!l)
+ break;
+ if (signalled)
+ pthread_cond_wait(&COND_mdl, &LOCK_mdl);
+ else
+ {
+ /*
+ Another thread obtained shared MDL-lock on some table but
+ has not yet opened it and/or tried to obtain data lock on
+ it. In this case we need to wait until this happens and try
+ to abort this thread once again.
+ */
+ struct timespec abstime;
+ set_timespec(abstime, 10);
+ DBUG_PRINT("info", ("Failed to wake-up from table-level lock ... sleeping"));
+ pthread_cond_timedwait(&COND_mdl, &LOCK_mdl, &abstime);
+ }
+ if (thd->killed)
+ {
+ it.rewind();
+ while ((l= it++))
+ if (l->state == MDL_PENDING_UPGRADE)
+ {
+ DBUG_ASSERT(l->type == MDL_SHARED);
+ l->state= MDL_ACQUIRED;
+ lock_data= l->lock_data;
+ lock_data->active_shared_waiting_upgrade.remove(l);
+ lock_data->active_shared.push_front(l);
+ }
+ /* Pending requests for shared locks can be satisfied now. */
+ pthread_cond_broadcast(&COND_mdl);
+ thd->exit_cond(old_msg);
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ it.rewind();
+ while ((l= it++))
+ if (l->state == MDL_PENDING_UPGRADE)
+ {
+ DBUG_ASSERT(l->type == MDL_SHARED);
+ lock_data= l->lock_data;
+ lock_data->active_shared_waiting_upgrade.remove(l);
+ lock_data->active_exclusive.push_front(l);
+ l->type= MDL_EXCLUSIVE;
+ l->state= MDL_ACQUIRED;
+ if (lock_data->cached_object)
+ (*lock_data->cached_object_release_hook)(lock_data->cached_object);
+ lock_data->cached_object= 0;
+ }
+
+ /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+ thd->exit_cond(old_msg);
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Try to acquire an exclusive lock on the object if there are
+ no conflicting locks.
+
+ Similar to the previous function, but returns
+ immediately without any side effect if encounters a lock
+ conflict. Otherwise takes the lock.
+
+ This function is used in CREATE TABLE ... LIKE to acquire a lock
+ on the table to be created. In this statement we don't want to
+ block and wait for the lock if the table already exists.
+
+ @param context [in] The context containing the lock request
+ @param lock [in] The lock request
+
+ @retval FALSE the lock was granted
+ @retval TRUE there were conflicting locks.
+
+ FIXME: Compared to lock_table_name_if_not_cached()
+ it gives sligthly more false negatives.
+*/
+
+bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, MDL_LOCK *l)
+{
+ MDL_LOCK_DATA *lock_data;
+
+ DBUG_ASSERT(l->type == MDL_EXCLUSIVE && l->state == MDL_PENDING);
+
+ safe_mutex_assert_not_owner(&LOCK_open);
+
+ pthread_mutex_lock(&LOCK_mdl);
+
+ if (!(lock_data= (MDL_LOCK_DATA *)my_hash_search(&mdl_locks, (uchar*)l->key,
+ l->key_length)))
+ {
+ lock_data= get_lock_data_object();
+ lock_data->active_exclusive.push_front(l);
+ lock_data->users= 1;
+ my_hash_insert(&mdl_locks, (uchar*)lock_data);
+ l->state= MDL_ACQUIRED;
+ l->lock_data= lock_data;
+ lock_data= 0;
+ global_intention_exclusive_locks_acquired++;
+ }
+ pthread_mutex_unlock(&LOCK_mdl);
+
+ /*
+ FIXME: We can't leave pending MDL_EXCLUSIVE lock request in the list since
+ for such locks we assume that they have MDL_LOCK::lock properly set.
+ Long term we should clearly define relation between lock types,
+ presence in the context lists and MDL_LOCK::lock values.
+ */
+ if (lock_data)
+ context->locks.remove(l);
+
+ return lock_data;
+}
+
+
+/**
+ Acquire global shared metadata lock.
+
+ Holding this lock will block all requests for exclusive locks
+ and shared locks which can be potentially upgraded to exclusive
+ (see MDL_LOCK::upgradable).
+
+ @param context Current metadata locking context.
+
+ @retval FALSE Success -- the lock was granted.
+ @retval TRUE Failure -- our thread was killed.
+*/
+
+bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context)
+{
+ THD *thd= context->thd;
+ const char *old_msg;
+
+ safe_mutex_assert_not_owner(&LOCK_open);
+ DBUG_ASSERT(thd == current_thd);
+ DBUG_ASSERT(!context->has_global_shared_lock);
+
+ pthread_mutex_lock(&LOCK_mdl);
+
+ global_shared_locks_pending++;
+ old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table");
+
+ while (!thd->killed && global_intention_exclusive_locks_acquired)
+ pthread_cond_wait(&COND_mdl, &LOCK_mdl);
+
+ global_shared_locks_pending--;
+ if (thd->killed)
+ {
+ /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+ thd->exit_cond(old_msg);
+ return TRUE;
+ }
+ global_shared_locks_acquired++;
+ context->has_global_shared_lock= TRUE;
+ /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+ thd->exit_cond(old_msg);
+ return FALSE;
+}
+
+
+/**
+ Wait until there will be no locks that conflict with lock requests
+ in the context.
+
+ This is a part of the locking protocol and must be used by the
+ acquirer of shared locks after a back-off.
+
+ Does not acquire the locks!
+
+ @param context Context with which lock requests are associated.
+
+ @retval FALSE Success. One can try to obtain metadata locks.
+ @retval TRUE Failure (thread was killed)
+*/
+
+bool mdl_wait_for_locks(MDL_CONTEXT *context)
+{
+ MDL_LOCK *l;
+ MDL_LOCK_DATA *lock_data;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+ const char *old_msg;
+ THD *thd= context->thd;
+
+ safe_mutex_assert_not_owner(&LOCK_open);
+ DBUG_ASSERT(thd == current_thd);
+
+ while (!thd->killed)
+ {
+ /*
+ We have to check if there are some HANDLERs open by this thread
+ which conflict with some pending exclusive locks. Otherwise we
+ might have a deadlock in situations when we are waiting for
+ pending writer to go away, which in its turn waits for HANDLER
+ open by our thread.
+
+ TODO: investigate situations in which we need to broadcast on
+ COND_mdl because of above scenario.
+ */
+ mysql_ha_flush(context->thd);
+ pthread_mutex_lock(&LOCK_mdl);
+ old_msg= thd->enter_cond(&COND_mdl, &LOCK_mdl, "Waiting for table");
+ it.rewind();
+ while ((l= it++))
+ {
+ DBUG_ASSERT(l->state == MDL_PENDING);
+ if ((l->upgradable || l->type == MDL_EXCLUSIVE) &&
+ (global_shared_locks_acquired || global_shared_locks_pending))
+ break;
+ /*
+ To avoid starvation we don't wait if we have pending MDL_EXCLUSIVE lock.
+ */
+ if (l->type == MDL_SHARED &&
+ (lock_data= (MDL_LOCK_DATA *)my_hash_search(&mdl_locks, (uchar*)l->key,
+ l->key_length)) &&
+ !(lock_data->active_exclusive.is_empty() &&
+ lock_data->active_shared_waiting_upgrade.is_empty() &&
+ lock_data->waiting_exclusive.is_empty()))
+ break;
+ }
+ if (!l)
+ {
+ pthread_mutex_unlock(&LOCK_mdl);
+ break;
+ }
+ pthread_cond_wait(&COND_mdl, &LOCK_mdl);
+ /* As a side-effect THD::exit_cond() unlocks LOCK_mdl. */
+ thd->exit_cond(old_msg);
+ }
+ return thd->killed;
+}
+
+
+/**
+ Auxiliary function which allows to release particular lock
+ ownership of which is represented by lock request object.
+*/
+
+static void release_lock(MDL_LOCK *l)
+{
+ MDL_LOCK_DATA *lock_data;
+
+ DBUG_ENTER("release_lock");
+ DBUG_PRINT("enter", ("db=%s name=%s", l->key + 4,
+ l->key + 4 + strlen(l->key + 4) + 1));
+
+ lock_data= l->lock_data;
+ if (lock_data->has_no_other_users())
+ {
+ my_hash_delete(&mdl_locks, (uchar *)lock_data);
+ DBUG_PRINT("info", ("releasing cached_object cached_object=%p",
+ lock_data->cached_object));
+ if (lock_data->cached_object)
+ (*lock_data->cached_object_release_hook)(lock_data->cached_object);
+ release_lock_data_object(lock_data);
+ if (l->type == MDL_EXCLUSIVE && l->state == MDL_ACQUIRED ||
+ l->type == MDL_SHARED && l->state == MDL_ACQUIRED && l->upgradable)
+ global_intention_exclusive_locks_acquired--;
+ }
+ else
+ {
+ switch (l->type)
+ {
+ case MDL_SHARED:
+ lock_data->active_shared.remove(l);
+ if (l->upgradable)
+ global_intention_exclusive_locks_acquired--;
+ break;
+ case MDL_EXCLUSIVE:
+ if (l->state == MDL_PENDING)
+ lock_data->waiting_exclusive.remove(l);
+ else
+ {
+ lock_data->active_exclusive.remove(l);
+ global_intention_exclusive_locks_acquired--;
+ }
+ break;
+ default:
+ /* TODO Really? How about problems during lock upgrade ? */
+ DBUG_ASSERT(0);
+ }
+ lock_data->users--;
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Release all locks associated with the context, but leave them
+ in the context as lock requests.
+
+ This function is used to back off in case of a lock conflict.
+ It is also used to release shared locks in the end of an SQL
+ statement.
+
+ @param context The context with which the locks to be released
+ are associated.
+*/
+
+void mdl_release_locks(MDL_CONTEXT *context)
+{
+ MDL_LOCK *l;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+ DBUG_ENTER("mdl_release_locks");
+
+ safe_mutex_assert_not_owner(&LOCK_open);
+
+ pthread_mutex_lock(&LOCK_mdl);
+ while ((l= it++))
+ {
+ DBUG_PRINT("info", ("found lock to release l=%p", l));
+ /*
+ We should not release locks which pending shared locks as these
+ are not associated with lock object and don't present in its
+ lists. Allows us to avoid problems in open_tables() in case of
+ back-off
+ */
+ if (!(l->type == MDL_SHARED && l->state == MDL_PENDING))
+ {
+ release_lock(l);
+ l->state= MDL_PENDING;
+#ifndef DBUG_OFF
+ l->lock_data= 0;
+#endif
+ }
+ /*
+ We will return lock request to its initial state only in
+ mdl_remove_all_locks() since we need to know type of lock
+ request and if it is upgradable in mdl_wait_for_locks().
+ */
+ }
+ /* Inefficient but will do for a while */
+ pthread_cond_broadcast(&COND_mdl);
+ pthread_mutex_unlock(&LOCK_mdl);
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Release all exclusive locks associated with context.
+ Removes the locks from the context.
+
+ @param context Context with exclusive locks.
+
+ @note Shared locks are left intact.
+ @note Resets lock requests for locks released back to their
+ initial state (i.e.sets type and priority to MDL_SHARED
+ and MDL_NORMAL_PRIO).
+*/
+
+void mdl_release_exclusive_locks(MDL_CONTEXT *context)
+{
+ MDL_LOCK *l;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+
+ safe_mutex_assert_not_owner(&LOCK_open);
+
+ pthread_mutex_lock(&LOCK_mdl);
+ while ((l= it++))
+ {
+ if (l->type == MDL_EXCLUSIVE)
+ {
+ DBUG_ASSERT(l->state == MDL_ACQUIRED);
+ release_lock(l);
+#ifndef DBUG_OFF
+ l->ctx= 0;
+ l->lock_data= 0;
+#endif
+ l->state= MDL_PENDING;
+ /* Return lock request to its initial state. */
+ l->type= MDL_SHARED;
+ l->prio= MDL_NORMAL_PRIO;
+ l->upgradable= FALSE;
+ context->locks.remove(l);
+ }
+ }
+ pthread_cond_broadcast(&COND_mdl);
+ pthread_mutex_unlock(&LOCK_mdl);
+}
+
+
+/**
+ Release a lock.
+ Removes the lock from the context.
+
+ @param context Context containing lock in question
+ @param lock Lock to be released
+
+ @note Resets lock request for lock released back to its initial state
+ (i.e.sets type and priority to MDL_SHARED and MDL_NORMAL_PRIO).
+*/
+
+void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK *lr)
+{
+ safe_mutex_assert_not_owner(&LOCK_open);
+
+ pthread_mutex_lock(&LOCK_mdl);
+ release_lock(lr);
+#ifndef DBUG_OFF
+ lr->ctx= 0;
+ lr->lock_data= 0;
+#endif
+ lr->state= MDL_PENDING;
+ /* Return lock request to its initial state. */
+ lr->type= MDL_SHARED;
+ lr->prio= MDL_NORMAL_PRIO;
+ lr->upgradable= FALSE;
+ context->locks.remove(lr);
+ pthread_cond_broadcast(&COND_mdl);
+ pthread_mutex_unlock(&LOCK_mdl);
+}
+
+
+/**
+ Downgrade all exclusive locks in the context to
+ shared.
+
+ @param context A context with exclusive locks.
+*/
+
+void mdl_downgrade_exclusive_locks(MDL_CONTEXT *context)
+{
+ MDL_LOCK *l;
+ MDL_LOCK_DATA *lock_data;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+
+ safe_mutex_assert_not_owner(&LOCK_open);
+
+ pthread_mutex_lock(&LOCK_mdl);
+ while ((l= it++))
+ if (l->type == MDL_EXCLUSIVE)
+ {
+ DBUG_ASSERT(l->state == MDL_ACQUIRED);
+ if (!l->upgradable)
+ global_intention_exclusive_locks_acquired--;
+ lock_data= l->lock_data;
+ lock_data->active_exclusive.remove(l);
+ l->type= MDL_SHARED;
+ lock_data->active_shared.push_front(l);
+ }
+ pthread_cond_broadcast(&COND_mdl);
+ pthread_mutex_unlock(&LOCK_mdl);
+}
+
+
+/**
+ Release global shared metadata lock.
+
+ @param context Current context
+*/
+
+void mdl_release_global_shared_lock(MDL_CONTEXT *context)
+{
+ safe_mutex_assert_not_owner(&LOCK_open);
+ DBUG_ASSERT(context->has_global_shared_lock);
+
+ pthread_mutex_lock(&LOCK_mdl);
+ global_shared_locks_acquired--;
+ context->has_global_shared_lock= FALSE;
+ pthread_cond_broadcast(&COND_mdl);
+ pthread_mutex_unlock(&LOCK_mdl);
+}
+
+
+/**
+ Auxiliary function which allows to check if we have exclusive lock
+ on the object.
+
+ @param context Current context
+ @param type Id of object type
+ @param db Name of the database
+ @param name Name of the object
+
+ @return TRUE if current context contains exclusive lock for the object,
+ FALSE otherwise.
+*/
+
+bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type,
+ const char *db, const char *name)
+{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
+ MDL_LOCK *l;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+
+ int4store(key, type);
+ key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
+
+ while ((l= it++) && (l->key_length != key_length || memcmp(l->key, key, key_length)))
+ continue;
+ return (l && l->type == MDL_EXCLUSIVE && l->state == MDL_ACQUIRED);
+}
+
+
+/**
+ Auxiliary function which allows to check if we some kind of lock on
+ the object.
+
+ @param context Current context
+ @param type Id of object type
+ @param db Name of the database
+ @param name Name of the object
+
+ @return TRUE if current context contains satisfied lock for the object,
+ FALSE otherwise.
+*/
+
+bool mdl_is_lock_owner(MDL_CONTEXT *context, int type, const char *db,
+ const char *name)
+{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
+ MDL_LOCK *l;
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it(context->locks);
+
+ int4store(key, type);
+ key_length= (uint) (strmov(strmov(key+4, db)+1, name)-key)+1;
+
+ while ((l= it++) && (l->key_length != key_length ||
+ memcmp(l->key, key, key_length) ||
+ l->state == MDL_PENDING))
+ continue;
+
+ return l;
+}
+
+
+/**
+ Check if we have any pending exclusive locks which conflict with
+ existing shared lock.
+
+ @param l Shared lock against which check should be performed.
+
+ @return TRUE if there are any conflicting locks, FALSE otherwise.
+*/
+
+bool mdl_has_pending_conflicting_lock(MDL_LOCK *l)
+{
+ bool result;
+
+ DBUG_ASSERT(l->type == MDL_SHARED && l->state == MDL_ACQUIRED);
+ safe_mutex_assert_not_owner(&LOCK_open);
+
+ pthread_mutex_lock(&LOCK_mdl);
+ result= !(l->lock_data->waiting_exclusive.is_empty() &&
+ l->lock_data->active_shared_waiting_upgrade.is_empty());
+ pthread_mutex_unlock(&LOCK_mdl);
+ return result;
+}
+
+
+/**
+ Associate pointer to an opaque object with a lock.
+
+ @param l Lock request for the lock with which the
+ object should be associated.
+ @param cached_object Pointer to the object
+ @param release_hook Cleanup function to be called when MDL subsystem
+ decides to remove lock or associate another object.
+
+ This is used to cache a pointer to TABLE_SHARE in the lock
+ structure. Such caching can save one acquisition of LOCK_open
+ and one table definition cache lookup for every table.
+
+ Since the pointer may be stored only inside an acquired lock,
+ the caching is only effective when there is more than one lock
+ granted on a given table.
+
+ This function has the following usage pattern:
+ - try to acquire an MDL lock
+ - when done, call for mdl_get_cached_object(). If it returns NULL, our
+ thread has the only lock on this table.
+ - look up TABLE_SHARE in the table definition cache
+ - call mdl_set_cache_object() to assign the share to the opaque pointer.
+
+ The release hook is invoked when the last shared metadata
+ lock on this name is released.
+*/
+
+void mdl_set_cached_object(MDL_LOCK *l, void *cached_object,
+ mdl_cached_object_release_hook release_hook)
+{
+ DBUG_ENTER("mdl_set_cached_object");
+ DBUG_PRINT("enter", ("db=%s name=%s cached_object=%p", l->key + 4,
+ l->key + 4 + strlen(l->key + 4) + 1,
+ cached_object));
+
+ DBUG_ASSERT(l->state == MDL_ACQUIRED || l->state == MDL_PENDING_UPGRADE);
+
+ /*
+ TODO: This assumption works now since we do mdl_get_cached_object()
+ and mdl_set_cached_object() in the same critical section. Once
+ this becomes false we will have to call release_hook here and
+ use additional mutex protecting 'cached_object' member.
+ */
+ DBUG_ASSERT(!l->lock_data->cached_object);
+
+ l->lock_data->cached_object= cached_object;
+ l->lock_data->cached_object_release_hook= release_hook;
+
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ Get a pointer to an opaque object that associated with the lock.
+
+ @param l Lock request for the lock with which the object is
+ associated.
+
+ @return Pointer to an opaque object associated with the lock.
+*/
+
+void* mdl_get_cached_object(MDL_LOCK *l)
+{
+ DBUG_ASSERT(l->state == MDL_ACQUIRED || l->state == MDL_PENDING_UPGRADE);
+ return l->lock_data->cached_object;
+}
diff --git a/sql/mdl.h b/sql/mdl.h
new file mode 100644
index 00000000000..ca3d8d0a784
--- /dev/null
+++ b/sql/mdl.h
@@ -0,0 +1,260 @@
+#ifndef MDL_H
+#define MDL_H
+/* Copyright (C) 2007-2008 MySQL AB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+#include "sql_plist.h"
+#include <my_sys.h>
+#include <m_string.h>
+
+class THD;
+
+struct MDL_LOCK;
+struct MDL_LOCK_DATA;
+struct MDL_CONTEXT;
+
+/** Type of metadata lock request. */
+
+enum enum_mdl_type {MDL_SHARED=0, MDL_EXCLUSIVE};
+
+
+/** States which metadata lock request can have. */
+
+enum enum_mdl_state {MDL_PENDING=0, MDL_ACQUIRED, MDL_PENDING_UPGRADE};
+
+
+/**
+ Priority of metadata lock requests. High priority attribute is
+ applicable only to requests for shared locks and indicates that
+ such request should ignore pending requests for exclusive locks
+ and for upgrading of shared locks to exclusive.
+*/
+
+enum enum_mdl_prio {MDL_NORMAL_PRIO=0, MDL_HIGH_PRIO};
+
+
+/**
+ A pending lock request or a granted metadata lock. A lock is requested
+ or granted based on a fully qualified name and type. E.g. for a table
+ the key consists of <0 (=table)>+<database name>+<table name>.
+ Later in this document this triple will be referred to simply as
+ "key" or "name".
+*/
+
+struct MDL_LOCK
+{
+ char *key;
+ uint key_length;
+ enum enum_mdl_type type;
+ enum enum_mdl_state state;
+ enum enum_mdl_prio prio;
+ /**
+ TRUE -- if shared lock corresponding to this lock request at some
+ point might be upgraded to an exclusive lock and therefore conflicts
+ with global shared lock, FALSE -- otherwise.
+ */
+ bool upgradable;
+
+private:
+ /**
+ Pointers for participating in the list of lock requests for this context.
+ */
+ MDL_LOCK *next_context;
+ MDL_LOCK **prev_context;
+ /**
+ Pointers for participating in the list of satisfied/pending requests
+ for the lock.
+ */
+ MDL_LOCK *next_lock;
+ MDL_LOCK **prev_lock;
+
+ friend struct MDL_LOCK_context;
+ friend struct MDL_LOCK_lock;
+
+public:
+ /*
+ Pointer to the lock object for this lock request. Valid only if this lock
+ request is satisified or is present in the list of pending lock requests
+ for particular lock.
+ */
+ MDL_LOCK_DATA *lock_data;
+ MDL_CONTEXT *ctx;
+};
+
+
+/**
+ Helper class which specifies which members of MDL_LOCK are used for
+ participation in the list lock requests belonging to one context.
+*/
+
+struct MDL_LOCK_context
+{
+ static inline MDL_LOCK **next_ptr(MDL_LOCK *l)
+ {
+ return &l->next_context;
+ }
+ static inline MDL_LOCK ***prev_ptr(MDL_LOCK *l)
+ {
+ return &l->prev_context;
+ }
+};
+
+
+/**
+ Helper class which specifies which members of MDL_LOCK are used for
+ participation in the list of satisfied/pending requests for the lock.
+*/
+
+struct MDL_LOCK_lock
+{
+ static inline MDL_LOCK **next_ptr(MDL_LOCK *l)
+ {
+ return &l->next_lock;
+ }
+ static inline MDL_LOCK ***prev_ptr(MDL_LOCK *l)
+ {
+ return &l->prev_lock;
+ }
+};
+
+
+/**
+ Context of the owner of metadata locks. I.e. each server
+ connection has such a context.
+*/
+
+struct MDL_CONTEXT
+{
+ I_P_List <MDL_LOCK, MDL_LOCK_context> locks;
+ bool has_global_shared_lock;
+ THD *thd;
+};
+
+
+void mdl_init();
+void mdl_destroy();
+
+void mdl_context_init(MDL_CONTEXT *context, THD *thd);
+void mdl_context_destroy(MDL_CONTEXT *context);
+void mdl_context_backup_and_reset(MDL_CONTEXT *ctx, MDL_CONTEXT *backup);
+void mdl_context_restore(MDL_CONTEXT *ctx, MDL_CONTEXT *backup);
+void mdl_context_merge(MDL_CONTEXT *target, MDL_CONTEXT *source);
+
+void mdl_init_lock(MDL_LOCK *mdl, char *key, int type, const char *db,
+ const char *name);
+MDL_LOCK *mdl_alloc_lock(int type, const char *db, const char *name,
+ MEM_ROOT *root);
+void mdl_add_lock(MDL_CONTEXT *context, MDL_LOCK *lock);
+void mdl_remove_all_locks(MDL_CONTEXT *context);
+
+/**
+ Set type of lock request. Can be only applied to pending locks.
+*/
+
+inline void mdl_set_lock_type(MDL_LOCK *lock, enum_mdl_type lock_type)
+{
+ DBUG_ASSERT(lock->state == MDL_PENDING);
+ lock->type= lock_type;
+}
+
+/**
+ Set priority for lock request. High priority can be only set
+ for shared locks.
+*/
+
+inline void mdl_set_lock_priority(MDL_LOCK *lock, enum_mdl_prio prio)
+{
+ DBUG_ASSERT(lock->type == MDL_SHARED && lock->state == MDL_PENDING);
+ lock->prio= prio;
+}
+
+/**
+ Mark request for shared lock as upgradable. Can be only applied
+ to pending locks.
+*/
+
+inline void mdl_set_upgradable(MDL_LOCK *lock)
+{
+ DBUG_ASSERT(lock->type == MDL_SHARED && lock->state == MDL_PENDING);
+ lock->upgradable= TRUE;
+}
+
+bool mdl_acquire_shared_lock(MDL_LOCK *l, bool *retry);
+bool mdl_acquire_exclusive_locks(MDL_CONTEXT *context);
+bool mdl_upgrade_shared_lock_to_exclusive(MDL_CONTEXT *context, int type,
+ const char *db, const char *name);
+bool mdl_try_acquire_exclusive_lock(MDL_CONTEXT *context, MDL_LOCK *lock);
+bool mdl_acquire_global_shared_lock(MDL_CONTEXT *context);
+
+bool mdl_wait_for_locks(MDL_CONTEXT *context);
+
+void mdl_release_locks(MDL_CONTEXT *context);
+void mdl_release_exclusive_locks(MDL_CONTEXT *context);
+void mdl_release_lock(MDL_CONTEXT *context, MDL_LOCK *lock);
+void mdl_downgrade_exclusive_locks(MDL_CONTEXT *context);
+void mdl_release_global_shared_lock(MDL_CONTEXT *context);
+
+bool mdl_is_exclusive_lock_owner(MDL_CONTEXT *context, int type, const char *db,
+ const char *name);
+bool mdl_is_lock_owner(MDL_CONTEXT *context, int type, const char *db,
+ const char *name);
+
+bool mdl_has_pending_conflicting_lock(MDL_LOCK *l);
+
+inline bool mdl_has_locks(MDL_CONTEXT *context)
+{
+ return !context->locks.is_empty();
+}
+
+
+/**
+ Get iterator for walking through all lock requests in the context.
+*/
+
+inline I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> mdl_get_locks(MDL_CONTEXT *ctx)
+{
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> result(ctx->locks);
+ return result;
+}
+
+/**
+ Give metadata lock request object for the table get table definition
+ cache key corresponding to it.
+
+ @param l [in] Lock request object for the table.
+ @param key [out] LEX_STRING object where table definition cache key
+ should be put.
+
+ @note This key will have the same life-time as this lock request object.
+
+ @note This is yet another place where border between MDL subsystem and the
+ rest of the server is broken. OTOH it allows to save some CPU cycles
+ and memory by avoiding generating these TDC keys from table list.
+*/
+
+inline void mdl_get_tdc_key(MDL_LOCK *l, LEX_STRING *key)
+{
+ key->str= l->key + 4;
+ key->length= l->key_length - 4;
+}
+
+
+typedef void (* mdl_cached_object_release_hook)(void *);
+void* mdl_get_cached_object(MDL_LOCK *l);
+void mdl_set_cached_object(MDL_LOCK *l, void *cached_object,
+ mdl_cached_object_release_hook release_hook);
+
+#endif
diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h
index c6454679e28..be03759b383 100644
--- a/sql/mysql_priv.h
+++ b/sql/mysql_priv.h
@@ -805,7 +805,7 @@ extern my_decimal decimal_zero;
void free_items(Item *item);
void cleanup_items(Item *item);
class THD;
-void close_thread_tables(THD *thd);
+void close_thread_tables(THD *thd, bool skip_mdl= 0);
#ifndef NO_EMBEDDED_ACCESS_CHECKS
bool check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *tables);
@@ -993,7 +993,6 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
bool drop_temporary, bool drop_view, bool log_query);
bool quick_rm_table(handlerton *base,const char *db,
const char *table_name, uint flags);
-void close_cached_table(THD *thd, TABLE *table);
bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent);
bool do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db,
char *new_table_name, char *new_table_alias,
@@ -1034,10 +1033,8 @@ bool check_dup(const char *db, const char *name, TABLE_LIST *tables);
bool compare_record(TABLE *table);
bool append_file_to_dir(THD *thd, const char **filename_ptr,
const char *table_name);
-void wait_while_table_is_used(THD *thd, TABLE *table,
+bool wait_while_table_is_used(THD *thd, TABLE *table,
enum ha_extra_function function);
-bool table_cache_init(void);
-void table_cache_free(void);
bool table_def_init(void);
void table_def_free(void);
void assign_new_table_id(TABLE_SHARE *share);
@@ -1223,28 +1220,30 @@ void release_table_share(TABLE_SHARE *share, enum release_type type);
TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name);
TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update,
uint lock_flags);
+enum enum_open_table_action {OT_NO_ACTION= 0, OT_BACK_OFF_AND_RETRY,
+ OT_DISCOVER, OT_REPAIR};
TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem,
- bool *refresh, uint flags);
+ enum_open_table_action *action, uint flags);
+bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias,
+ char *cache_key, uint cache_key_length,
+ MEM_ROOT *mem_root, uint flags);
bool name_lock_locked_table(THD *thd, TABLE_LIST *tables);
-bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in);
-TABLE *table_cache_insert_placeholder(THD *thd, const char *key,
- uint key_length);
-bool lock_table_name_if_not_cached(THD *thd, const char *db,
- const char *table_name, TABLE **table);
-TABLE *find_locked_table(THD *thd, const char *db,const char *table_name);
+bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list);
+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);
bool reopen_table(TABLE *table);
-bool reopen_tables(THD *thd,bool get_locks,bool in_refresh);
+bool reopen_tables(THD *thd, bool get_locks);
thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table);
void close_data_files_and_morph_locks(THD *thd, const char *db,
const char *table_name);
void close_handle_and_leave_table_as_lock(TABLE *table);
bool wait_for_tables(THD *thd);
bool table_is_used(TABLE *table, bool wait_for_name_lock);
-TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name);
-void abort_locked_tables(THD *thd,const char *db, const char *table_name);
+void unlock_locked_tables(THD *thd);
void execute_init_command(THD *thd, sys_var_str *init_command_var,
rw_lock_t *var_mutex);
extern Field *not_found_field;
@@ -1364,7 +1363,7 @@ bool mysql_ha_close(THD *thd, TABLE_LIST *tables);
bool mysql_ha_read(THD *, TABLE_LIST *,enum enum_ha_read_modes,char *,
List<Item> *,enum ha_rkey_function,Item *,ha_rows,ha_rows);
void mysql_ha_flush(THD *thd);
-void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables, bool is_locked);
+void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables);
void mysql_ha_cleanup(THD *thd);
/* sql_base.cc */
@@ -1501,7 +1500,7 @@ void free_io_cache(TABLE *entry);
void intern_close_table(TABLE *entry);
bool close_thread_table(THD *thd, TABLE **table_ptr);
void close_temporary_tables(THD *thd);
-void close_tables_for_reopen(THD *thd, TABLE_LIST **tables);
+void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, bool skip_mdl);
TABLE_LIST *find_table_in_list(TABLE_LIST *table,
TABLE_LIST *TABLE_LIST::*link,
const char *db_name,
@@ -1550,6 +1549,9 @@ char *generate_partition_syntax(partition_info *part_info,
#define RTFC_CHECK_KILLED_FLAG 0x0004
bool remove_table_from_cache(THD *thd, const char *db, const char *table,
uint flags);
+bool notify_thread_having_shared_lock(THD *thd, THD *in_use);
+void expel_table_from_cache(THD *leave_thd, const char *db,
+ const char *table_name);
#define NORMAL_PART_NAME 0
#define TEMP_PART_NAME 1
@@ -1681,7 +1683,7 @@ TABLE *open_performance_schema_table(THD *thd, TABLE_LIST *one_table,
void close_performance_schema_table(THD *thd, Open_tables_state *backup);
bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
- bool wait_for_refresh, bool wait_for_placeholders);
+ bool wait_for_refresh);
bool close_cached_connection_tables(THD *thd, bool wait_for_refresh,
LEX_STRING *connect_string,
bool have_lock = FALSE);
@@ -2001,8 +2003,9 @@ extern const char *opt_date_time_formats[];
extern KNOWN_DATE_TIME_FORMAT known_date_time_formats[];
extern String null_string;
-extern HASH open_cache, lock_db_cache;
+extern HASH table_def_cache, lock_db_cache;
extern TABLE *unused_tables;
+extern uint table_cache_count;
extern const char* any_db;
extern struct my_option my_long_options[];
extern const LEX_STRING view_type;
@@ -2070,18 +2073,8 @@ void unset_protect_against_global_read_lock(void);
void broadcast_refresh(void);
/* Lock based on name */
-int lock_and_wait_for_table_name(THD *thd, TABLE_LIST *table_list);
-int lock_table_name(THD *thd, TABLE_LIST *table_list, bool check_in_use);
-void unlock_table_name(THD *thd, TABLE_LIST *table_list);
-bool wait_for_locked_table_names(THD *thd, TABLE_LIST *table_list);
bool lock_table_names(THD *thd, TABLE_LIST *table_list);
-void unlock_table_names(THD *thd, TABLE_LIST *table_list,
- TABLE_LIST *last_table);
-bool lock_table_names_exclusively(THD *thd, TABLE_LIST *table_list);
-bool is_table_name_exclusively_locked_by_this_thread(THD *thd,
- TABLE_LIST *table_list);
-bool is_table_name_exclusively_locked_by_this_thread(THD *thd, uchar *key,
- int key_length);
+void unlock_table_names(THD *thd);
/* old unireg functions */
diff --git a/sql/mysqld.cc b/sql/mysqld.cc
index 1cab245b317..90924b5b662 100644
--- a/sql/mysqld.cc
+++ b/sql/mysqld.cc
@@ -1301,9 +1301,9 @@ void clean_up(bool print_message)
grant_free();
#endif
query_cache_destroy();
- table_cache_free();
table_def_free();
hostname_cache_free();
+ mdl_destroy();
item_user_lock_free();
lex_free(); /* Free some memory */
item_create_cleanup();
@@ -3755,7 +3755,8 @@ static int init_server_components()
We need to call each of these following functions to ensure that
all things are initialized so that unireg_abort() doesn't fail
*/
- if (table_cache_init() | table_def_init() | hostname_cache_init())
+ mdl_init();
+ if (table_def_init() | hostname_cache_init())
unireg_abort(1);
query_cache_result_size_limit(query_cache_limit);
diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc
index ae3cbf789a5..42cd12bd66c 100644
--- a/sql/rpl_rli.cc
+++ b/sql/rpl_rli.cc
@@ -1187,8 +1187,7 @@ void Relay_log_info::cleanup_context(THD *thd, bool error)
end_trans(thd, ROLLBACK); // if a "real transaction"
}
m_table_map.clear_tables();
- close_thread_tables(thd);
- clear_tables_to_lock();
+ slave_close_thread_tables(thd);
clear_flag(IN_STMT);
/*
Cleanup for the flags that have been set at do_apply_event.
@@ -1200,6 +1199,13 @@ void Relay_log_info::cleanup_context(THD *thd, bool error)
void Relay_log_info::clear_tables_to_lock()
{
+ /*
+ Deallocating elements of table list below will also free memory where
+ meta-data locks are stored. So we want to be sure that we don't have
+ any references to this memory left.
+ */
+ DBUG_ASSERT(!mdl_has_locks(&(current_thd->mdl_context)));
+
while (tables_to_lock)
{
uchar* to_free= reinterpret_cast<uchar*>(tables_to_lock);
@@ -1216,4 +1222,15 @@ void Relay_log_info::clear_tables_to_lock()
DBUG_ASSERT(tables_to_lock == NULL && tables_to_lock_count == 0);
}
+void Relay_log_info::slave_close_thread_tables(THD *thd)
+{
+ /*
+ Since we use same memory chunks for allocation of metadata lock
+ objects for tables as we use for allocating corresponding elements
+ of 'tables_to_lock' list, we have to release metadata locks by
+ closing tables before calling clear_tables_to_lock().
+ */
+ close_thread_tables(thd);
+ clear_tables_to_lock();
+}
#endif
diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h
index fd36d18adae..332bc4e53d0 100644
--- a/sql/rpl_rli.h
+++ b/sql/rpl_rli.h
@@ -346,6 +346,7 @@ public:
bool cached_charset_compare(char *charset) const;
void cleanup_context(THD *, bool);
+ void slave_close_thread_tables(THD *);
void clear_tables_to_lock();
/*
diff --git a/sql/set_var.cc b/sql/set_var.cc
index 1028e5441ae..1490ffd9598 100644
--- a/sql/set_var.cc
+++ b/sql/set_var.cc
@@ -4333,7 +4333,7 @@ bool sys_var_opt_readonly::update(THD *thd, set_var *var)
can cause to wait on a read lock, it's required for the client application
to unlock everything, and acceptable for the server to wait on all locks.
*/
- if ((result= close_cached_tables(thd, NULL, FALSE, TRUE, TRUE)))
+ if ((result= close_cached_tables(thd, NULL, FALSE, TRUE)))
goto end_with_read_lock;
if ((result= make_global_read_lock_block_commit(thd)))
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index f90aefc2a3f..14573cd6884 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -3984,6 +3984,9 @@ sp_head::add_used_tables_to_table_list(THD *thd,
table->prelocking_placeholder= 1;
table->belong_to_view= belong_to_view;
table->trg_event_map= stab->trg_event_map;
+ table->mdl_lock= mdl_alloc_lock(0, table->db, table->table_name,
+ thd->mdl_el_root ? thd->mdl_el_root :
+ thd->mem_root);
/* Everyting else should be zeroed */
@@ -4025,7 +4028,9 @@ sp_add_to_query_tables(THD *thd, LEX *lex,
table->lock_type= locktype;
table->select_lex= lex->current_select;
table->cacheable_table= 1;
-
+ table->mdl_lock= mdl_alloc_lock(0, table->db, table->table_name,
+ thd->mdl_el_root ? thd->mdl_el_root :
+ thd->mem_root);
lex->add_to_query_tables(table);
return table;
}
diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc
index 641423605aa..b01e7b1049d 100644
--- a/sql/sql_acl.cc
+++ b/sql/sql_acl.cc
@@ -676,12 +676,7 @@ my_bool acl_reload(THD *thd)
my_bool return_val= 1;
DBUG_ENTER("acl_reload");
- if (thd->locked_tables)
- { // Can't have locked tables here
- thd->lock=thd->locked_tables;
- thd->locked_tables=0;
- close_thread_tables(thd);
- }
+ unlock_locked_tables(thd); // Can't have locked tables here
/*
To avoid deadlocks we should obtain table locks before
@@ -697,6 +692,7 @@ my_bool acl_reload(THD *thd)
tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_READ;
tables[0].skip_temporary= tables[1].skip_temporary=
tables[2].skip_temporary= TRUE;
+ alloc_mdl_locks(tables, thd->mem_root);
if (simple_open_n_lock_tables(thd, tables))
{
@@ -1601,6 +1597,7 @@ bool change_password(THD *thd, const char *host, const char *user,
bzero((char*) &tables, sizeof(tables));
tables.alias= tables.table_name= (char*) "user";
tables.db= (char*) "mysql";
+ alloc_mdl_locks(&tables, thd->mem_root);
#ifdef HAVE_REPLICATION
/*
@@ -3053,6 +3050,7 @@ int mysql_table_grant(THD *thd, TABLE_LIST *table_list,
? tables+2 : 0);
tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_WRITE;
tables[0].db=tables[1].db=tables[2].db=(char*) "mysql";
+ alloc_mdl_locks(tables, thd->mem_root);
/*
This statement will be replicated as a statement, even when using
@@ -3270,6 +3268,7 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, bool is_proc,
tables[0].next_local= tables[0].next_global= tables+1;
tables[0].lock_type=tables[1].lock_type=TL_WRITE;
tables[0].db=tables[1].db=(char*) "mysql";
+ alloc_mdl_locks(tables, thd->mem_root);
/*
This statement will be replicated as a statement, even when using
@@ -3408,6 +3407,7 @@ bool mysql_grant(THD *thd, const char *db, List <LEX_USER> &list,
tables[0].next_local= tables[0].next_global= tables+1;
tables[0].lock_type=tables[1].lock_type=TL_WRITE;
tables[0].db=tables[1].db=(char*) "mysql";
+ alloc_mdl_locks(tables, thd->mem_root);
/*
This statement will be replicated as a statement, even when using
@@ -3741,6 +3741,7 @@ static my_bool grant_reload_procs_priv(THD *thd)
table.db= (char *) "mysql";
table.lock_type= TL_READ;
table.skip_temporary= 1;
+ alloc_mdl_locks(&table, thd->mem_root);
if (simple_open_n_lock_tables(thd, &table))
{
@@ -3807,6 +3808,8 @@ my_bool grant_reload(THD *thd)
tables[0].next_local= tables[0].next_global= tables+1;
tables[0].lock_type= tables[1].lock_type= TL_READ;
tables[0].skip_temporary= tables[1].skip_temporary= TRUE;
+ alloc_mdl_locks(tables, thd->mem_root);
+
/*
To avoid deadlocks we should obtain table locks before
obtaining LOCK_grant rwlock.
@@ -5152,6 +5155,7 @@ int open_grant_tables(THD *thd, TABLE_LIST *tables)
(tables+4)->lock_type= TL_WRITE;
tables->db= (tables+1)->db= (tables+2)->db=
(tables+3)->db= (tables+4)->db= (char*) "mysql";
+ alloc_mdl_locks(tables, thd->mem_root);
#ifdef HAVE_REPLICATION
/*
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index 8f31ef6999a..de4aaac633e 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -96,17 +96,32 @@ bool Prelock_error_handler::safely_trapped_errors()
@defgroup Data_Dictionary Data Dictionary
@{
*/
-TABLE *unused_tables; /* Used by mysql_test */
-HASH open_cache; /* Used by mysql_test */
-static HASH table_def_cache;
+
+/**
+ Total number of TABLE instances for tables in the table definition cache
+ (both in use by threads and not in use). This value is accessible to user
+ as "Open_tables" status variable.
+*/
+uint table_cache_count= 0;
+/**
+ List that contains all TABLE instances for tables in the table definition
+ cache that are not in use by any thread. Recently used TABLE instances are
+ appended to the end of the list. Thus the beginning of the list contains
+ tables which have been least recently used.
+*/
+TABLE *unused_tables;
+HASH table_def_cache;
static TABLE_SHARE *oldest_unused_share, end_of_unused_share;
static pthread_mutex_t LOCK_table_share;
static bool table_def_inited= 0;
-static int open_unireg_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list,
- const char *alias,
- char *cache_key, uint cache_key_length,
- MEM_ROOT *mem_root, uint flags);
+static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables,
+ TABLE_SHARE *table_share);
+static bool reopen_table_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list,
+ const char *alias, char *cache_key,
+ uint cache_key_length);
+static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry);
+static bool auto_repair_table(THD *thd, TABLE_LIST *table_list);
static void free_cache_entry(TABLE *entry);
static bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias,
uint db_stat, uint prgflag,
@@ -114,41 +129,14 @@ static bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias,
TABLE_LIST *table_desc, MEM_ROOT *mem_root);
static void close_old_data_files(THD *thd, TABLE *table, bool morph_locks,
bool send_refresh);
+static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context);
static bool
has_write_table_with_auto_increment(TABLE_LIST *tables);
-extern "C" uchar *table_cache_key(const uchar *record, size_t *length,
- my_bool not_used __attribute__((unused)))
-{
- TABLE *entry=(TABLE*) record;
- *length= entry->s->table_cache_key.length;
- return (uchar*) entry->s->table_cache_key.str;
-}
-
-
-bool table_cache_init(void)
-{
- return my_hash_init(&open_cache, &my_charset_bin, table_cache_size+16,
- 0, 0, table_cache_key,
- (my_hash_free_key) free_cache_entry, 0) != 0;
-}
-
-void table_cache_free(void)
-{
- DBUG_ENTER("table_cache_free");
- if (table_def_inited)
- {
- close_cached_tables(NULL, NULL, FALSE, FALSE, FALSE);
- if (!open_cache.records) // Safety first
- my_hash_free(&open_cache);
- }
- DBUG_VOID_RETURN;
-}
-
uint cached_open_tables(void)
{
- return open_cache.records;
+ return table_cache_count;
}
@@ -156,7 +144,8 @@ uint cached_open_tables(void)
static void check_unused(void)
{
uint count= 0, open_files= 0, idx= 0;
- TABLE *cur_link,*start_link;
+ TABLE *cur_link, *start_link, *entry;
+ TABLE_SHARE *share;
if ((start_link=cur_link=unused_tables))
{
@@ -167,45 +156,42 @@ static void check_unused(void)
DBUG_PRINT("error",("Unused_links aren't linked properly")); /* purecov: inspected */
return; /* purecov: inspected */
}
- } while (count++ < open_cache.records &&
+ } while (count++ < table_cache_count &&
(cur_link=cur_link->next) != start_link);
if (cur_link != start_link)
{
DBUG_PRINT("error",("Unused_links aren't connected")); /* purecov: inspected */
}
}
- for (idx=0 ; idx < open_cache.records ; idx++)
+ for (idx=0 ; idx < table_def_cache.records ; idx++)
{
- TABLE *entry=(TABLE*) my_hash_element(&open_cache,idx);
- if (!entry->in_use)
+ share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx);
+
+ I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables);
+ while ((entry= it++))
+ {
+ if (entry->in_use)
+ {
+ DBUG_PRINT("error",("Used table is in share's list of unused tables")); /* purecov: inspected */
+ }
count--;
- if (entry->file)
open_files++;
+ }
+ it.init(share->used_tables);
+ while ((entry= it++))
+ {
+ if (!entry->in_use)
+ {
+ DBUG_PRINT("error",("Unused table is in share's list of used tables")); /* purecov: inspected */
+ }
+ open_files++;
+ }
}
if (count != 0)
{
DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d", /* purecov: inspected */
count)); /* purecov: inspected */
}
-
-#ifdef NOT_SAFE_FOR_REPAIR
- /*
- check that open cache and table definition cache has same number of
- aktive tables
- */
- count= 0;
- for (idx=0 ; idx < table_def_cache.records ; idx++)
- {
- TABLE_SHARE *entry= (TABLE_SHARE*) hash_element(&table_def_cache,idx);
- count+= entry->ref_count;
- }
- if (count != open_files)
- {
- DBUG_PRINT("error", ("table_def ref_count: %u open_cache: %u",
- count, open_files));
- DBUG_ASSERT(count == open_files);
- }
-#endif
}
#else
#define check_unused()
@@ -300,8 +286,11 @@ void table_def_free(void)
DBUG_ENTER("table_def_free");
if (table_def_inited)
{
+ /* Free all open TABLEs first. */
+ close_cached_tables(NULL, NULL, FALSE, FALSE);
table_def_inited= 0;
pthread_mutex_destroy(&LOCK_table_share);
+ /* Free table definitions. */
my_hash_free(&table_def_cache);
}
DBUG_VOID_RETURN;
@@ -315,6 +304,128 @@ uint cached_table_definitions(void)
/*
+ Auxiliary routines for manipulating with per-share used/unused and
+ global unused lists of TABLE objects and table_cache_count counter.
+ Responsible for preserving invariants between those lists, counter
+ and TABLE::in_use member.
+ In fact those routines implement sort of implicit table cache as
+ part of table definition cache.
+*/
+
+
+/**
+ Add newly created TABLE object for table share which is going
+ to be used right away.
+*/
+
+static void table_def_add_used_table(THD *thd, TABLE *table)
+{
+ DBUG_ASSERT(table->in_use == thd);
+ table->s->used_tables.push_front(table);
+ table_cache_count++;
+}
+
+
+/**
+ Prepare used or unused TABLE instance for destruction by removing
+ it from share's and global list.
+*/
+
+static void table_def_remove_table(TABLE *table)
+{
+ if (table->in_use)
+ {
+ /* Remove from per-share chain of used TABLE objects. */
+ table->s->used_tables.remove(table);
+ }
+ else
+ {
+ /* Remove from per-share chain of unused TABLE objects. */
+ table->s->free_tables.remove(table);
+
+ /* And global unused chain. */
+ table->next->prev=table->prev;
+ table->prev->next=table->next;
+ if (table == unused_tables)
+ {
+ unused_tables=unused_tables->next;
+ if (table == unused_tables)
+ unused_tables=0;
+ }
+ check_unused();
+ }
+ table_cache_count--;
+}
+
+
+/**
+ Mark already existing TABLE instance as used.
+*/
+
+static void table_def_use_table(THD *thd, TABLE *table)
+{
+ DBUG_ASSERT(!table->in_use);
+
+ /* Unlink table from list of unused tables for this share. */
+ table->s->free_tables.remove(table);
+ /* Unlink able from global unused tables list. */
+ if (table == unused_tables)
+ { // First unused
+ unused_tables=unused_tables->next; // Remove from link
+ if (table == unused_tables)
+ unused_tables=0;
+ }
+ table->prev->next=table->next; /* Remove from unused list */
+ table->next->prev=table->prev;
+ check_unused();
+ /* Add table to list of used tables for this share. */
+ table->s->used_tables.push_front(table);
+ table->in_use= thd;
+}
+
+
+/**
+ Mark already existing used TABLE instance as unused.
+*/
+
+static void table_def_unuse_table(TABLE *table)
+{
+ DBUG_ASSERT(table->in_use);
+
+ table->in_use= 0;
+ /* Remove table from the list of tables used in this share. */
+ table->s->used_tables.remove(table);
+ /* Add table to the list of unused TABLE objects for this share. */
+ table->s->free_tables.push_front(table);
+ /* Also link it last in the global list of unused TABLE objects. */
+ if (unused_tables)
+ {
+ table->next=unused_tables;
+ table->prev=unused_tables->prev;
+ unused_tables->prev=table;
+ table->prev->next=table;
+ }
+ else
+ unused_tables=table->next=table->prev=table;
+ check_unused();
+}
+
+
+/**
+ Bind used TABLE instance to another table share.
+
+ @note Will go away once we refactor code responsible
+ for reopening tables under lock tables.
+*/
+
+static void table_def_change_share(TABLE *table, TABLE_SHARE *new_share)
+{
+ table->s->used_tables.remove(table);
+ new_share->used_tables.push_front(table);
+}
+
+
+/*
Get TABLE_SHARE for a table.
get_table_share()
@@ -347,6 +458,13 @@ TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key,
*error= 0;
+ /*
+ To be able perform any operation on table we should own
+ some kind of metadata lock on it.
+ */
+ DBUG_ASSERT(mdl_is_lock_owner(&thd->mdl_context, 0, table_list->db,
+ table_list->table_name));
+
/* Read table definition from cache */
if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key,
key_length)))
@@ -569,6 +687,7 @@ void release_table_share(TABLE_SHARE *share, enum release_type type)
safe_mutex_assert_owner(&LOCK_open);
pthread_mutex_lock(&share->mutex);
+ DBUG_ASSERT(share->ref_count);
if (!--share->ref_count)
{
if (share->version != refresh_version)
@@ -629,6 +748,26 @@ TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name)
}
+/**
+ @brief Mark table share as having one more user (increase its reference
+ count).
+
+ @param share Table share for which reference count should be increased.
+*/
+
+static void reference_table_share(TABLE_SHARE *share)
+{
+ DBUG_ENTER("reference_table_share");
+ DBUG_ASSERT(share->ref_count);
+ pthread_mutex_lock(&share->mutex);
+ share->ref_count++;
+ pthread_mutex_unlock(&share->mutex);
+ DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u",
+ (ulong) share, share->ref_count));
+ DBUG_VOID_RETURN;
+}
+
+
/*
Close file handle, but leave the table in the table cache
@@ -680,6 +819,7 @@ void close_handle_and_leave_table_as_lock(TABLE *table)
detach_merge_children(table, FALSE);
table->file->close();
table->db_stat= 0; // Mark file closed
+ table_def_change_share(table, share);
release_table_share(table->s, RELEASE_NORMAL);
table->s= share;
table->file->change_table_ptr(table, table->s);
@@ -719,11 +859,9 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild)
start_list= &open_list;
open_list=0;
- for (uint idx=0 ; result == 0 && idx < open_cache.records; idx++)
+ for (uint idx=0 ; result == 0 && idx < table_def_cache.records; idx++)
{
- OPEN_TABLE_LIST *table;
- TABLE *entry=(TABLE*) my_hash_element(&open_cache,idx);
- TABLE_SHARE *share= entry->s;
+ TABLE_SHARE *share= (TABLE_SHARE *)my_hash_element(&table_def_cache, idx);
if (db && my_strcasecmp(system_charset_info, db, share->db.str))
continue;
@@ -737,21 +875,7 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild)
if (check_table_access(thd,SELECT_ACL,&table_list, TRUE, 1, TRUE))
continue;
- /* need to check if we haven't already listed it */
- for (table= open_list ; table ; table=table->next)
- {
- if (!strcmp(table->table, share->table_name.str) &&
- !strcmp(table->db, share->db.str))
- {
- if (entry->in_use)
- table->in_use++;
- if (entry->locked_by_name)
- table->locked++;
- break;
- }
- }
- if (table)
- continue;
+
if (!(*start_list = (OPEN_TABLE_LIST *)
sql_alloc(sizeof(**start_list)+share->table_cache_key.length)))
{
@@ -762,8 +886,11 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild)
strmov(((*start_list)->db= (char*) ((*start_list)+1)),
share->db.str)+1,
share->table_name.str);
- (*start_list)->in_use= entry->in_use ? 1 : 0;
- (*start_list)->locked= entry->locked_by_name ? 1 : 0;
+ (*start_list)->in_use= 0;
+ I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables);
+ while (it++)
+ ++(*start_list)->in_use;
+ (*start_list)->locked= (share->version == 0) ? 1 : 0;
start_list= &(*start_list)->next;
*start_list=0;
}
@@ -809,19 +936,11 @@ static void free_cache_entry(TABLE *table)
/* 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);
+
intern_close_table(table);
- if (!table->in_use)
- {
- table->next->prev=table->prev; /* remove from used chain */
- table->prev->next=table->next;
- if (table == unused_tables)
- {
- unused_tables=unused_tables->next;
- if (table == unused_tables)
- unused_tables=0;
- }
- check_unused(); // consisty check
- }
+
my_free((uchar*) table,MYF(0));
DBUG_VOID_RETURN;
}
@@ -841,6 +960,38 @@ void free_io_cache(TABLE *table)
}
+/**
+ Auxiliary function which allows to kill delayed threads for
+ particular table identified by its share.
+
+ @param share Table share.
+*/
+
+static void kill_delayed_threads_for_table(TABLE_SHARE *share)
+{
+ I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables);
+ TABLE *tab;
+ while ((tab= it++))
+ {
+ THD *in_use= tab->in_use;
+
+ if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
+ ! in_use->killed)
+ {
+ in_use->killed= THD::KILL_CONNECTION;
+ pthread_mutex_lock(&in_use->mysys_var->mutex);
+ if (in_use->mysys_var->current_cond)
+ {
+ pthread_mutex_lock(in_use->mysys_var->current_mutex);
+ pthread_cond_broadcast(in_use->mysys_var->current_cond);
+ pthread_mutex_unlock(in_use->mysys_var->current_mutex);
+ }
+ pthread_mutex_unlock(&in_use->mysys_var->mutex);
+ }
+ }
+}
+
+
/*
Close all tables which aren't in use by any thread
@@ -848,18 +999,23 @@ void free_io_cache(TABLE *table)
@param tables List of tables to remove from the cache
@param have_lock If LOCK_open is locked
@param wait_for_refresh Wait for a impending flush
- @param wait_for_placeholders Wait for tables being reopened so that the GRL
- won't proceed while write-locked tables are being reopened by other
- threads.
- @remark THD can be NULL, but then wait_for_refresh must be FALSE
- and tables must be NULL.
+ @note THD can be NULL, but then wait_for_refresh must be FALSE
+ and tables must be NULL.
+
+ @note When called as part of FLUSH TABLES WITH READ LOCK this function
+ ignores metadata locks held by other threads. In order to avoid
+ situation when FLUSH TABLES WITH READ LOCK sneaks in at the moment
+ when some write-locked table is being reopened (by FLUSH TABLES or
+ ALTER TABLE) we have to rely on additional global shared metadata
+ lock taken by thread trying to obtain global read lock.
*/
bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
- bool wait_for_refresh, bool wait_for_placeholders)
+ bool wait_for_refresh)
{
- bool result=0;
+ bool result= FALSE;
+ bool found= TRUE;
DBUG_ENTER("close_cached_tables");
DBUG_ASSERT(thd || (!wait_for_refresh && !tables));
@@ -868,165 +1024,181 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
if (!tables)
{
refresh_version++; // Force close of open tables
- while (unused_tables)
- {
-#ifdef EXTRA_DEBUG
- if (my_hash_delete(&open_cache,(uchar*) unused_tables))
- printf("Warning: Couldn't delete open table from hash\n");
-#else
- (void) my_hash_delete(&open_cache,(uchar*) unused_tables);
-#endif
- }
- /* Free table shares */
- while (oldest_unused_share->next)
- {
- pthread_mutex_lock(&oldest_unused_share->mutex);
- (void) my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share);
- }
DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu",
refresh_version));
- if (wait_for_refresh)
- {
- /*
- Other threads could wait in a loop in open_and_lock_tables(),
- trying to lock one or more of our tables.
-
- If they wait for the locks in thr_multi_lock(), their lock
- request is aborted. They loop in open_and_lock_tables() and
- enter open_table(). Here they notice the table is refreshed and
- wait for COND_refresh. Then they loop again in
- open_and_lock_tables() and this time open_table() succeeds. At
- this moment, if we (the FLUSH TABLES thread) are scheduled and
- on another FLUSH TABLES enter close_cached_tables(), they could
- awake while we sleep below, waiting for others threads (us) to
- close their open tables. If this happens, the other threads
- would find the tables unlocked. They would get the locks, one
- after the other, and could do their destructive work. This is an
- issue if we have LOCK TABLES in effect.
-
- The problem is that the other threads passed all checks in
- open_table() before we refresh the table.
-
- The fix for this problem is to set some_tables_deleted for all
- threads with open tables. These threads can still get their
- locks, but will immediately release them again after checking
- this variable. They will then loop in open_and_lock_tables()
- again. There they will wait until we update all tables version
- below.
-
- Setting some_tables_deleted is done by remove_table_from_cache()
- in the other branch.
-
- In other words (reviewer suggestion): You need this setting of
- some_tables_deleted for the case when table was opened and all
- related checks were passed before incrementing refresh_version
- (which you already have) but attempt to lock the table happened
- after the call to close_old_data_files() i.e. after removal of
- current thread locks.
- */
- for (uint idx=0 ; idx < open_cache.records ; idx++)
- {
- TABLE *table=(TABLE*) my_hash_element(&open_cache,idx);
- if (table->in_use)
- table->in_use->some_tables_deleted= 1;
- }
- }
+ kill_delayed_threads();
}
else
{
bool found=0;
for (TABLE_LIST *table= tables; table; table= table->next_local)
{
- if (remove_table_from_cache(thd, table->db, table->table_name,
- RTFC_OWNED_BY_THD_FLAG))
+ TABLE_SHARE *share= get_cached_table_share(table->db, table->table_name);
+
+ if (share)
+ {
+ share->version= 0;
+ kill_delayed_threads_for_table(share);
found=1;
+ }
}
if (!found)
wait_for_refresh=0; // Nothing to wait for
}
-#ifndef EMBEDDED_LIBRARY
- if (!tables)
- kill_delayed_threads();
-#endif
- if (wait_for_refresh)
+
+ /*
+ Get rid of all unused TABLE and TABLE_SHARE instances. By doing
+ this we automatically close all tables which were marked as "old".
+
+ FIXME: Do not close all unused TABLE instances when flushing
+ particular table.
+ */
+ while (unused_tables)
+ free_cache_entry(unused_tables);
+ /* Free table shares */
+ while (oldest_unused_share->next)
+ {
+ pthread_mutex_lock(&oldest_unused_share->mutex);
+ (void) my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share);
+ }
+
+ if (!wait_for_refresh)
+ {
+ if (!have_lock)
+ pthread_mutex_unlock(&LOCK_open);
+ DBUG_RETURN(result);
+ }
+
+ DBUG_ASSERT(!have_lock);
+ pthread_mutex_unlock(&LOCK_open);
+
+ if (thd->locked_tables)
{
/*
- If there is any table that has a lower refresh_version, wait until
- this is closed (or this thread is killed) before returning
+ If we are under LOCK TABLES we need to reopen tables without
+ opening a door for any concurrent threads to sneak in and get
+ lock on our tables. To achieve this we use exclusive metadata
+ locks.
*/
- thd->mysys_var->current_mutex= &LOCK_open;
- thd->mysys_var->current_cond= &COND_refresh;
- thd_proc_info(thd, "Flushing tables");
+ if (!tables)
+ {
+ for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
+ {
+ /*
+ Checking TABLE::db_stat is essential in case when we have
+ several instances of the table open and locked.
+ */
+ if (tab->db_stat)
+ {
+ char dbname[NAME_LEN+1];
+ char tname[NAME_LEN+1];
+ /*
+ Since close_data_files_and_morph_locks() frees share's memroot
+ we need to make copies of database and table names.
+ */
+ strmov(dbname, tab->s->db.str);
+ strmov(tname, tab->s->table_name.str);
+ if (wait_while_table_is_used(thd, tab, HA_EXTRA_FORCE_REOPEN))
+ {
+ result= TRUE;
+ goto err_with_reopen;
+ }
+ pthread_mutex_lock(&LOCK_open);
+ close_data_files_and_morph_locks(thd, dbname, tname);
+ pthread_mutex_unlock(&LOCK_open);
+ }
+ }
+ }
+ else
+ {
+ for (TABLE_LIST *table= tables; table; table= table->next_local)
+ {
+ TABLE *tab= find_locked_table(thd->open_tables, table->db,
+ table->table_name);
+ /*
+ Checking TABLE::db_stat is essential in case when we have
+ several instances of the table open and locked.
+ */
+ if (tab->db_stat)
+ {
+ if (wait_while_table_is_used(thd, tab, HA_EXTRA_FORCE_REOPEN))
+ {
+ result= TRUE;
+ goto err_with_reopen;
+ }
+ pthread_mutex_lock(&LOCK_open);
+ close_data_files_and_morph_locks(thd, table->db, table->table_name);
+ pthread_mutex_unlock(&LOCK_open);
+ }
+ }
+ }
+ }
- close_old_data_files(thd,thd->open_tables,1,1);
+ /* Wait until all threads have closed all the tables we are flushing. */
+ DBUG_PRINT("info", ("Waiting for other threads to close their open tables"));
+
+ while (found && ! thd->killed)
+ {
+ found= FALSE;
+ /*
+ To avoid self and other kinds of deadlock we have to flush open HANDLERs.
+ */
mysql_ha_flush(thd);
DEBUG_SYNC(thd, "after_flush_unlock");
- bool found=1;
- /* Wait until all threads has closed all the tables we had locked */
- DBUG_PRINT("info",
- ("Waiting for other threads to close their open tables"));
- while (found && ! thd->killed)
+ pthread_mutex_lock(&LOCK_open);
+
+ thd->enter_cond(&COND_refresh, &LOCK_open, "Flushing tables");
+
+ if (!tables)
{
- found=0;
- for (uint idx=0 ; idx < open_cache.records ; idx++)
+ for (uint idx=0 ; idx < table_def_cache.records ; idx++)
{
- TABLE *table=(TABLE*) my_hash_element(&open_cache,idx);
- /* Avoid a self-deadlock. */
- if (table->in_use == thd)
- continue;
- /*
- Note that we wait here only for tables which are actually open, and
- not for placeholders with TABLE::open_placeholder set. Waiting for
- latter will cause deadlock in the following scenario, for example:
-
- conn1: lock table t1 write;
- conn2: lock table t2 write;
- conn1: flush tables;
- conn2: flush tables;
-
- It also does not make sense to wait for those of placeholders that
- are employed by CREATE TABLE as in this case table simply does not
- exist yet.
- */
- if (table->needs_reopen_or_name_lock() && (table->db_stat ||
- (table->open_placeholder && wait_for_placeholders)))
- {
- found=1;
- DBUG_PRINT("signal", ("Waiting for COND_refresh"));
- pthread_cond_wait(&COND_refresh,&LOCK_open);
- break;
- }
+ TABLE_SHARE *share=(TABLE_SHARE*) my_hash_element(&table_def_cache,
+ idx);
+ if (share->version != refresh_version)
+ {
+ found= TRUE;
+ break;
+ }
+ }
+ }
+ else
+ {
+ for (TABLE_LIST *table= tables; table; table= table->next_local)
+ {
+ TABLE_SHARE *share= get_cached_table_share(table->db, table->table_name);
+ if (share && share->version != refresh_version)
+ {
+ found= TRUE;
+ break;
+ }
}
}
+
+ if (found)
+ {
+ DBUG_PRINT("signal", ("Waiting for COND_refresh"));
+ pthread_cond_wait(&COND_refresh,&LOCK_open);
+ }
+
+ thd->exit_cond(NULL);
+ }
+
+err_with_reopen:
+ if (thd->locked_tables)
+ {
+ pthread_mutex_lock(&LOCK_open);
/*
No other thread has the locked tables open; reopen them and get the
old locks. This should always succeed (unless some external process
has removed the tables)
*/
thd->in_lock_tables=1;
- result=reopen_tables(thd,1,1);
+ result|= reopen_tables(thd, 1);
thd->in_lock_tables=0;
- /* Set version for table */
- for (TABLE *table=thd->open_tables; table ; table= table->next)
- {
- /*
- Preserve the version (0) of write locked tables so that a impending
- global read lock won't sneak in.
- */
- if (table->reginfo.lock_type < TL_WRITE_ALLOW_WRITE)
- table->s->version= refresh_version;
- }
- }
- if (!have_lock)
pthread_mutex_unlock(&LOCK_open);
- if (wait_for_refresh)
- {
- pthread_mutex_lock(&thd->mysys_var->mutex);
- thd->mysys_var->current_mutex= 0;
- thd->mysys_var->current_cond= 0;
- thd_proc_info(thd, 0);
- pthread_mutex_unlock(&thd->mysys_var->mutex);
+ mdl_downgrade_exclusive_locks(&thd->mdl_context);
}
DBUG_RETURN(result);
}
@@ -1079,7 +1251,7 @@ bool close_cached_connection_tables(THD *thd, bool if_wait_for_refresh,
}
if (tables)
- result= close_cached_tables(thd, tables, TRUE, FALSE, FALSE);
+ result= close_cached_tables(thd, tables, TRUE, FALSE);
if (!have_lock)
pthread_mutex_unlock(&LOCK_open);
@@ -1208,9 +1380,8 @@ static void close_open_tables(THD *thd)
thd->some_tables_deleted= 0;
/* Free tables to hold down open files */
- while (open_cache.records > table_cache_size && unused_tables)
- my_hash_delete(&open_cache,(uchar*) unused_tables); /* purecov: tested */
- check_unused();
+ while (table_cache_count > table_cache_size && unused_tables)
+ free_cache_entry(unused_tables);
if (found_old_table)
{
/* Tell threads waiting for refresh that something has happened */
@@ -1239,7 +1410,8 @@ static void close_open_tables(THD *thd)
leave prelocked mode if needed.
*/
-void close_thread_tables(THD *thd)
+void close_thread_tables(THD *thd,
+ bool skip_mdl)
{
TABLE *table;
prelocked_mode_type prelocked_mode= thd->prelocked_mode;
@@ -1328,6 +1500,10 @@ void close_thread_tables(THD *thd)
if (prelocked_mode == PRELOCKED_UNDER_LOCK_TABLES)
DBUG_VOID_RETURN;
+ /*
+ Note that we are leaving prelocked mode so we don't need
+ to care about THD::locked_tables_root.
+ */
thd->lock= thd->locked_tables;
thd->locked_tables= 0;
/* Fallthrough */
@@ -1358,6 +1534,12 @@ void close_thread_tables(THD *thd)
if (thd->open_tables)
close_open_tables(thd);
+ mdl_release_locks(&thd->mdl_context);
+ if (!skip_mdl)
+ {
+ mdl_remove_all_locks(&thd->mdl_context);
+ }
+
if (prelocked_mode == PRELOCKED)
{
/*
@@ -1381,8 +1563,7 @@ bool close_thread_table(THD *thd, TABLE **table_ptr)
DBUG_ENTER("close_thread_table");
DBUG_ASSERT(table->key_read == 0);
DBUG_ASSERT(!table->file || table->file->inited == handler::NONE);
- DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
- table->s->table_name.str, (long) table));
+ safe_mutex_assert_owner(&LOCK_open);
*table_ptr=table->next;
/*
@@ -1392,10 +1573,11 @@ bool close_thread_table(THD *thd, TABLE **table_ptr)
if (table->child_l || table->parent)
detach_merge_children(table, TRUE);
+ table->mdl_lock= 0;
if (table->needs_reopen_or_name_lock() ||
thd->version != refresh_version || !table->db_stat)
{
- my_hash_delete(&open_cache,(uchar*) table);
+ free_cache_entry(table);
found_old_table=1;
}
else
@@ -1413,16 +1595,7 @@ bool close_thread_table(THD *thd, TABLE **table_ptr)
free_field_buffers_larger_than(table,MAX_TDC_BLOB_SIZE);
table->file->ha_reset();
- table->in_use=0;
- if (unused_tables)
- {
- table->next=unused_tables; /* Link in last */
- table->prev=unused_tables->prev;
- unused_tables->prev=table;
- table->prev->next=table;
- }
- else
- unused_tables=table->next=table->prev=table;
+ table_def_unuse_table(table);
}
DBUG_RETURN(found_old_table);
}
@@ -2121,7 +2294,7 @@ void unlink_open_table(THD *thd, TABLE *find, bool unlock)
/* Remove table from open_tables list. */
*prev= list->next;
/* Close table. */
- my_hash_delete(&open_cache,(uchar*) list); // Close table
+ free_cache_entry(list);
}
else
{
@@ -2231,43 +2404,34 @@ void wait_for_condition(THD *thd, pthread_mutex_t *mutex, pthread_cond_t *cond)
bool name_lock_locked_table(THD *thd, TABLE_LIST *tables)
{
+ bool result= TRUE;
+
DBUG_ENTER("name_lock_locked_table");
/* Under LOCK TABLES we must only accept write locked tables. */
- tables->table= find_locked_table(thd, tables->db, tables->table_name);
+ tables->table= find_write_locked_table(thd->open_tables, tables->db,
+ tables->table_name);
- if (!tables->table)
- my_error(ER_TABLE_NOT_LOCKED, MYF(0), tables->alias);
- else if (tables->table->reginfo.lock_type < TL_WRITE_LOW_PRIORITY)
- my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), tables->alias);
- else
+ if (tables->table)
{
/*
Ensures that table is opened only by this thread and that no
other statement will open this table.
*/
- wait_while_table_is_used(thd, tables->table, HA_EXTRA_FORCE_REOPEN);
- DBUG_RETURN(FALSE);
+ result= wait_while_table_is_used(thd, tables->table, HA_EXTRA_FORCE_REOPEN);
}
- DBUG_RETURN(TRUE);
+ DBUG_RETURN(result);
}
/*
- Open table which is already name-locked by this thread.
+ Open table for which this thread has exclusive meta-data lock.
SYNOPSIS
reopen_name_locked_table()
thd Thread handle
- table_list TABLE_LIST object for table to be open, TABLE_LIST::table
- member should point to TABLE object which was used for
- name-locking.
- link_in TRUE - if TABLE object for table to be opened should be
- linked into THD::open_tables list.
- FALSE - placeholder used for name-locking is already in
- this list so we only need to preserve TABLE::next
- pointer.
+ table_list TABLE_LIST object for table to be open.
NOTE
This function assumes that its caller already acquired LOCK_open mutex.
@@ -2277,33 +2441,32 @@ bool name_lock_locked_table(THD *thd, TABLE_LIST *tables)
TRUE - Error
*/
-bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in)
+bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list)
{
- TABLE *table= table_list->table;
+ TABLE *table;
TABLE_SHARE *share;
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
char *table_name= table_list->table_name;
- TABLE orig_table;
DBUG_ENTER("reopen_name_locked_table");
- safe_mutex_assert_owner(&LOCK_open);
-
- if (thd->killed || !table)
+ if (thd->killed)
DBUG_RETURN(TRUE);
- orig_table= *table;
+ key_length= create_table_def_key(thd, key, table_list, 0);
+
+ if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
+ DBUG_RETURN(TRUE);
- if (open_unireg_entry(thd, table, table_list, table_name,
- table->s->table_cache_key.str,
- table->s->table_cache_key.length, thd->mem_root, 0))
+ if (reopen_table_entry(thd, table, table_list, table_name, key, key_length))
{
- intern_close_table(table);
/*
If there was an error during opening of table (for example if it
does not exist) '*table' object can be wiped out. To be able
properly release name-lock in this case we should restore this
object to its original state.
*/
- *table= orig_table;
+ my_free((uchar*)table, MYF(0));
DBUG_RETURN(TRUE);
}
@@ -2318,21 +2481,11 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in)
*/
share->version=0;
table->in_use = thd;
- check_unused();
- if (link_in)
- {
- table->next= thd->open_tables;
- thd->open_tables= table;
- }
- else
- {
- /*
- TABLE object should be already in THD::open_tables list so we just
- need to set TABLE::next correctly.
- */
- table->next= orig_table.next;
- }
+ table_def_add_used_table(thd, table);
+
+ table->next= thd->open_tables;
+ thd->open_tables= table;
table->tablenr=thd->current_tablenr++;
table->used_fields=0;
@@ -2340,109 +2493,7 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in)
table->null_row= table->maybe_null= 0;
table->force_index= table->force_index_order= table->force_index_group= 0;
table->status=STATUS_NO_RECORD;
- DBUG_RETURN(FALSE);
-}
-
-
-/**
- Create and insert into table cache placeholder for table
- which will prevent its opening (or creation) (a.k.a lock
- table name).
-
- @param thd Thread context
- @param key Table cache key for name to be locked
- @param key_length Table cache key length
-
- @return Pointer to TABLE object used for name locking or 0 in
- case of failure.
-*/
-
-TABLE *table_cache_insert_placeholder(THD *thd, const char *key,
- uint key_length)
-{
- TABLE *table;
- TABLE_SHARE *share;
- char *key_buff;
- DBUG_ENTER("table_cache_insert_placeholder");
-
- safe_mutex_assert_owner(&LOCK_open);
-
- /*
- Create a table entry with the right key and with an old refresh version
- Note that we must use my_multi_malloc() here as this is freed by the
- table cache
- */
- if (!my_multi_malloc(MYF(MY_WME | MY_ZEROFILL),
- &table, sizeof(*table),
- &share, sizeof(*share),
- &key_buff, key_length,
- NULL))
- DBUG_RETURN(NULL);
-
- table->s= share;
- share->set_table_cache_key(key_buff, key, key_length);
- share->tmp_table= INTERNAL_TMP_TABLE; // for intern_close_table
- table->in_use= thd;
- table->locked_by_name=1;
-
- if (my_hash_insert(&open_cache, (uchar*)table))
- {
- my_free((uchar*) table, MYF(0));
- DBUG_RETURN(NULL);
- }
-
- DBUG_RETURN(table);
-}
-
-
-/**
- Obtain an exclusive name lock on the table if it is not cached
- in the table cache.
-
- @param thd Thread context
- @param db Name of database
- @param table_name Name of table
- @param[out] table Out parameter which is either:
- - set to NULL if table cache contains record for
- the table or
- - set to point to the TABLE instance used for
- name-locking.
-
- @note This function takes into account all records for table in table
- cache, even placeholders used for name-locking. This means that
- 'table' parameter can be set to NULL for some situations when
- table does not really exist.
-
- @retval TRUE Error occured (OOM)
- @retval FALSE Success. 'table' parameter set according to above rules.
-*/
-
-bool lock_table_name_if_not_cached(THD *thd, const char *db,
- const char *table_name, TABLE **table)
-{
- char key[MAX_DBKEY_LENGTH];
- uint key_length;
- DBUG_ENTER("lock_table_name_if_not_cached");
-
- key_length= (uint)(strmov(strmov(key, db) + 1, table_name) - key) + 1;
- pthread_mutex_lock(&LOCK_open);
-
- if (my_hash_search(&open_cache, (uchar *)key, key_length))
- {
- pthread_mutex_unlock(&LOCK_open);
- DBUG_PRINT("info", ("Table is cached, name-lock is not obtained"));
- *table= 0;
- DBUG_RETURN(FALSE);
- }
- if (!(*table= table_cache_insert_placeholder(thd, key, key_length)))
- {
- pthread_mutex_unlock(&LOCK_open);
- DBUG_RETURN(TRUE);
- }
- (*table)->open_placeholder= 1;
- (*table)->next= thd->open_tables;
- thd->open_tables= *table;
- pthread_mutex_unlock(&LOCK_open);
+ table_list->table= table;
DBUG_RETURN(FALSE);
}
@@ -2511,6 +2562,20 @@ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists)
}
+/**
+ @brief Helper function used by MDL subsystem for releasing TABLE_SHARE
+ objects in cases when it no longer wants to cache reference to it.
+*/
+
+void table_share_release_hook(void *share)
+{
+ pthread_mutex_lock(&LOCK_open);
+ release_table_share((TABLE_SHARE*)share, RELEASE_NORMAL);
+ broadcast_refresh();
+ pthread_mutex_unlock(&LOCK_open);
+}
+
+
/*
Open a table.
@@ -2518,13 +2583,17 @@ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists)
open_table()
thd Thread context.
table_list Open first table in list.
- refresh INOUT Pointer to memory that will be set to 1 if
- we need to close all tables and reopen them.
+ action INOUT Pointer to variable of enum_open_table_action type
+ which will be set according to action which is
+ required to remedy problem appeared during attempt
+ to open table.
If this is a NULL pointer, then the table is not
put in the thread-open-list.
flags Bitmap of flags to modify how open works:
MYSQL_LOCK_IGNORE_FLUSH - Open table even if
- someone has done a flush or namelock on it.
+ someone has done a flush or there is a pending
+ exclusive metadata lock requests against it
+ (i.e. request high priority metadata lock).
No version number checking is done.
MYSQL_OPEN_TEMPORARY_ONLY - Open only temporary
table not the base table or view.
@@ -2545,21 +2614,23 @@ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists)
TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
- bool *refresh, uint flags)
+ enum_open_table_action *action, uint flags)
{
reg1 TABLE *table;
char key[MAX_DBKEY_LENGTH];
uint key_length;
char *alias= table_list->alias;
- HASH_SEARCH_STATE state;
+ MDL_LOCK *mdl_lock;
+ int error;
+ TABLE_SHARE *share;
DBUG_ENTER("open_table");
/* Parsing of partitioning information from .frm needs thd->lex set up. */
DBUG_ASSERT(thd->lex->is_lex_started);
/* find a unused table in the open table cache */
- if (refresh)
- *refresh=0;
+ if (action)
+ *action= OT_NO_ACTION;
/* an open table operation needs a lot of the stack space */
if (check_stack_overrun(thd, STACK_MIN_SIZE_FOR_OPEN, (uchar *)&alias))
@@ -2685,7 +2756,13 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
Is this table a view and not a base table?
(it is work around to allow to open view with locked tables,
real fix will be made after definition cache will be made)
+
+ Since opening of view which was not explicitly locked by LOCK
+ TABLES breaks metadata locking protocol (potentially can lead
+ to deadlocks) it should be disallowed.
*/
+ if (mdl_is_lock_owner(&thd->mdl_context, 0, table_list->db,
+ table_list->table_name))
{
char path[FN_REFLEN + 1];
enum legacy_db_type not_used;
@@ -2693,21 +2770,12 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
table_list->db, table_list->table_name, reg_ext, 0);
if (mysql_frm_type(thd, path, &not_used) == FRMTYPE_VIEW)
{
- /*
- Will not be used (because it's VIEW) but has to be passed.
- Also we will not free it (because it is a stack variable).
- */
- TABLE tab;
- table= &tab;
- pthread_mutex_lock(&LOCK_open);
- if (!open_unireg_entry(thd, table, table_list, alias,
- key, key_length, mem_root, 0))
+ if (!tdc_open_view(thd, table_list, alias, key, key_length,
+ mem_root, 0))
{
DBUG_ASSERT(table_list->view != 0);
- pthread_mutex_unlock(&LOCK_open);
DBUG_RETURN(0); // VIEW
}
- pthread_mutex_unlock(&LOCK_open);
}
}
/*
@@ -2740,6 +2808,32 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
on disk.
*/
+ mdl_lock= table_list->mdl_lock;
+ mdl_add_lock(&thd->mdl_context, mdl_lock);
+
+ if (table_list->open_table_type)
+ {
+ mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE);
+ /* TODO: This case can be significantly optimized. */
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+ DBUG_RETURN(0);
+ }
+ else
+ {
+ bool retry;
+
+ if (table_list->mdl_upgradable)
+ mdl_set_upgradable(mdl_lock);
+ mdl_set_lock_priority(mdl_lock, (flags & MYSQL_LOCK_IGNORE_FLUSH) ?
+ MDL_HIGH_PRIO : MDL_NORMAL_PRIO);
+ if (mdl_acquire_shared_lock(mdl_lock, &retry))
+ {
+ if (action && retry)
+ *action= OT_BACK_OFF_AND_RETRY;
+ DBUG_RETURN(0);
+ }
+ }
+
pthread_mutex_lock(&LOCK_open);
/*
@@ -2756,222 +2850,210 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
! (flags & MYSQL_LOCK_IGNORE_FLUSH))
{
/* Someone did a refresh while thread was opening tables */
- if (refresh)
- *refresh=1;
+ if (action)
+ *action= OT_BACK_OFF_AND_RETRY;
pthread_mutex_unlock(&LOCK_open);
DBUG_RETURN(0);
}
- /*
- In order for the back off and re-start process to work properly,
- handler tables having old versions (due to FLUSH TABLES or pending
- name-lock) MUST be closed. This is specially important if a name-lock
- is pending for any table of the handler_tables list, otherwise a
- deadlock may occur.
- */
- if (thd->handler_tables)
- mysql_ha_flush(thd);
-
- /*
- Actually try to find the table in the open_cache.
- The cache may contain several "TABLE" instances for the same
- physical table. The instances that are currently "in use" by
- some thread have their "in_use" member != NULL.
- There is no good reason for having more than one entry in the
- hash for the same physical table, except that we use this as
- an implicit "pending locks queue" - see
- wait_for_locked_table_names for details.
- */
- for (table= (TABLE*) my_hash_first(&open_cache, (uchar*) key, key_length,
- &state);
- table && table->in_use ;
- table= (TABLE*) my_hash_next(&open_cache, (uchar*) key, key_length,
- &state))
+ if (table_list->open_table_type == TABLE_LIST::OPEN_OR_CREATE)
{
- DBUG_PRINT("tcache", ("in_use table: '%s'.'%s' 0x%lx", table->s->db.str,
- table->s->table_name.str, (long) table));
- /*
- Here we flush tables marked for flush.
- Normally, table->s->version contains the value of
- refresh_version from the moment when this table was
- (re-)opened and added to the cache.
- If since then we did (or just started) FLUSH TABLES
- statement, refresh_version has been increased.
- For "name-locked" TABLE instances, table->s->version is set
- to 0 (see lock_table_name for details).
- In case there is a pending FLUSH TABLES or a name lock, we
- need to back off and re-start opening tables.
- If we do not back off now, we may dead lock in case of lock
- order mismatch with some other thread:
- c1: name lock t1; -- sort of exclusive lock
- c2: open t2; -- sort of shared lock
- c1: name lock t2; -- blocks
- c2: open t1; -- blocks
- */
- if (table->needs_reopen_or_name_lock())
- {
- DBUG_PRINT("note",
- ("Found table '%s.%s' with different refresh version",
- table_list->db, table_list->table_name));
+ bool exists;
- if (flags & MYSQL_LOCK_IGNORE_FLUSH)
- {
- /* Force close at once after usage */
- thd->version= table->s->version;
- continue;
- }
+ if (check_if_table_exists(thd, table_list, &exists))
+ goto err_unlock2;
- /* Avoid self-deadlocks by detecting self-dependencies. */
- if (table->open_placeholder && table->in_use == thd)
- {
- pthread_mutex_unlock(&LOCK_open);
- my_error(ER_UPDATE_TABLE_USED, MYF(0), table->s->table_name.str);
- DBUG_RETURN(0);
- }
+ if (!exists)
+ {
+ pthread_mutex_unlock(&LOCK_open);
+ DBUG_RETURN(0);
+ }
+ /* Table exists. Let us try to open it. */
+ }
+ else if (table_list->open_table_type == TABLE_LIST::TAKE_EXCLUSIVE_MDL)
+ {
+ pthread_mutex_unlock(&LOCK_open);
+ DBUG_RETURN(0);
+ }
+ if (!(share= (TABLE_SHARE *)mdl_get_cached_object(mdl_lock)))
+ {
+ if (!(share= get_table_share_with_create(thd, table_list, key,
+ key_length, OPEN_VIEW,
+ &error)))
+ goto err_unlock2;
+
+ if (share->is_view)
+ {
/*
- Back off, part 1: mark the table as "unused" for the
- purpose of name-locking by setting table->db_stat to 0. Do
- that only for the tables in this thread that have an old
- table->s->version (this is an optimization (?)).
- table->db_stat == 0 signals wait_for_locked_table_names
- that the tables in question are not used any more. See
- table_is_used call for details.
-
- Notice that HANDLER tables were already taken care of by
- the earlier call to mysql_ha_flush() in this same critical
- section.
- */
- close_old_data_files(thd,thd->open_tables,0,0);
- /*
- Back-off part 2: try to avoid "busy waiting" on the table:
- if the table is in use by some other thread, we suspend
- and wait till the operation is complete: when any
- operation that juggles with table->s->version completes,
- it broadcasts COND_refresh condition variable.
- If 'old' table we met is in use by current thread we return
- without waiting since in this situation it's this thread
- which is responsible for broadcasting on COND_refresh
- (and this was done already in close_old_data_files()).
- Good example of such situation is when we have statement
- that needs two instances of table and FLUSH TABLES comes
- after we open first instance but before we open second
- instance.
+ This table is a view. Validate its metadata version: in particular,
+ that it was a view when the statement was prepared.
*/
- if (table->in_use != thd)
+ if (check_and_update_table_version(thd, table_list, share))
+ goto err_unlock;
+ if (table_list->i_s_requested_object & OPEN_TABLE_ONLY)
+ goto err_unlock;
+
+ /* Open view */
+ if (open_new_frm(thd, share, alias,
+ (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
+ HA_GET_INDEX | HA_TRY_READ_ONLY),
+ READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD |
+ (flags & OPEN_VIEW_NO_PARSE), thd->open_options,
+ 0, table_list, mem_root))
+ goto err_unlock;
+
+ /* TODO: Don't free this */
+ release_table_share(share, RELEASE_NORMAL);
+
+ if (flags & OPEN_VIEW_NO_PARSE)
{
- /* wait_for_conditionwill unlock LOCK_open for us */
- wait_for_condition(thd, &LOCK_open, &COND_refresh);
+ /*
+ VIEW not really opened, only frm were read.
+ Set 1 as a flag here
+ */
+ table_list->view= (LEX*)1;
}
else
{
- pthread_mutex_unlock(&LOCK_open);
+ DBUG_ASSERT(table_list->view);
}
+
+ pthread_mutex_unlock(&LOCK_open);
+ DBUG_RETURN(0);
+ }
+ else if (table_list->view)
+ {
/*
- There is a refresh in progress for this table.
- Signal the caller that it has to try again.
+ We're trying to open a table for what was a view.
+ This can only happen during (re-)execution.
+ At prepared statement prepare the view has been opened and
+ merged into the statement parse tree. After that, someone
+ performed a DDL and replaced the view with a base table.
+ Don't try to open the table inside a prepared statement,
+ invalidate it instead.
+
+ Note, the assert below is known to fail inside stored
+ procedures (Bug#27011).
*/
- if (refresh)
- *refresh=1;
- DBUG_RETURN(0);
+ DBUG_ASSERT(thd->m_reprepare_observer);
+ check_and_update_table_version(thd, table_list, share);
+ /* Always an error. */
+ DBUG_ASSERT(thd->is_error());
+ goto err_unlock;
}
+
+ if (table_list->i_s_requested_object & OPEN_VIEW_ONLY)
+ goto err_unlock;
+
+ /*
+ We are going to to store extra reference to the share in MDL-subsystem
+ so we need to increase reference counter;
+ */
+ reference_table_share(share);
+ mdl_set_cached_object(mdl_lock, share, table_share_release_hook);
}
- if (table)
+ else
{
- DBUG_PRINT("tcache", ("unused table: '%s'.'%s' 0x%lx", table->s->db.str,
- table->s->table_name.str, (long) table));
- /* Unlink the table from "unused_tables" list. */
- if (table == unused_tables)
- { // First unused
- unused_tables=unused_tables->next; // Remove from link
- if (table == unused_tables)
- unused_tables=0;
+ if (table_list->view)
+ {
+ DBUG_ASSERT(thd->m_reprepare_observer);
+ check_and_update_table_version(thd, table_list, share);
+ /* Always an error. */
+ DBUG_ASSERT(thd->is_error());
+ goto err_unlock;
}
- table->prev->next=table->next; /* Remove from unused list */
- table->next->prev=table->prev;
- table->in_use= thd;
+ /* When we have cached TABLE_SHARE we know that is not a view. */
+ if (table_list->i_s_requested_object & OPEN_VIEW_ONLY)
+ goto err_unlock;
+
+ /*
+ We are going to use this share for construction of new TABLE object
+ so reference counter should be increased.
+ */
+ reference_table_share(share);
+ }
+
+ if (share->version != refresh_version)
+ {
+ if (!(flags & MYSQL_LOCK_IGNORE_FLUSH))
+ {
+ if (action)
+ *action= OT_BACK_OFF_AND_RETRY;
+ release_table_share(share, RELEASE_NORMAL);
+ pthread_mutex_unlock(&LOCK_open);
+ DBUG_RETURN(0);
+ }
+ /* Force close at once after usage */
+ thd->version= share->version;
+ }
+
+ if (!share->free_tables.is_empty())
+ {
+ table= share->free_tables.head();
+ table_def_use_table(thd, table);
+ /* We need to release share as we have EXTRA reference to it in our hands. */
+ release_table_share(share, RELEASE_NORMAL);
}
else
{
- /* Insert a new TABLE instance into the open cache */
- int error;
- DBUG_PRINT("tcache", ("opening new table"));
- /* Free cache if too big */
- while (open_cache.records > table_cache_size && unused_tables)
- my_hash_delete(&open_cache,(uchar*) unused_tables); /* purecov: tested */
+ /* We have too many TABLE instances around let us try to get rid of them. */
+ while (table_cache_count > table_cache_size && unused_tables)
+ free_cache_entry(unused_tables);
- if (table_list->create)
- {
- bool exists;
+ /* make a new table */
+ if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
+ goto err_unlock;
- if (check_if_table_exists(thd, table_list, &exists))
- {
- pthread_mutex_unlock(&LOCK_open);
- DBUG_RETURN(NULL);
- }
+ error= open_table_from_share(thd, share, alias,
+ (uint) (HA_OPEN_KEYFILE |
+ HA_OPEN_RNDFILE |
+ HA_GET_INDEX |
+ HA_TRY_READ_ONLY),
+ (READ_KEYINFO | COMPUTE_TYPES |
+ EXTRA_RECORD),
+ thd->open_options, table, FALSE);
+
+ if (error)
+ {
+ my_free(table, MYF(0));
- if (!exists)
+ if (action)
{
- /*
- Table to be created, so we need to create placeholder in table-cache.
- */
- if (!(table= table_cache_insert_placeholder(thd, key, key_length)))
+ if (error == 7)
{
- pthread_mutex_unlock(&LOCK_open);
- DBUG_RETURN(NULL);
+ share->version= 0;
+ *action= OT_DISCOVER;
+ }
+ else if (share->crashed)
+ {
+ share->version= 0;
+ *action= OT_REPAIR;
}
- /*
- Link placeholder to the open tables list so it will be automatically
- removed once tables are closed. Also mark it so it won't be ignored
- by other trying to take name-lock.
- */
- table->open_placeholder= 1;
- table->next= thd->open_tables;
- thd->open_tables= table;
- pthread_mutex_unlock(&LOCK_open);
- DBUG_RETURN(table);
}
- /* Table exists. Let us try to open it. */
- }
- /* make a new table */
- if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
- {
- pthread_mutex_unlock(&LOCK_open);
- DBUG_RETURN(NULL);
+ goto err_unlock;
}
- error= open_unireg_entry(thd, table, table_list, alias, key, key_length,
- mem_root, (flags & OPEN_VIEW_NO_PARSE));
- if (error > 0)
+ if (open_table_entry_fini(thd, share, table))
{
+ closefrm(table, 0);
my_free((uchar*)table, MYF(0));
- pthread_mutex_unlock(&LOCK_open);
- DBUG_RETURN(NULL);
+ goto err_unlock;
}
- if (table_list->view || error < 0)
- {
- /*
- VIEW not really opened, only frm were read.
- Set 1 as a flag here
- */
- if (error < 0)
- table_list->view= (LEX*)1;
- my_free((uchar*)table, MYF(0));
- pthread_mutex_unlock(&LOCK_open);
- DBUG_RETURN(0); // VIEW
- }
- DBUG_PRINT("info", ("inserting table '%s'.'%s' 0x%lx into the cache",
- table->s->db.str, table->s->table_name.str,
- (long) table));
- (void) my_hash_insert(&open_cache,(uchar*) table);
+ /* Add table to the share's used tables list. */
+ table_def_add_used_table(thd, table);
}
- check_unused(); // Debugging call
-
pthread_mutex_unlock(&LOCK_open);
- if (refresh)
+
+ // Table existed
+ if (table_list->open_table_type == TABLE_LIST::OPEN_OR_CREATE)
+ mdl_downgrade_exclusive_locks(&thd->mdl_context);
+
+ table->mdl_lock= mdl_lock;
+ if (action)
{
table->next=thd->open_tables; /* Link into simple list */
thd->open_tables=table;
@@ -3013,15 +3095,32 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
table->clear_column_bitmaps();
DBUG_ASSERT(table->key_read == 0);
DBUG_RETURN(table);
+
+err_unlock:
+ release_table_share(share, RELEASE_NORMAL);
+err_unlock2:
+ pthread_mutex_unlock(&LOCK_open);
+ mdl_release_lock(&thd->mdl_context, mdl_lock);
+ DBUG_RETURN(0);
}
-TABLE *find_locked_table(THD *thd, const char *db,const char *table_name)
+/**
+ Find table in the list of open tables.
+
+ @param list List of TABLE objects to be inspected.
+ @param db Database name
+ @param table_name Table name
+
+ @return Pointer to the TABLE object found, 0 if no table found.
+*/
+
+TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name)
{
char key[MAX_DBKEY_LENGTH];
uint key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1;
- for (TABLE *table=thd->open_tables; table ; table=table->next)
+ for (TABLE *table= list; table ; table=table->next)
{
if (table->s->table_cache_key.length == key_length &&
!memcmp(table->s->table_cache_key.str, key, key_length))
@@ -3031,6 +3130,41 @@ TABLE *find_locked_table(THD *thd, const char *db,const char *table_name)
}
+/**
+ Find write locked instance of table in the list of open tables,
+ emit error if no such instance found.
+
+ @param thd List of TABLE objects to be searched
+ @param db Database name.
+ @param table_name Name of table.
+
+ @return Pointer to write-locked TABLE instance, 0 - otherwise.
+*/
+
+TABLE *find_write_locked_table(TABLE *list, const char *db, const char *table_name)
+{
+ TABLE *tab= find_locked_table(list, db, table_name);
+
+ if (!tab)
+ {
+ my_error(ER_TABLE_NOT_LOCKED, MYF(0), table_name);
+ return 0;
+ }
+ else
+ {
+ while (tab->reginfo.lock_type < TL_WRITE_LOW_PRIORITY &&
+ (tab= find_locked_table(tab->next, db, table_name)))
+ continue;
+ if (!tab)
+ {
+ my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0), table_name);
+ return 0;
+ }
+ }
+ return tab;
+}
+
+
/*
Reopen an table because the definition has changed.
@@ -3073,14 +3207,10 @@ bool reopen_table(TABLE *table)
table_list.table_name= table->s->table_name.str;
table_list.table= table;
- if (wait_for_locked_table_names(thd, &table_list))
- DBUG_RETURN(1); // Thread was killed
-
- if (open_unireg_entry(thd, &tmp, &table_list,
- table->alias,
- table->s->table_cache_key.str,
- table->s->table_cache_key.length,
- thd->mem_root, 0))
+ if (reopen_table_entry(thd, &tmp, &table_list,
+ table->alias,
+ table->s->table_cache_key.str,
+ table->s->table_cache_key.length))
goto end;
/* This list copies variables set by open_table */
@@ -3112,6 +3242,12 @@ bool reopen_table(TABLE *table)
(void) closefrm(&tmp, 1); // close file, free everything
goto end;
}
+ tmp.mdl_lock= table->mdl_lock;
+
+ table_def_change_share(table, tmp.s);
+ /* Avoid wiping out TABLE's position in new share's used tables list. */
+ tmp.share_next= table->share_next;
+ tmp.share_prev= table->share_prev;
delete table->triggers;
if (table->file)
@@ -3214,6 +3350,7 @@ void close_data_files_and_morph_locks(THD *thd, const char *db,
mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
}
table->open_placeholder= 1;
+ table->s->version= 0;
close_handle_and_leave_table_as_lock(table);
}
}
@@ -3282,8 +3419,6 @@ static bool reattach_merge(THD *thd, TABLE **err_tables_p)
@param thd Thread context
@param get_locks Should we get locks after reopening tables ?
- @param mark_share_as_old Mark share as old to protect from a impending
- global read lock.
@note Since this function can't properly handle prelocking and
create placeholders it should be used in very special
@@ -3297,11 +3432,11 @@ static bool reattach_merge(THD *thd, TABLE **err_tables_p)
@return FALSE in case of success, TRUE - otherwise.
*/
-bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old)
+bool reopen_tables(THD *thd, bool get_locks)
{
TABLE *table,*next,**prev;
TABLE **tables,**tables_ptr; // For locks
- TABLE *err_tables= NULL;
+ TABLE *err_tables= NULL, *err_tab_tmp;
bool error=0, not_used;
bool merge_table_found= FALSE;
const uint flags= MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN |
@@ -3356,7 +3491,7 @@ bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old)
*/
if (table->child_l || table->parent)
detach_merge_children(table, TRUE);
- my_hash_delete(&open_cache,(uchar*) table);
+ free_cache_entry(table);
error=1;
}
else
@@ -3367,11 +3502,15 @@ bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old)
prev= &table->next;
/* Do not handle locks of MERGE children. */
if (get_locks && !db_stat && !table->parent)
- *tables_ptr++= table; // need new lock on this
- if (mark_share_as_old)
{
- table->s->version=0;
- table->open_placeholder= 0;
+ *tables_ptr++= table; // need new lock on this
+ /*
+ We rely on having exclusive metadata lock on the table to be
+ able safely re-acquire table locks on it.
+ */
+ DBUG_ASSERT(mdl_is_exclusive_lock_owner(&thd->mdl_context, 0,
+ table->s->db.str,
+ table->s->table_name.str));
}
}
}
@@ -3385,8 +3524,9 @@ bool reopen_tables(THD *thd, bool get_locks, bool mark_share_as_old)
{
while (err_tables)
{
- my_hash_delete(&open_cache, (uchar*) err_tables);
- err_tables= err_tables->next;
+ err_tab_tmp= err_tables->next;
+ free_cache_entry(err_tables);
+ err_tables= err_tab_tmp;
}
}
DBUG_PRINT("tcache", ("open tables to lock: %u",
@@ -3534,41 +3674,24 @@ bool table_is_used(TABLE *table, bool wait_for_name_lock)
{
char *key= table->s->table_cache_key.str;
uint key_length= table->s->table_cache_key.length;
-
- DBUG_PRINT("loop", ("table_name: %s", table->alias));
- HASH_SEARCH_STATE state;
- for (TABLE *search= (TABLE*) my_hash_first(&open_cache, (uchar*) key,
- key_length, &state);
- search ;
- search= (TABLE*) my_hash_next(&open_cache, (uchar*) key,
- key_length, &state))
- {
- DBUG_PRINT("info", ("share: 0x%lx "
- "open_placeholder: %d locked_by_name: %d "
- "db_stat: %u version: %lu",
- (ulong) search->s,
- search->open_placeholder, search->locked_by_name,
- search->db_stat,
- search->s->version));
- if (search->in_use == table->in_use)
- continue; // Name locked by this thread
- /*
- We can't use the table under any of the following conditions:
- - There is an name lock on it (Table is to be deleted or altered)
- - If we are in flush table and we didn't execute the flush
- - If the table engine is open and it's an old version
- (We must wait until all engines are shut down to use the table)
- */
- if ( (search->locked_by_name && wait_for_name_lock) ||
- (search->is_name_opened() && search->needs_reopen_or_name_lock()))
- DBUG_RETURN(1);
- }
+ /* Note that 'table' can use artificial TABLE_SHARE object. */
+ TABLE_SHARE *share= (TABLE_SHARE*)my_hash_search(&table_def_cache,
+ (uchar*) key, key_length);
+ if (share && !share->used_tables.is_empty() &&
+ share->version != refresh_version)
+ DBUG_RETURN(1);
} while ((table=table->next));
DBUG_RETURN(0);
}
-/* Wait until all used tables are refreshed */
+/*
+ Wait until all used tables are refreshed.
+
+ FIXME We should remove this function since for several functions which
+ are invoked by it new scenarios of usage are introduced, while
+ this function implements optimization useful only in rare cases.
+*/
bool wait_for_tables(THD *thd)
{
@@ -3593,7 +3716,7 @@ bool wait_for_tables(THD *thd)
/* Now we can open all tables without any interference */
thd_proc_info(thd, "Reopen tables");
thd->version= refresh_version;
- result=reopen_tables(thd,0,0);
+ result=reopen_tables(thd, 0);
}
pthread_mutex_unlock(&LOCK_open);
thd_proc_info(thd, 0);
@@ -3601,111 +3724,27 @@ bool wait_for_tables(THD *thd)
}
-/*
- drop tables from locked list
-
- SYNOPSIS
- drop_locked_tables()
- thd Thread thandler
- db Database
- table_name Table name
-
- INFORMATION
- This is only called on drop tables
-
- The TABLE object for the dropped table is unlocked but still kept around
- as a name lock, which means that the table will be available for other
- thread as soon as we call unlock_table_names().
- If there is multiple copies of the table locked, all copies except
- the first, which acts as a name lock, is removed.
+/**
+ Unlock and close tables open and locked by LOCK TABLES statement.
- RETURN
- # If table existed, return table
- 0 Table was not locked
+ @param thd Current thread's context.
*/
-
-TABLE *drop_locked_tables(THD *thd,const char *db, const char *table_name)
+void unlock_locked_tables(THD *thd)
{
- TABLE *table,*next,**prev, *found= 0;
- prev= &thd->open_tables;
- DBUG_ENTER("drop_locked_tables");
-
- /*
- Note that we need to hold LOCK_open while changing the
- open_tables list. Another thread may work on it.
- (See: remove_table_from_cache(), mysql_wait_completed_table())
- Closing a MERGE child before the parent would be fatal if the
- other thread tries to abort the MERGE lock in between.
- */
- for (table= thd->open_tables; table ; table=next)
- {
- next=table->next;
- if (!strcmp(table->s->table_name.str, table_name) &&
- !strcmp(table->s->db.str, db))
- {
- /* If MERGE child, forward lock handling to parent. */
- mysql_lock_remove(thd, thd->locked_tables,
- table->parent ? table->parent : table, TRUE);
- /*
- When closing a MERGE parent or child table, detach the children first.
- Clear child table references in case this object is opened again.
- */
- if (table->child_l || table->parent)
- detach_merge_children(table, TRUE);
+ DBUG_ASSERT(!thd->in_sub_stmt &&
+ !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
- if (!found)
- {
- found= table;
- /* Close engine table, but keep object around as a name lock */
- if (table->db_stat)
- {
- table->db_stat= 0;
- table->file->close();
- }
- }
- else
- {
- /* We already have a name lock, remove copy */
- my_hash_delete(&open_cache,(uchar*) table);
- }
- }
- else
- {
- *prev=table;
- prev= &table->next;
- }
- }
- *prev=0;
- if (found)
- broadcast_refresh();
- if (thd->locked_tables && thd->locked_tables->table_count == 0)
+ if (thd->locked_tables)
{
- my_free((uchar*) thd->locked_tables,MYF(0));
+ thd->lock= thd->locked_tables;
thd->locked_tables=0;
- }
- DBUG_RETURN(found);
-}
-
-
-/*
- If we have the table open, which only happens when a LOCK TABLE has been
- done on the table, change the lock type to a lock that will abort all
- other threads trying to get the lock.
-*/
-
-void abort_locked_tables(THD *thd,const char *db, const char *table_name)
-{
- TABLE *table;
- for (table= thd->open_tables; table ; table= table->next)
- {
- if (!strcmp(table->s->table_name.str, table_name) &&
- !strcmp(table->s->db.str, db))
- {
- /* If MERGE child, forward lock handling to parent. */
- mysql_lock_abort(thd, table->parent ? table->parent : table, TRUE);
- break;
- }
+ close_thread_tables(thd);
+ /*
+ After closing tables we can free memory used for storing lock
+ request objects for metadata locks
+ */
+ free_root(&thd->locked_tables_root, MYF(MY_MARK_BLOCKS_FREE));
}
}
@@ -3812,7 +3851,7 @@ static bool inject_reprepare(THD *thd)
@retval FALSE success, version in TABLE_LIST has been updated
*/
-bool
+static bool
check_and_update_table_version(THD *thd,
TABLE_LIST *tables, TABLE_SHARE *table_share)
{
@@ -3838,41 +3877,98 @@ check_and_update_table_version(THD *thd,
return FALSE;
}
-/*
- Load a table definition from file and open unireg table
- SYNOPSIS
- open_unireg_entry()
- thd Thread handle
- entry Store open table definition here
- table_list TABLE_LIST with db, table_name & belong_to_view
- alias Alias name
- cache_key Key for share_cache
- cache_key_length length of cache_key
- mem_root temporary mem_root for parsing
- flags the OPEN_VIEW_NO_PARSE flag to be passed to
- openfrm()/open_new_frm()
+/**
+ Open view by getting its definition from disk (and table cache in future).
- NOTES
- Extra argument for open is taken from thd->open_options
- One must have a lock on LOCK_open when calling this function
+ @param thd Thread handle
+ @param table_list TABLE_LIST with db, table_name & belong_to_view
+ @param alias Alias name
+ @param cache_key Key for table definition cache
+ @param cache_key_length Length of cache_key
+ @param mem_root Memory to be used for .frm parsing.
+ @param flags Flags which modify how we open the view
- RETURN
- 0 ok
- # Error
+ @todo This function is needed for special handling of views under
+ LOCK TABLES. We probably should get rid of it in long term.
+
+ @return FALSE if success, TRUE - otherwise.
*/
-static int open_unireg_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list,
- const char *alias,
- char *cache_key, uint cache_key_length,
- MEM_ROOT *mem_root, uint flags)
+bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias,
+ char *cache_key, uint cache_key_length,
+ MEM_ROOT *mem_root, uint flags)
+{
+ TABLE not_used;
+ int error;
+ TABLE_SHARE *share;
+
+ pthread_mutex_lock(&LOCK_open);
+
+ if (!(share= get_table_share_with_create(thd, table_list, cache_key,
+ cache_key_length,
+ OPEN_VIEW, &error)))
+ goto err;
+
+ if (share->is_view &&
+ !open_new_frm(thd, share, alias,
+ (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
+ HA_GET_INDEX | HA_TRY_READ_ONLY),
+ READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD |
+ flags, thd->open_options, &not_used, table_list,
+ mem_root))
+ {
+ release_table_share(share, RELEASE_NORMAL);
+ pthread_mutex_unlock(&LOCK_open);
+ return FALSE;
+ }
+
+ my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str, "VIEW");
+ release_table_share(share, RELEASE_NORMAL);
+err:
+ pthread_mutex_unlock(&LOCK_open);
+ return TRUE;
+}
+
+
+/**
+ Load table definition from file and open table while holding exclusive
+ meta-data lock on it.
+
+ @param thd Thread handle
+ @param entry Memory for TABLE object to be created
+ @param table_list TABLE_LIST with db, table_name & belong_to_view
+ @param alias Alias name
+ @param cache_key Key for table definition cache
+ @param cache_key_length Length of cache_key
+
+ @note This auxiliary function is mostly inteded for re-opening table
+ in situations when we hold exclusive meta-data lock. It is not
+ intended for normal case in which we have only shared meta-data
+ lock on the table to be open.
+
+ @note Extra argument for open is taken from thd->open_options.
+
+ @note One must have a lock on LOCK_open as well as exclusive meta-data
+ lock on the table when calling this function.
+
+ @return FALSE in case of success, TRUE otherwise.
+*/
+
+static bool reopen_table_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list,
+ const char *alias, char *cache_key,
+ uint cache_key_length)
{
int error;
TABLE_SHARE *share;
uint discover_retry_count= 0;
- DBUG_ENTER("open_unireg_entry");
+ DBUG_ENTER("reopen_table_entry");
safe_mutex_assert_owner(&LOCK_open);
+ DBUG_ASSERT(mdl_is_exclusive_lock_owner(&thd->mdl_context, 0,
+ table_list->db,
+ table_list->table_name));
+
retry:
if (!(share= get_table_share_with_create(thd, table_list, cache_key,
cache_key_length,
@@ -3901,40 +3997,11 @@ retry:
goto err;
if (table_list->i_s_requested_object & OPEN_TABLE_ONLY)
goto err;
-
- /* Open view */
- error= (int) open_new_frm(thd, share, alias,
- (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
- HA_GET_INDEX | HA_TRY_READ_ONLY),
- READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD |
- (flags & OPEN_VIEW_NO_PARSE),
- thd->open_options, entry, table_list,
- mem_root);
- if (error)
- goto err;
- /* TODO: Don't free this */
+ /* Attempt to reopen view will bring havoc to upper layers anyway. */
release_table_share(share, RELEASE_NORMAL);
- DBUG_RETURN((flags & OPEN_VIEW_NO_PARSE)? -1 : 0);
- }
- else if (table_list->view)
- {
- /*
- We're trying to open a table for what was a view.
- This can only happen during (re-)execution.
- At prepared statement prepare the view has been opened and
- merged into the statement parse tree. After that, someone
- performed a DDL and replaced the view with a base table.
- Don't try to open the table inside a prepared statement,
- invalidate it instead.
-
- Note, the assert below is known to fail inside stored
- procedures (Bug#27011).
- */
- DBUG_ASSERT(thd->m_reprepare_observer);
- check_and_update_table_version(thd, table_list, share);
- /* Always an error. */
- DBUG_ASSERT(thd->is_error());
- goto err;
+ my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str,
+ "BASE TABLE");
+ DBUG_RETURN(1);
}
if (table_list->i_s_requested_object & OPEN_VIEW_ONLY)
@@ -3956,89 +4023,67 @@ retry:
goto err;
/*
- TODO:
- Here we should wait until all threads has released the table.
- For now we do one retry. This may cause a deadlock if there
- is other threads waiting for other tables used by this thread.
-
- Proper fix would be to if the second retry failed:
- - Mark that table def changed
- - Return from open table
- - Close all tables used by this thread
- - Start waiting that the share is released
- - Retry by opening all tables again
+ Since we have exclusive metadata lock on the table here the only
+ practical case when share->ref_count != 1 is when we have several
+ instances of the table opened by this thread (i.e we are under LOCK
+ TABLES).
*/
+ if (share->ref_count != 1)
+ goto err;
+
+ release_table_share(share, RELEASE_NORMAL);
+
if (ha_create_table_from_engine(thd, table_list->db,
table_list->table_name))
goto err;
- /*
- TO BE FIXED
- To avoid deadlock, only wait for release if no one else is
- using the share.
- */
- if (share->ref_count != 1)
- goto err;
- /* Free share and wait until it's released by all threads */
- release_table_share(share, RELEASE_WAIT_FOR_DROP);
- if (!thd->killed)
- {
- thd->warning_info->clear_warning_info(thd->query_id);
- thd->clear_error(); // Clear error message
- goto retry;
- }
- DBUG_RETURN(1);
+
+ thd->warning_info->clear_warning_info(thd->query_id);
+ thd->clear_error(); // Clear error message
+ goto retry;
}
if (!entry->s || !entry->s->crashed)
goto err;
- // Code below is for repairing a crashed file
- if ((error= lock_table_name(thd, table_list, TRUE)))
- {
- if (error < 0)
- goto err;
- if (wait_for_locked_table_names(thd, table_list))
- {
- unlock_table_name(thd, table_list);
- goto err;
- }
- }
- pthread_mutex_unlock(&LOCK_open);
- thd->clear_error(); // Clear error message
- error= 0;
- if (open_table_from_share(thd, share, alias,
- (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
- HA_GET_INDEX |
- HA_TRY_READ_ONLY),
- READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD,
- ha_open_options | HA_OPEN_FOR_REPAIR,
- entry, FALSE) || ! entry->file ||
- (entry->file->is_crashed() && entry->file->ha_check_and_repair(thd)))
- {
- /* Give right error message */
- thd->clear_error();
- my_error(ER_NOT_KEYFILE, MYF(0), share->table_name.str, my_errno);
- sql_print_error("Couldn't repair table: %s.%s", share->db.str,
- share->table_name.str);
- if (entry->file)
- closefrm(entry, 0);
- error=1;
- }
- else
- thd->clear_error(); // Clear error message
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, table_list);
-
- if (error)
- goto err;
- break;
- }
- if (Table_triggers_list::check_n_load(thd, share->db.str,
- share->table_name.str, entry, 0))
+ entry->s->version= 0;
+
+ /* TODO: We don't need to release share here. */
+ release_table_share(share, RELEASE_NORMAL);
+ pthread_mutex_unlock(&LOCK_open);
+ error= (int)auto_repair_table(thd, table_list);
+ pthread_mutex_lock(&LOCK_open);
+
+ if (error)
+ goto err;
+
+ goto retry;
+ }
+
+ if (open_table_entry_fini(thd, share, entry))
{
closefrm(entry, 0);
goto err;
}
+ DBUG_RETURN(0);
+
+err:
+ release_table_share(share, RELEASE_NORMAL);
+ DBUG_RETURN(1);
+}
+
+
+/**
+ Auxiliary routine which finalizes process of TABLE object creation
+ by loading triggers and handling implicitly emptied tables.
+*/
+
+static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry)
+{
+
+ if (Table_triggers_list::check_n_load(thd, share->db.str,
+ share->table_name.str, entry, 0))
+ return TRUE;
+
/*
If we are here, there was no fatal error (but error may be still
unitialized).
@@ -4070,18 +4115,141 @@ retry:
*/
sql_print_error("When opening HEAP table, could not allocate memory "
"to write 'DELETE FROM `%s`.`%s`' to the binary log",
- table_list->db, table_list->table_name);
+ share->db.str, share->table_name.str);
delete entry->triggers;
- closefrm(entry, 0);
- goto err;
+ return TRUE;
}
}
}
- DBUG_RETURN(0);
+ return FALSE;
+}
-err:
+
+/**
+ Auxiliary routine which is used for performing automatical table repair.
+*/
+
+static bool auto_repair_table(THD *thd, TABLE_LIST *table_list)
+{
+ char cache_key[MAX_DBKEY_LENGTH];
+ uint cache_key_length;
+ TABLE_SHARE *share;
+ TABLE *entry;
+ int not_used;
+ bool result= FALSE;
+
+ cache_key_length= create_table_def_key(thd, cache_key, table_list, 0);
+
+ thd->clear_error();
+
+ pthread_mutex_lock(&LOCK_open);
+
+ if (!(share= get_table_share_with_create(thd, table_list, cache_key,
+ cache_key_length,
+ OPEN_VIEW, &not_used)))
+ {
+ pthread_mutex_unlock(&LOCK_open);
+ return TRUE;
+ }
+
+ if (share->is_view)
+ goto end_with_lock_open;
+
+ if (!(entry= (TABLE*)my_malloc(sizeof(TABLE), MYF(MY_WME))))
+ {
+ result= TRUE;
+ goto end_with_lock_open;
+ }
+ share->version= 0;
+ pthread_mutex_unlock(&LOCK_open);
+
+ if (open_table_from_share(thd, share, table_list->alias,
+ (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
+ HA_GET_INDEX |
+ HA_TRY_READ_ONLY),
+ READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD,
+ ha_open_options | HA_OPEN_FOR_REPAIR,
+ entry, FALSE) || ! entry->file ||
+ (entry->file->is_crashed() && entry->file->ha_check_and_repair(thd)))
+ {
+ /* Give right error message */
+ thd->clear_error();
+ my_error(ER_NOT_KEYFILE, MYF(0), share->table_name.str, my_errno);
+ sql_print_error("Couldn't repair table: %s.%s", share->db.str,
+ share->table_name.str);
+ if (entry->file)
+ closefrm(entry, 0);
+ result= TRUE;
+ }
+ else
+ {
+ thd->clear_error(); // Clear error message
+ closefrm(entry, 0);
+ }
+ my_free(entry, MYF(0));
+
+ pthread_mutex_lock(&LOCK_open);
+
+end_with_lock_open:
release_table_share(share, RELEASE_NORMAL);
- DBUG_RETURN(1);
+ pthread_mutex_unlock(&LOCK_open);
+ return result;
+}
+
+
+/**
+ Handle failed attempt ot open table by performing requested action.
+
+ @param thd Thread context
+ @param table Table list element for table that caused problem
+ @param action Type of action requested by failed open_table() call
+
+ @retval FALSE - Success. One should try to open tables once again.
+ @retval TRUE - Error
+*/
+
+static bool handle_failed_open_table_attempt(THD *thd, TABLE_LIST *table,
+ enum_open_table_action action)
+{
+ bool result= FALSE;
+
+ switch (action)
+ {
+ case OT_BACK_OFF_AND_RETRY:
+ result= (mdl_wait_for_locks(&thd->mdl_context) ||
+ tdc_wait_for_old_versions(thd, &thd->mdl_context));
+ mdl_remove_all_locks(&thd->mdl_context);
+ break;
+ case OT_DISCOVER:
+ mdl_set_lock_type(table->mdl_lock, MDL_EXCLUSIVE);
+ mdl_add_lock(&thd->mdl_context, table->mdl_lock);
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+ return TRUE;
+ pthread_mutex_lock(&LOCK_open);
+ expel_table_from_cache(0, table->db, table->table_name);
+ ha_create_table_from_engine(thd, table->db, table->table_name);
+ pthread_mutex_unlock(&LOCK_open);
+
+ thd->warning_info->clear_warning_info(thd->query_id);
+ thd->clear_error(); // Clear error message
+ mdl_release_exclusive_locks(&thd->mdl_context);
+ break;
+ case OT_REPAIR:
+ mdl_set_lock_type(table->mdl_lock, MDL_EXCLUSIVE);
+ mdl_add_lock(&thd->mdl_context, table->mdl_lock);
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+ return TRUE;
+ pthread_mutex_lock(&LOCK_open);
+ expel_table_from_cache(0, table->db, table->table_name);
+ pthread_mutex_unlock(&LOCK_open);
+
+ result= auto_repair_table(thd, table);
+ mdl_release_exclusive_locks(&thd->mdl_context);
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+ return result;
}
@@ -4132,6 +4300,7 @@ static int add_merge_table_list(TABLE_LIST *tlist)
/* Set lock type. */
child_l->lock_type= tlist->lock_type;
+ child_l->mdl_upgradable= tlist->mdl_upgradable;
/* Set parent reference. */
child_l->parent_l= tlist;
@@ -4487,7 +4656,7 @@ thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table)
int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
{
TABLE_LIST *tables= NULL;
- bool refresh;
+ enum_open_table_action action;
int result=0;
MEM_ROOT new_frm_mem;
/* Also used for indicating that prelocking is need */
@@ -4508,6 +4677,18 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
thd_proc_info(thd, "Opening tables");
/*
+ Close HANDLER tables which are marked for flush or against which there
+ are pending exclusive metadata locks. Note that we do this not to avoid
+ deadlocks (calls to mysql_ha_flush() in mdl_wait_for_locks() and
+ tdc_wait_for_old_version() are enough for this) but in order to have
+ a point during statement execution at which such HANDLERs are closed
+ even if they don't create problems for current thread (i.e. to avoid
+ having DDL blocked by HANDLERs opened for long time).
+ */
+ if (thd->handler_tables)
+ mysql_ha_flush(thd);
+
+ /*
If we are not already executing prelocked statement and don't have
statement for which table list for prelocking is already built, let
us cache routines and try to build such table list.
@@ -4556,9 +4737,18 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
*/
if (tables->derived)
{
- if (tables->view)
- goto process_view_routines;
- continue;
+ if (!tables->view)
+ continue;
+ /*
+ We restore view's name and database wiped out by derived tables
+ processing and fall back to standard open process in order to
+ obtain proper metadata locks and do other necessary steps like
+ stored routine processing.
+ */
+ tables->db= tables->view_db.str;
+ tables->db_length= tables->view_db.length;
+ tables->table_name= tables->view_name.str;
+ tables->table_name_length= tables->view_name.length;
}
/*
If this TABLE_LIST object is a placeholder for an information_schema
@@ -4602,12 +4792,12 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
*/
Prelock_error_handler prelock_handler;
thd->push_internal_handler(& prelock_handler);
- tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags);
+ tables->table= open_table(thd, tables, &new_frm_mem, &action, flags);
thd->pop_internal_handler();
safe_to_ignore_table= prelock_handler.safely_trapped_errors();
}
else
- tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags);
+ tables->table= open_table(thd, tables, &new_frm_mem, &action, flags);
}
else
DBUG_PRINT("tcache", ("referenced table: '%s'.'%s' 0x%lx",
@@ -4657,7 +4847,16 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
parent_l->next_global= *parent_l->table->child_last_l;
}
- if (refresh) // Refresh in progress
+ /*
+ FIXME This is a temporary hack. Actually we need check that will
+ allow us to differentiate between error while opening/creating
+ table and successful table creation.
+ ...
+ */
+ if (tables->open_table_type)
+ continue;
+
+ if (action)
{
/*
We have met name-locked or old version of table. Now we have
@@ -4675,7 +4874,17 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
*/
if (query_tables_last_own)
thd->lex->mark_as_requiring_prelocking(query_tables_last_own);
- close_tables_for_reopen(thd, start);
+ close_tables_for_reopen(thd, start, (action == OT_BACK_OFF_AND_RETRY));
+ /*
+ Here we rely on the fact that 'tables' still points to the valid
+ TABLE_LIST element. Altough currently this assumption is valid
+ it may change in future.
+ */
+ if (handle_failed_open_table_attempt(thd, tables, action))
+ {
+ result= -1;
+ goto err;
+ }
goto restart;
}
@@ -4929,6 +5138,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type,
uint lock_flags)
{
TABLE *table;
+ enum_open_table_action action;
bool refresh;
DBUG_ENTER("open_ltable");
@@ -4939,9 +5149,20 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type,
thd->current_tablenr= 0;
/* open_ltable can be used only for BASIC TABLEs */
table_list->required_type= FRMTYPE_TABLE;
- while (!(table= open_table(thd, table_list, thd->mem_root, &refresh, 0)) &&
- refresh)
- ;
+
+retry:
+ while (!(table= open_table(thd, table_list, thd->mem_root, &action, 0)) &&
+ action)
+ {
+ /*
+ Even altough we have failed to open table we still need to
+ call close_thread_tables() to release metadata locks which
+ might have been acquired successfully.
+ */
+ close_thread_tables(thd, (action == OT_BACK_OFF_AND_RETRY));
+ if (handle_failed_open_table_attempt(thd, table_list, action))
+ break;
+ }
if (table)
{
@@ -4969,8 +5190,22 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type,
DBUG_ASSERT(thd->lock == 0); // You must lock everything at once
if ((table->reginfo.lock_type= lock_type) != TL_UNLOCK)
if (! (thd->lock= mysql_lock_tables(thd, &table_list->table, 1,
- lock_flags, &refresh)))
- table= 0;
+ (lock_flags |
+ MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN),
+ &refresh)))
+ {
+ /*
+ FIXME: Actually we should get rid of MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN option
+ as all reopening should happen outside of mysql_lock_tables() code.
+ */
+ if (refresh)
+ {
+ close_thread_tables(thd);
+ goto retry;
+ }
+ else
+ table= 0;
+ }
}
}
@@ -5026,7 +5261,7 @@ int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived)
break;
if (!need_reopen)
DBUG_RETURN(-1);
- close_tables_for_reopen(thd, &tables);
+ close_tables_for_reopen(thd, &tables, FALSE);
}
if (derived &&
(mysql_handle_derived(thd->lex, &mysql_derived_prepare) ||
@@ -5383,6 +5618,10 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
table->table->query_id= thd->query_id;
if (check_lock_and_start_stmt(thd, table->table, table->lock_type))
{
+ /*
+ This was an attempt to enter prelocked mode so there is no
+ need to care about THD::locked_tables_root here.
+ */
mysql_unlock_tables(thd, thd->locked_tables);
thd->locked_tables= 0;
thd->options&= ~(OPTION_TABLE_LOCK);
@@ -5469,7 +5708,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
*/
-void close_tables_for_reopen(THD *thd, TABLE_LIST **tables)
+void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, bool skip_mdl)
{
/*
If table list consists only from tables from prelocking set, table list
@@ -5481,7 +5720,7 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables)
sp_remove_not_own_routines(thd->lex);
for (TABLE_LIST *tmp= *tables; tmp; tmp= tmp->next_global)
tmp->table= 0;
- close_thread_tables(thd);
+ close_thread_tables(thd, skip_mdl);
}
@@ -8393,36 +8632,6 @@ my_bool mysql_rm_tmp_tables(void)
*****************************************************************************/
/*
- Invalidate any cache entries that are for some DB
-
- SYNOPSIS
- remove_db_from_cache()
- db Database name. This will be in lower case if
- lower_case_table_name is set
-
- NOTE:
- We can't use hash_delete when looping hash_elements. We mark them first
- and afterwards delete those marked unused.
-*/
-
-void remove_db_from_cache(const char *db)
-{
- for (uint idx=0 ; idx < open_cache.records ; idx++)
- {
- TABLE *table=(TABLE*) my_hash_element(&open_cache,idx);
- if (!strcmp(table->s->db.str, db))
- {
- table->s->version= 0L; /* Free when thread is ready */
- if (!table->in_use)
- relink_unused(table);
- }
- }
- while (unused_tables && !unused_tables->s->version)
- my_hash_delete(&open_cache,(uchar*) unused_tables);
-}
-
-
-/*
free all unused tables
NOTE
@@ -8434,7 +8643,7 @@ void flush_tables()
{
(void) pthread_mutex_lock(&LOCK_open);
while (unused_tables)
- my_hash_delete(&open_cache,(uchar*) unused_tables);
+ free_cache_entry(unused_tables);
(void) pthread_mutex_unlock(&LOCK_open);
}
@@ -8468,90 +8677,82 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name,
key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1;
for (;;)
{
- HASH_SEARCH_STATE state;
result= signalled= 0;
- for (table= (TABLE*) my_hash_first(&open_cache, (uchar*) key, key_length,
- &state);
- table;
- table= (TABLE*) my_hash_next(&open_cache, (uchar*) key, key_length,
- &state))
+ if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache, (uchar*) key,
+ key_length)))
{
- THD *in_use;
- DBUG_PRINT("tcache", ("found table: '%s'.'%s' 0x%lx", table->s->db.str,
- table->s->table_name.str, (long) table));
-
- table->s->version=0L; /* Free when thread is ready */
- if (!(in_use=table->in_use))
- {
- DBUG_PRINT("info",("Table was not in use"));
+ I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables);
+ share->version= 0;
+ while ((table= it++))
relink_unused(table);
- }
- else if (in_use != thd)
+
+ it.init(share->used_tables);
+ while ((table= it++))
{
- DBUG_PRINT("info", ("Table was in use by other thread"));
- /*
- Mark that table is going to be deleted from cache. This will
- force threads that are in mysql_lock_tables() (but not yet
- in thr_multi_lock()) to abort it's locks, close all tables and retry
- */
- in_use->some_tables_deleted= 1;
- if (table->is_name_opened())
- {
- DBUG_PRINT("info", ("Found another active instance of the table"));
- result=1;
- }
- /* Kill delayed insert threads */
- if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
- ! in_use->killed)
+ THD *in_use= table->in_use;
+ DBUG_ASSERT(in_use);
+ if (in_use != thd)
{
- in_use->killed= THD::KILL_CONNECTION;
- pthread_mutex_lock(&in_use->mysys_var->mutex);
- if (in_use->mysys_var->current_cond)
- {
- pthread_mutex_lock(in_use->mysys_var->current_mutex);
- signalled= 1;
- pthread_cond_broadcast(in_use->mysys_var->current_cond);
- pthread_mutex_unlock(in_use->mysys_var->current_mutex);
- }
- pthread_mutex_unlock(&in_use->mysys_var->mutex);
+ DBUG_PRINT("info", ("Table was in use by other thread"));
+ /*
+ Mark that table is going to be deleted from cache. This will
+ force threads that are in mysql_lock_tables() (but not yet
+ in thr_multi_lock()) to abort it's locks, close all tables and retry
+ */
+ in_use->some_tables_deleted= 1;
+
+ if (table->is_name_opened())
+ {
+ DBUG_PRINT("info", ("Found another active instance of the table"));
+ result=1;
+ }
+ /* Kill delayed insert threads */
+ if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
+ ! in_use->killed)
+ {
+ in_use->killed= THD::KILL_CONNECTION;
+ pthread_mutex_lock(&in_use->mysys_var->mutex);
+ if (in_use->mysys_var->current_cond)
+ {
+ pthread_mutex_lock(in_use->mysys_var->current_mutex);
+ signalled= 1;
+ pthread_cond_broadcast(in_use->mysys_var->current_cond);
+ pthread_mutex_unlock(in_use->mysys_var->current_mutex);
+ }
+ pthread_mutex_unlock(&in_use->mysys_var->mutex);
+ }
+ /*
+ Now we must abort all tables locks used by this thread
+ as the thread may be waiting to get a lock for another table.
+ Note that we need to hold LOCK_open while going through the
+ list. So that the other thread cannot change it. The other
+ thread must also hold LOCK_open whenever changing the
+ open_tables list. Aborting the MERGE lock after a child was
+ closed and before the parent is closed would be fatal.
+ */
+ for (TABLE *thd_table= in_use->open_tables;
+ thd_table ;
+ thd_table= thd_table->next)
+ {
+ /* Do not handle locks of MERGE children. */
+ if (thd_table->db_stat && !thd_table->parent) // If table is open
+ signalled|= mysql_lock_abort_for_thread(thd, thd_table);
+ }
}
- /*
- Now we must abort all tables locks used by this thread
- as the thread may be waiting to get a lock for another table.
- Note that we need to hold LOCK_open while going through the
- list. So that the other thread cannot change it. The other
- thread must also hold LOCK_open whenever changing the
- open_tables list. Aborting the MERGE lock after a child was
- closed and before the parent is closed would be fatal.
- */
- for (TABLE *thd_table= in_use->open_tables;
- thd_table ;
- thd_table= thd_table->next)
+ else
{
- /* Do not handle locks of MERGE children. */
- if (thd_table->db_stat && !thd_table->parent) // If table is open
- signalled|= mysql_lock_abort_for_thread(thd, thd_table);
+ DBUG_PRINT("info", ("Table was in use by current thread. db_stat: %u",
+ table->db_stat));
+ result= result || (flags & RTFC_OWNED_BY_THD_FLAG);
}
}
- else
- {
- DBUG_PRINT("info", ("Table was in use by current thread. db_stat: %u",
- table->db_stat));
- result= result || (flags & RTFC_OWNED_BY_THD_FLAG);
- }
- }
- while (unused_tables && !unused_tables->s->version)
- my_hash_delete(&open_cache,(uchar*) unused_tables);
- DBUG_PRINT("info", ("Removing table from table_def_cache"));
- /* Remove table from table definition cache if it's not in use */
- if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key,
- key_length)))
- {
+ while (unused_tables && !unused_tables->s->version)
+ free_cache_entry(unused_tables);
+
DBUG_PRINT("info", ("share version: %lu ref_count: %u",
share->version, share->ref_count));
- share->version= 0; // Mark for delete
if (share->ref_count == 0)
{
pthread_mutex_lock(&share->mutex);
@@ -8598,6 +8799,160 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name,
}
+/**
+ A callback to the server internals that is used to address
+ special cases of the locking protocol.
+ Invoked when acquiring an exclusive lock, for each thread that
+ has a conflicting shared metadata lock.
+
+ This function:
+ - aborts waiting of the thread on a data lock, to make it notice
+ the pending exclusive lock and back off.
+ - if the thread is an INSERT DELAYED thread, sends it a KILL
+ signal to terminate it.
+
+ @note This function does not wait for the thread to give away its
+ locks. Waiting is done outside for all threads at once.
+
+ @param thd Current thread context
+ @param in_use The thread to wake up
+
+ @retval TRUE if the thread was woken up
+ @retval FALSE otherwise (e.g. it was not waiting for a table-level lock).
+
+ @note It is one of two places where border between MDL and the
+ rest of the server is broken.
+*/
+
+bool notify_thread_having_shared_lock(THD *thd, THD *in_use)
+{
+ bool signalled= FALSE;
+ if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
+ !in_use->killed)
+ {
+ in_use->killed= THD::KILL_CONNECTION;
+ pthread_mutex_lock(&in_use->mysys_var->mutex);
+ if (in_use->mysys_var->current_cond)
+ pthread_cond_broadcast(in_use->mysys_var->current_cond);
+ pthread_mutex_unlock(&in_use->mysys_var->mutex);
+ signalled= TRUE;
+ }
+ pthread_mutex_lock(&LOCK_open);
+ for (TABLE *thd_table= in_use->open_tables;
+ thd_table ;
+ thd_table= thd_table->next)
+ {
+ /* TODO With new MDL check for db_stat is probably a legacy */
+ if (thd_table->db_stat)
+ signalled|= mysql_lock_abort_for_thread(thd, thd_table);
+ }
+ pthread_mutex_unlock(&LOCK_open);
+ return signalled;
+}
+
+
+/**
+ Remove all instances of the table from cache assuming that current thread
+ has exclusive meta-data lock on it (optionally leave instances belonging
+ to the current thread in cache).
+
+ @param leave_thd 0 If we should remove all instances
+ non-0 Pointer to current thread context if we should
+ leave instances belonging to this thread.
+ @param db Name of database
+ @param table_name Name of table
+
+ @note Unlike remove_table_from_cache() it assumes that table instances
+ are already not used by any (other) thread (this should be achieved
+ by using meta-data locks).
+*/
+
+void expel_table_from_cache(THD *leave_thd, const char *db, const char *table_name)
+{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
+ TABLE *table;
+ TABLE_SHARE *share;
+
+ key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1;
+
+ if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key,
+ key_length)))
+ {
+ I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables);
+ share->version= 0;
+
+ while ((table= it++))
+ relink_unused(table);
+ }
+
+ /* This may destroy share so we have to do new look-up later. */
+ while (unused_tables && !unused_tables->s->version)
+ free_cache_entry(unused_tables);
+
+ if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key,
+ key_length)))
+ {
+ DBUG_ASSERT(leave_thd || share->ref_count == 0);
+ if (share->ref_count == 0)
+ {
+ pthread_mutex_lock(&share->mutex);
+ my_hash_delete(&table_def_cache, (uchar*) share);
+ }
+ }
+}
+
+
+/**
+ Wait until there are no old versions of tables in the table
+ definition cache for the metadata locks that we try to acquire.
+
+ @param thd Thread context
+ @param context Metadata locking context with locks.
+*/
+
+static bool tdc_wait_for_old_versions(THD *thd, MDL_CONTEXT *context)
+{
+ MDL_LOCK *l;
+ TABLE_SHARE *share;
+ const char *old_msg;
+ LEX_STRING key;
+
+ while (!thd->killed)
+ {
+ /*
+ Here we have situation as in mdl_wait_for_locks() we need to
+ get rid of offending HANDLERs to avoid deadlock.
+ TODO: We should also investigate in which situations we have
+ to broadcast on COND_refresh because of this.
+ */
+ mysql_ha_flush(thd);
+ pthread_mutex_lock(&LOCK_open);
+
+ I_P_List_iterator<MDL_LOCK, MDL_LOCK_context> it= mdl_get_locks(context);
+ while ((l= it++))
+ {
+ mdl_get_tdc_key(l, &key);
+ if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache, (uchar*) key.str,
+ key.length)) &&
+ share->version != refresh_version &&
+ !share->used_tables.is_empty())
+ break;
+ }
+ if (!l)
+ {
+ pthread_mutex_unlock(&LOCK_open);
+ break;
+ }
+ old_msg= thd->enter_cond(&COND_refresh, &LOCK_open, "Waiting for table");
+ pthread_cond_wait(&COND_refresh, &LOCK_open);
+ /* LOCK_open mutex is unlocked by THD::exit_cond() as side-effect. */
+ thd->exit_cond(old_msg);
+ }
+ return thd->killed;
+}
+
+
int setup_ftfuncs(SELECT_LEX *select_lex)
{
List_iterator<Item_func_match> li(*(select_lex->ftfunc_list)),
@@ -8695,7 +9050,6 @@ open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias,
}
err:
- bzero(outparam, sizeof(TABLE)); // do not run repair
DBUG_RETURN(1);
}
@@ -8728,15 +9082,23 @@ bool is_equal(const LEX_STRING *a, const LEX_STRING *b)
int abort_and_upgrade_lock(ALTER_PARTITION_PARAM_TYPE *lpt)
{
- uint flags= RTFC_WAIT_OTHER_THREAD_FLAG | RTFC_CHECK_KILLED_FLAG;
DBUG_ENTER("abort_and_upgrade_locks");
lpt->old_lock_type= lpt->table->reginfo.lock_type;
- pthread_mutex_lock(&LOCK_open);
/* If MERGE child, forward lock handling to parent. */
mysql_lock_abort(lpt->thd, lpt->table->parent ? lpt->table->parent :
lpt->table, TRUE);
- (void) remove_table_from_cache(lpt->thd, lpt->db, lpt->table_name, flags);
+ if (mdl_upgrade_shared_lock_to_exclusive(&lpt->thd->mdl_context, 0,
+ lpt->db, lpt->table_name))
+ {
+ mysql_lock_downgrade_write(lpt->thd,
+ lpt->table->parent ? lpt->table->parent :
+ lpt->table,
+ lpt->old_lock_type);
+ DBUG_RETURN(1);
+ }
+ pthread_mutex_lock(&LOCK_open);
+ expel_table_from_cache(lpt->thd, lpt->db, lpt->table_name);
pthread_mutex_unlock(&LOCK_open);
DBUG_RETURN(0);
}
@@ -8771,106 +9133,12 @@ void close_open_tables_and_downgrade(ALTER_PARTITION_PARAM_TYPE *lpt)
/*
- SYNOPSIS
- mysql_wait_completed_table()
- lpt Parameter passing struct
- my_table My table object
- All parameters passed through the ALTER_PARTITION_PARAM object
- RETURN VALUES
- TRUE Failure
- FALSE Success
- DESCRIPTION
- We have changed the frm file and now we want to wait for all users of
- the old frm to complete before proceeding to ensure that no one
- remains that uses the old frm definition.
- Start by ensuring that all users of the table will be removed from cache
- once they are done. Then abort all that have stumbled on locks and
- haven't been started yet.
-
- thd Thread object
- table Table object
- db Database name
- table_name Table name
-*/
+ Tells if two (or more) tables have auto_increment columns and we want to
+ lock those tables with a write lock.
-void mysql_wait_completed_table(ALTER_PARTITION_PARAM_TYPE *lpt, TABLE *my_table)
-{
- char key[MAX_DBKEY_LENGTH];
- uint key_length;
- TABLE *table;
- DBUG_ENTER("mysql_wait_completed_table");
-
- key_length=(uint) (strmov(strmov(key,lpt->db)+1,lpt->table_name)-key)+1;
- pthread_mutex_lock(&LOCK_open);
- HASH_SEARCH_STATE state;
- for (table= (TABLE*) my_hash_first(&open_cache,(uchar*) key,key_length,
- &state) ;
- table;
- table= (TABLE*) my_hash_next(&open_cache,(uchar*) key,key_length,
- &state))
- {
- THD *in_use= table->in_use;
- table->s->version= 0L;
- if (!in_use)
- {
- relink_unused(table);
- }
- else
- {
- /* Kill delayed insert threads */
- if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
- ! in_use->killed)
- {
- in_use->killed= THD::KILL_CONNECTION;
- pthread_mutex_lock(&in_use->mysys_var->mutex);
- if (in_use->mysys_var->current_cond)
- {
- pthread_mutex_lock(in_use->mysys_var->current_mutex);
- pthread_cond_broadcast(in_use->mysys_var->current_cond);
- pthread_mutex_unlock(in_use->mysys_var->current_mutex);
- }
- pthread_mutex_unlock(&in_use->mysys_var->mutex);
- }
- /*
- Now we must abort all tables locks used by this thread
- as the thread may be waiting to get a lock for another table.
- Note that we need to hold LOCK_open while going through the
- list. So that the other thread cannot change it. The other
- thread must also hold LOCK_open whenever changing the
- open_tables list. Aborting the MERGE lock after a child was
- closed and before the parent is closed would be fatal.
- */
- for (TABLE *thd_table= in_use->open_tables;
- thd_table ;
- thd_table= thd_table->next)
- {
- /* Do not handle locks of MERGE children. */
- if (thd_table->db_stat && !thd_table->parent) // If table is open
- mysql_lock_abort_for_thread(lpt->thd, thd_table);
- }
- }
- }
- /*
- We start by removing all unused objects from the cache and marking
- those in use for removal after completion. Now we also need to abort
- all that are locked and are not progressing due to being locked
- by our lock. We don't upgrade our lock here.
- If MERGE child, forward lock handling to parent.
- */
- mysql_lock_abort(lpt->thd, my_table->parent ? my_table->parent : my_table,
- FALSE);
- pthread_mutex_unlock(&LOCK_open);
- DBUG_VOID_RETURN;
-}
-
-
-/*
- Check if one (or more) write tables have auto_increment columns.
-
- @param[in] tables Table list
-
- @retval 0 if at least one write tables has an auto_increment column
- @retval 1 otherwise
+ SYNOPSIS
+ has_two_write_locked_tables_with_auto_increment
+ tables Table list
NOTES:
Call this function only when you have established the list of all tables
@@ -8924,10 +9192,13 @@ open_system_tables_for_read(THD *thd, TABLE_LIST *table_list,
{
DBUG_ENTER("open_system_tables_for_read");
+ alloc_mdl_locks(table_list, thd->mem_root);
+
thd->reset_n_backup_open_tables_state(backup);
uint count= 0;
- bool not_used;
+ enum_open_table_action not_used;
+ bool not_used_2;
for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global)
{
TABLE *table= open_table(thd, tables, thd->mem_root, &not_used,
@@ -8950,7 +9221,7 @@ open_system_tables_for_read(THD *thd, TABLE_LIST *table_list,
*(ptr++)= tables->table;
thd->lock= mysql_lock_tables(thd, list, count,
- MYSQL_LOCK_IGNORE_FLUSH, &not_used);
+ MYSQL_LOCK_IGNORE_FLUSH, &not_used_2);
}
if (thd->lock)
DBUG_RETURN(FALSE);
@@ -9002,6 +9273,8 @@ open_system_table_for_update(THD *thd, TABLE_LIST *one_table)
{
DBUG_ENTER("open_system_table_for_update");
+ alloc_mdl_locks(one_table, thd->mem_root);
+
TABLE *table= open_ltable(thd, one_table, one_table->lock_type, 0);
if (table)
{
@@ -9038,6 +9311,7 @@ open_performance_schema_table(THD *thd, TABLE_LIST *one_table,
thd->reset_n_backup_open_tables_state(backup);
+ alloc_mdl_locks(one_table, thd->mem_root);
if ((table= open_ltable(thd, one_table, one_table->lock_type, flags)))
{
DBUG_ASSERT(table->s->table_category == TABLE_CATEGORY_PERFORMANCE);
@@ -9116,6 +9390,9 @@ void close_performance_schema_table(THD *thd, Open_tables_state *backup)
pthread_mutex_unlock(&LOCK_open);
+ mdl_release_locks(&thd->mdl_context);
+ mdl_remove_all_locks(&thd->mdl_context);
+
thd->restore_backup_open_tables_state(backup);
}
diff --git a/sql/sql_binlog.cc b/sql/sql_binlog.cc
index 58c309ef57b..31d4430cbe6 100644
--- a/sql/sql_binlog.cc
+++ b/sql/sql_binlog.cc
@@ -241,7 +241,7 @@ void mysql_client_binlog_statement(THD* thd)
my_ok(thd);
end:
- rli->clear_tables_to_lock();
+ rli->slave_close_thread_tables(thd);
my_free(buf, MYF(MY_ALLOW_ZERO_PTR));
DBUG_VOID_RETURN;
}
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index b7d88eca89a..9445f092546 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -202,10 +202,10 @@ bool foreign_key_prefix(Key *a, Key *b)
** Thread specific functions
****************************************************************************/
-Open_tables_state::Open_tables_state(ulong version_arg)
+Open_tables_state::Open_tables_state(THD *thd, ulong version_arg)
:version(version_arg), state_flags(0U)
{
- reset_open_tables_state();
+ reset_open_tables_state(thd);
}
/*
@@ -440,7 +440,7 @@ bool Drop_table_error_handler::handle_condition(THD *thd,
THD::THD()
:Statement(&main_lex, &main_mem_root, CONVENTIONAL_EXECUTION,
/* statement id */ 0),
- Open_tables_state(refresh_version), rli_fake(0),
+ Open_tables_state(this, refresh_version), rli_fake(0),
lock_id(&main_lock_id),
user_time(0), in_sub_stmt(0),
sql_log_bin_toplevel(false),
@@ -468,7 +468,8 @@ THD::THD()
#if defined(ENABLED_DEBUG_SYNC)
debug_sync_control(0),
#endif /* defined(ENABLED_DEBUG_SYNC) */
- main_warning_info(0)
+ main_warning_info(0),
+ mdl_el_root(NULL)
{
ulong tmp;
@@ -573,6 +574,8 @@ THD::THD()
thr_lock_owner_init(&main_lock_id, &lock_info);
m_internal_handler= NULL;
+
+ init_sql_alloc(&locked_tables_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0);
}
@@ -1051,6 +1054,9 @@ THD::~THD()
if (!cleanup_done)
cleanup();
+ mdl_context_destroy(&mdl_context);
+ mdl_context_destroy(&handler_mdl_context);
+
ha_close_connection(this);
plugin_thdvar_cleanup(this);
@@ -1072,6 +1078,7 @@ THD::~THD()
#endif
free_root(&main_mem_root, MYF(0));
+ free_root(&locked_tables_root, MYF(0));
DBUG_VOID_RETURN;
}
@@ -3014,7 +3021,7 @@ void THD::reset_n_backup_open_tables_state(Open_tables_state *backup)
{
DBUG_ENTER("reset_n_backup_open_tables_state");
backup->set_open_tables_state(this);
- reset_open_tables_state();
+ reset_open_tables_state(this);
state_flags|= Open_tables_state::BACKUPS_AVAIL;
DBUG_VOID_RETURN;
}
@@ -3032,6 +3039,9 @@ void THD::restore_backup_open_tables_state(Open_tables_state *backup)
lock == 0 && locked_tables == 0 &&
prelocked_mode == NON_PRELOCKED &&
m_reprepare_observer == NULL);
+ mdl_context_destroy(&mdl_context);
+ mdl_context_destroy(&handler_mdl_context);
+
set_open_tables_state(backup);
DBUG_VOID_RETURN;
}
diff --git a/sql/sql_class.h b/sql/sql_class.h
index 03a92c1a685..0f7d9d9a8d5 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -25,6 +25,7 @@
#include "log.h"
#include "rpl_tblmap.h"
+#include "mdl.h"
class Reprepare_observer;
@@ -976,26 +977,31 @@ public:
*/
uint state_flags;
+ MDL_CONTEXT mdl_context;
+ MDL_CONTEXT handler_mdl_context;
+
/*
This constructor serves for creation of Open_tables_state instances
which are used as backup storage.
*/
Open_tables_state() : state_flags(0U) { }
- Open_tables_state(ulong version_arg);
+ Open_tables_state(THD *thd, ulong version_arg);
void set_open_tables_state(Open_tables_state *state)
{
*this= *state;
}
- void reset_open_tables_state()
+ void reset_open_tables_state(THD *thd)
{
open_tables= temporary_tables= handler_tables= derived_tables= 0;
extra_lock= lock= locked_tables= 0;
prelocked_mode= NON_PRELOCKED;
state_flags= 0U;
m_reprepare_observer= NULL;
+ mdl_context_init(&mdl_context, thd);
+ mdl_context_init(&handler_mdl_context, thd);
}
};
@@ -1809,6 +1815,9 @@ public:
struct st_debug_sync_control *debug_sync_control;
#endif /* defined(ENABLED_DEBUG_SYNC) */
+ MEM_ROOT *mdl_el_root;
+ MEM_ROOT locked_tables_root;
+
THD();
~THD();
diff --git a/sql/sql_db.cc b/sql/sql_db.cc
index 17626f05aa1..44909880da0 100644
--- a/sql/sql_db.cc
+++ b/sql/sql_db.cc
@@ -904,10 +904,6 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent)
}
else
{
- pthread_mutex_lock(&LOCK_open);
- remove_db_from_cache(db);
- pthread_mutex_unlock(&LOCK_open);
-
Drop_table_error_handler err_handler(thd->get_internal_handler());
thd->push_internal_handler(&err_handler);
diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc
index d8aa27c9695..fb48f32660b 100644
--- a/sql/sql_delete.cc
+++ b/sql/sql_delete.cc
@@ -1089,12 +1089,13 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok)
TABLE *table;
bool error;
uint path_length;
+ MDL_LOCK *mdl_lock= 0;
DBUG_ENTER("mysql_truncate");
bzero((char*) &create_info,sizeof(create_info));
/* Remove tables from the HANDLER's hash. */
- mysql_ha_rm_tables(thd, table_list, FALSE);
+ mysql_ha_rm_tables(thd, table_list);
/* If it is a temporary table, close and regenerate it */
if (!dont_send_ok && (table= find_temporary_table(thd, table_list)))
@@ -1158,8 +1159,20 @@ bool mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok)
thd->lex->alter_info.flags & ALTER_ADMIN_PARTITION)
goto trunc_by_del;
- if (lock_and_wait_for_table_name(thd, table_list))
+ /*
+ FIXME: Actually code of TRUNCATE breaks meta-data locking protocol since
+ tries to get table enging and therefore accesses table in some way
+ without holding any kind of meta-data lock.
+ */
+ mdl_lock= mdl_alloc_lock(0, table_list->db, table_list->table_name,
+ thd->mem_root);
+ mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE);
+ mdl_add_lock(&thd->mdl_context, mdl_lock);
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
DBUG_RETURN(TRUE);
+ pthread_mutex_lock(&LOCK_open);
+ expel_table_from_cache(0, table_list->db, table_list->table_name);
+ pthread_mutex_unlock(&LOCK_open);
}
// Remove the .frm extension AIX 5.2 64-bit compiler bug (BUG#16155): this
@@ -1184,15 +1197,13 @@ end:
write_bin_log(thd, TRUE, thd->query(), thd->query_length());
my_ok(thd); // This should return record count
}
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, table_list);
- pthread_mutex_unlock(&LOCK_open);
+ if (mdl_lock)
+ mdl_release_lock(&thd->mdl_context, mdl_lock);
}
else if (error)
{
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, table_list);
- pthread_mutex_unlock(&LOCK_open);
+ if (mdl_lock)
+ mdl_release_lock(&thd->mdl_context, mdl_lock);
}
DBUG_RETURN(error);
diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc
index da5ee93fcb9..c8a66073a67 100644
--- a/sql/sql_handler.cc
+++ b/sql/sql_handler.cc
@@ -116,17 +116,16 @@ static void mysql_ha_hash_free(TABLE_LIST *tables)
@param thd Thread identifier.
@param tables A list of tables with the first entry to close.
- @param is_locked If LOCK_open is locked.
@note Though this function takes a list of tables, only the first list entry
will be closed.
@note Broadcasts refresh if it closed a table with old version.
*/
-static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables,
- bool is_locked)
+static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables)
{
TABLE **table_ptr;
+ MDL_LOCK *mdl_lock;
/*
Though we could take the table pointer from hash_tables->table,
@@ -142,15 +141,15 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables,
if (*table_ptr)
{
(*table_ptr)->file->ha_index_or_rnd_end();
- if (! is_locked)
- pthread_mutex_lock(&LOCK_open);
+ mdl_lock= (*table_ptr)->mdl_lock;
+ pthread_mutex_lock(&LOCK_open);
if (close_thread_table(thd, table_ptr))
{
/* Tell threads waiting for refresh that something has happened */
broadcast_refresh();
}
- if (! is_locked)
- pthread_mutex_unlock(&LOCK_open);
+ pthread_mutex_unlock(&LOCK_open);
+ mdl_release_lock(&thd->handler_mdl_context, mdl_lock);
}
else if (tables->table)
{
@@ -190,10 +189,12 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables,
bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
{
TABLE_LIST *hash_tables = NULL;
- char *db, *name, *alias;
+ MDL_LOCK *mdl_lock;
+ char *db, *name, *alias, *mdlkey;
uint dblen, namelen, aliaslen, counter;
int error;
TABLE *backup_open_tables;
+ MDL_CONTEXT backup_mdl_context;
DBUG_ENTER("mysql_ha_open");
DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d",
tables->db, tables->table_name, tables->alias,
@@ -216,7 +217,10 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
HANDLER_TABLES_HASH_SIZE, 0, 0,
(my_hash_get_key) mysql_ha_hash_get_key,
(my_hash_free_key) mysql_ha_hash_free, 0))
- goto err;
+ {
+ DBUG_PRINT("exit",("ERROR"));
+ DBUG_RETURN(TRUE);
+ }
}
else if (! reopen) /* Otherwise we have 'tables' already. */
{
@@ -224,10 +228,51 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
strlen(tables->alias) + 1))
{
DBUG_PRINT("info",("duplicate '%s'", tables->alias));
+ DBUG_PRINT("exit",("ERROR"));
my_error(ER_NONUNIQ_TABLE, MYF(0), tables->alias);
- goto err;
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ if (! reopen)
+ {
+ /* copy the TABLE_LIST struct */
+ dblen= strlen(tables->db) + 1;
+ namelen= strlen(tables->table_name) + 1;
+ aliaslen= strlen(tables->alias) + 1;
+ if (!(my_multi_malloc(MYF(MY_WME),
+ &hash_tables, (uint) sizeof(*hash_tables),
+ &db, (uint) dblen,
+ &name, (uint) namelen,
+ &alias, (uint) aliaslen,
+ &mdl_lock, sizeof(MDL_LOCK),
+ &mdlkey, MAX_DBKEY_LENGTH,
+ NullS)))
+ {
+ DBUG_PRINT("exit",("ERROR"));
+ DBUG_RETURN(TRUE);
+ }
+ /* structure copy */
+ *hash_tables= *tables;
+ hash_tables->db= db;
+ hash_tables->table_name= name;
+ hash_tables->alias= alias;
+ memcpy(hash_tables->db, tables->db, dblen);
+ memcpy(hash_tables->table_name, tables->table_name, namelen);
+ memcpy(hash_tables->alias, tables->alias, aliaslen);
+ mdl_init_lock(mdl_lock, mdlkey, 0, db, name);
+ hash_tables->mdl_lock= mdl_lock;
+
+ /* add to hash */
+ if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables))
+ {
+ my_free((char*) hash_tables, MYF(0));
+ DBUG_PRINT("exit",("ERROR"));
+ DBUG_RETURN(TRUE);
}
}
+ else
+ hash_tables= tables;
/*
Save and reset the open_tables list so that open_tables() won't
@@ -243,21 +288,22 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
*/
backup_open_tables= thd->open_tables;
thd->open_tables= NULL;
+ mdl_context_backup_and_reset(&thd->mdl_context, &backup_mdl_context);
/*
- open_tables() will set 'tables->table' if successful.
+ open_tables() will set 'hash_tables->table' if successful.
It must be NULL for a real open when calling open_tables().
*/
- DBUG_ASSERT(! tables->table);
+ DBUG_ASSERT(! hash_tables->table);
/* for now HANDLER can be used only for real TABLES */
- tables->required_type= FRMTYPE_TABLE;
+ hash_tables->required_type= FRMTYPE_TABLE;
/*
We use open_tables() here, rather than, say,
open_ltable() or open_table() because we would like to be able
to open a temporary table.
*/
- error= open_tables(thd, &tables, &counter, 0);
+ error= open_tables(thd, &hash_tables, &counter, 0);
if (thd->open_tables)
{
if (thd->open_tables->next)
@@ -281,52 +327,26 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
thd->handler_tables= thd->open_tables;
}
}
+ mdl_context_merge(&thd->handler_mdl_context, &thd->mdl_context);
- /* Restore the state. */
thd->open_tables= backup_open_tables;
+ mdl_context_restore(&thd->mdl_context, &backup_mdl_context);
if (error)
goto err;
/* There can be only one table in '*tables'. */
- if (! (tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER))
+ if (! (hash_tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER))
{
my_error(ER_ILLEGAL_HA, MYF(0), tables->alias);
goto err;
}
- if (! reopen)
- {
- /* copy the TABLE_LIST struct */
- dblen= strlen(tables->db) + 1;
- namelen= strlen(tables->table_name) + 1;
- aliaslen= strlen(tables->alias) + 1;
- if (!(my_multi_malloc(MYF(MY_WME),
- &hash_tables, (uint) sizeof(*hash_tables),
- &db, (uint) dblen,
- &name, (uint) namelen,
- &alias, (uint) aliaslen,
- NullS)))
- goto err;
- /* structure copy */
- *hash_tables= *tables;
- hash_tables->db= db;
- hash_tables->table_name= name;
- hash_tables->alias= alias;
- memcpy(hash_tables->db, tables->db, dblen);
- memcpy(hash_tables->table_name, tables->table_name, namelen);
- memcpy(hash_tables->alias, tables->alias, aliaslen);
-
- /* add to hash */
- if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables))
- goto err;
- }
-
/*
If it's a temp table, don't reset table->query_id as the table is
being used by this handler. Otherwise, no meaning at all.
*/
- tables->table->open_by_handler= 1;
+ hash_tables->table->open_by_handler= 1;
if (! reopen)
my_ok(thd);
@@ -334,10 +354,10 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen)
DBUG_RETURN(FALSE);
err:
- if (hash_tables)
- my_free((char*) hash_tables, MYF(0));
- if (tables->table)
- mysql_ha_close_table(thd, tables, FALSE);
+ if (hash_tables->table)
+ mysql_ha_close_table(thd, hash_tables);
+ if (!reopen)
+ my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables);
DBUG_PRINT("exit",("ERROR"));
DBUG_RETURN(TRUE);
}
@@ -371,7 +391,7 @@ bool mysql_ha_close(THD *thd, TABLE_LIST *tables)
(uchar*) tables->alias,
strlen(tables->alias) + 1)))
{
- mysql_ha_close_table(thd, hash_tables, FALSE);
+ mysql_ha_close_table(thd, hash_tables);
my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables);
}
else
@@ -507,7 +527,7 @@ retry:
if (need_reopen)
{
- mysql_ha_close_table(thd, hash_tables, FALSE);
+ mysql_ha_close_table(thd, hash_tables);
/*
The lock might have been aborted, we need to manually reset
thd->some_tables_deleted because handler's tables are closed
@@ -734,12 +754,11 @@ static TABLE_LIST *mysql_ha_find(THD *thd, TABLE_LIST *tables)
@param thd Thread identifier.
@param tables The list of tables to remove.
- @param is_locked If LOCK_open is locked.
@note Broadcasts refresh if it closed a table with old version.
*/
-void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables, bool is_locked)
+void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables)
{
TABLE_LIST *hash_tables, *next;
DBUG_ENTER("mysql_ha_rm_tables");
@@ -752,7 +771,7 @@ void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables, bool is_locked)
{
next= hash_tables->next_local;
if (hash_tables->table)
- mysql_ha_close_table(thd, hash_tables, is_locked);
+ mysql_ha_close_table(thd, hash_tables);
my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables);
hash_tables= next;
}
@@ -775,13 +794,16 @@ void mysql_ha_flush(THD *thd)
TABLE_LIST *hash_tables;
DBUG_ENTER("mysql_ha_flush");
- safe_mutex_assert_owner(&LOCK_open);
+ safe_mutex_assert_not_owner(&LOCK_open);
for (uint i= 0; i < thd->handler_tables_hash.records; i++)
{
hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i);
- if (hash_tables->table && hash_tables->table->needs_reopen_or_name_lock())
- mysql_ha_close_table(thd, hash_tables, TRUE);
+ if (hash_tables->table &&
+ (hash_tables->table->mdl_lock &&
+ mdl_has_pending_conflicting_lock(hash_tables->table->mdl_lock) ||
+ hash_tables->table->needs_reopen_or_name_lock()))
+ mysql_ha_close_table(thd, hash_tables);
}
DBUG_VOID_RETURN;
@@ -805,7 +827,7 @@ void mysql_ha_cleanup(THD *thd)
{
hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i);
if (hash_tables->table)
- mysql_ha_close_table(thd, hash_tables, FALSE);
+ mysql_ha_close_table(thd, hash_tables);
}
my_hash_free(&thd->handler_tables_hash);
diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc
index efdc8caa3e5..eb4eee9abb5 100644
--- a/sql/sql_insert.cc
+++ b/sql/sql_insert.cc
@@ -612,7 +612,8 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
upgrade the lock here instead?
*/
if (table_list->lock_type == TL_WRITE_DELAYED && thd->locked_tables &&
- find_locked_table(thd, table_list->db, table_list->table_name))
+ find_locked_table(thd->open_tables, table_list->db,
+ table_list->table_name))
{
my_error(ER_DELAYED_INSERT_TABLE_LOCKED, MYF(0),
table_list->table_name);
@@ -2351,6 +2352,8 @@ pthread_handler_t handle_delayed_insert(void *arg)
thd->lex->set_stmt_unsafe();
thd->set_current_stmt_binlog_row_based_if_mixed();
+ alloc_mdl_locks(&di->table_list, thd->mem_root);
+
/* Open table */
if (!(di->table= open_n_lock_single_table(thd, &di->table_list,
TL_WRITE_DELAYED)))
@@ -3495,7 +3498,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
{
pthread_mutex_lock(&LOCK_open);
- if (reopen_name_locked_table(thd, create_table, FALSE))
+ if (reopen_name_locked_table(thd, create_table))
{
quick_rm_table(create_info->db_type, create_table->db,
table_case_name(create_info, create_table->table_name),
@@ -3507,7 +3510,8 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
}
else
{
- if (!(table= open_table(thd, create_table, thd->mem_root, (bool*) 0,
+ if (!(table= open_table(thd, create_table, thd->mem_root,
+ (enum_open_table_action*) 0,
MYSQL_OPEN_TEMPORARY_ONLY)) &&
!create_info->table_existed)
{
@@ -3621,8 +3625,7 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
DBUG_EXECUTE_IF("sleep_create_select_before_check_if_exists", my_sleep(6000000););
- if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE) &&
- (create_table->table && create_table->table->db_stat))
+ if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE) && create_table->table)
{
/* Table already exists and was open at open_and_lock_tables() stage. */
if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 1271c3112ff..2b2c736fd9e 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -47,6 +47,7 @@
"FUNCTION" : "PROCEDURE")
static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables);
+static void adjust_mdl_locks_upgradability(TABLE_LIST *tables);
const char *any_db="*any*"; // Special symbol for check_access
@@ -143,17 +144,6 @@ static bool xa_trans_rollback(THD *thd)
return status;
}
-static void unlock_locked_tables(THD *thd)
-{
- if (thd->locked_tables)
- {
- thd->lock=thd->locked_tables;
- thd->locked_tables=0; // Will be automatically closed
- close_thread_tables(thd); // Free tables
- }
-}
-
-
bool end_active_trans(THD *thd)
{
int error=0;
@@ -194,12 +184,9 @@ bool begin_trans(THD *thd)
my_error(ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG, MYF(0));
return 1;
}
- if (thd->locked_tables)
- {
- thd->lock=thd->locked_tables;
- thd->locked_tables=0; // Will be automatically closed
- close_thread_tables(thd); // Free tables
- }
+
+ unlock_locked_tables(thd);
+
if (end_active_trans(thd))
error= -1;
else
@@ -1342,6 +1329,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
select_lex.table_list.link_in_list((uchar*) &table_list,
(uchar**) &table_list.next_local);
thd->lex->add_to_query_tables(&table_list);
+ alloc_mdl_locks(&table_list, thd->mem_root);
/* switch on VIEW optimisation: do not fill temporary tables */
thd->lex->sql_command= SQLCOM_SHOW_FIELDS;
@@ -2643,7 +2631,7 @@ case SQLCOM_PREPARE:
if (!(create_info.options & HA_LEX_CREATE_TMP_TABLE))
{
lex->link_first_table_back(create_table, link_to_local);
- create_table->create= TRUE;
+ create_table->open_table_type= TABLE_LIST::OPEN_OR_CREATE;
}
if (!(res= open_and_lock_tables(thd, lex->query_tables)))
@@ -3618,6 +3606,9 @@ end_with_restore_list:
goto error;
thd->in_lock_tables=1;
thd->options|= OPTION_TABLE_LOCK;
+ alloc_mdl_locks(all_tables, &thd->locked_tables_root);
+ thd->mdl_el_root= &thd->locked_tables_root;
+ adjust_mdl_locks_upgradability(all_tables);
if (!(res= simple_open_n_lock_tables(thd, all_tables)))
{
@@ -3641,6 +3632,7 @@ end_with_restore_list:
thd->options&= ~(OPTION_TABLE_LOCK);
}
thd->in_lock_tables=0;
+ thd->mdl_el_root= 0;
break;
case SQLCOM_CREATE_DB:
{
@@ -6542,6 +6534,9 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd,
ptr->next_name_resolution_table= NULL;
/* Link table in global list (all used tables) */
lex->add_to_query_tables(ptr);
+ ptr->mdl_lock= mdl_alloc_lock(0 , ptr->db, ptr->table_name,
+ thd->mdl_el_root ? thd->mdl_el_root :
+ thd->mem_root);
DBUG_RETURN(ptr);
}
@@ -7079,23 +7074,15 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables,
if ((options & REFRESH_READ_LOCK) && thd)
{
/*
- We must not try to aspire a global read lock if we have a write
- locked table. This would lead to a deadlock when trying to
- reopen (and re-lock) the table after the flush.
+ On the first hand we need write lock on the tables to be flushed,
+ on the other hand we must not try to aspire a global read lock
+ if we have a write locked table as this would lead to a deadlock
+ when trying to reopen (and re-lock) the table after the flush.
*/
if (thd->locked_tables)
{
- THR_LOCK_DATA **lock_p= thd->locked_tables->locks;
- THR_LOCK_DATA **end_p= lock_p + thd->locked_tables->lock_count;
-
- for (; lock_p < end_p; lock_p++)
- {
- if ((*lock_p)->type >= TL_WRITE_ALLOW_WRITE)
- {
- my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
- return 1;
- }
- }
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ return 1;
}
/*
Writing to the binlog could cause deadlocks, as we don't log
@@ -7105,7 +7092,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables,
if (lock_global_read_lock(thd))
return 1; // Killed
if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ?
- FALSE : TRUE, TRUE))
+ FALSE : TRUE))
result= 1;
if (make_global_read_lock_block_commit(thd)) // Killed
@@ -7117,8 +7104,35 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables,
}
else
{
+ if (thd && thd->locked_tables)
+ {
+ /*
+ If we are under LOCK TABLES we should have a write
+ lock on tables which we are going to flush.
+ */
+ if (tables)
+ {
+ for (TABLE_LIST *t= tables; t; t= t->next_local)
+ if (!find_write_locked_table(thd->open_tables, t->db,
+ t->table_name))
+ return 1;
+ }
+ else
+ {
+ for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
+ {
+ if (tab->reginfo.lock_type < TL_WRITE_ALLOW_WRITE)
+ {
+ my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0),
+ tab->s->table_name.str);
+ return 1;
+ }
+ }
+ }
+ }
+
if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ?
- FALSE : TRUE, FALSE))
+ FALSE : TRUE))
result= 1;
}
my_dbopt_cleanup();
@@ -8092,6 +8106,42 @@ bool parse_sql(THD *thd,
return ret_value;
}
+
+/**
+ Auxiliary function which marks metadata locks for all tables
+ on which we plan to take write lock as upgradable.
+*/
+
+static void adjust_mdl_locks_upgradability(TABLE_LIST *tables)
+{
+ TABLE_LIST *tab, *otab;
+
+ for (tab= tables; tab; tab= tab->next_global)
+ {
+ if (tab->lock_type >= TL_WRITE_ALLOW_WRITE)
+ tab->mdl_upgradable= TRUE;
+ else
+ {
+ /*
+ TODO: To get rid of this loop we need to change our code to do
+ metadata lock upgrade only for those instances of tables
+ which are write locked instead of doing such upgrade for
+ all instances of tables.
+ */
+ for (otab= tables; otab; otab= otab->next_global)
+ if (otab->lock_type >= TL_WRITE_ALLOW_WRITE &&
+ otab->db_length == tab->db_length &&
+ otab->table_name_length == tab->table_name_length &&
+ !strcmp(otab->db, tab->db) &&
+ !strcmp(otab->table_name, tab->table_name))
+ {
+ tab->mdl_upgradable= TRUE;
+ break;
+ }
+ }
+ }
+}
+
/**
@} (end of group Runtime_Environment)
*/
diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc
index 868dfc3e968..48f33bf3295 100644
--- a/sql/sql_partition.cc
+++ b/sql/sql_partition.cc
@@ -6225,7 +6225,7 @@ static void alter_partition_lock_handling(ALTER_PARTITION_PARAM_TYPE *lpt)
*/
pthread_mutex_lock(&LOCK_open);
lpt->thd->in_lock_tables= 1;
- err= reopen_tables(lpt->thd, 1, 1);
+ err= reopen_tables(lpt->thd, 1);
lpt->thd->in_lock_tables= 0;
if (err)
{
@@ -6564,7 +6564,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table,
write_log_drop_partition(lpt) ||
ERROR_INJECT_CRASH("crash_drop_partition_3") ||
(not_completed= FALSE) ||
- abort_and_upgrade_lock(lpt) || /* Always returns 0 */
+ abort_and_upgrade_lock(lpt) ||
ERROR_INJECT_CRASH("crash_drop_partition_4") ||
alter_close_tables(lpt) ||
ERROR_INJECT_CRASH("crash_drop_partition_5") ||
@@ -6631,7 +6631,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table,
ERROR_INJECT_CRASH("crash_add_partition_2") ||
mysql_change_partitions(lpt) ||
ERROR_INJECT_CRASH("crash_add_partition_3") ||
- abort_and_upgrade_lock(lpt) || /* Always returns 0 */
+ abort_and_upgrade_lock(lpt) ||
ERROR_INJECT_CRASH("crash_add_partition_4") ||
alter_close_tables(lpt) ||
ERROR_INJECT_CRASH("crash_add_partition_5") ||
@@ -6647,7 +6647,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table,
ERROR_INJECT_CRASH("crash_add_partition_8") ||
(write_log_completed(lpt, FALSE), FALSE) ||
ERROR_INJECT_CRASH("crash_add_partition_9") ||
- (alter_partition_lock_handling(lpt), FALSE))
+ (alter_partition_lock_handling(lpt), FALSE))
{
handle_alter_part_error(lpt, not_completed, FALSE, frm_install);
goto err;
@@ -6721,7 +6721,7 @@ uint fast_alter_partition_table(THD *thd, TABLE *table,
write_log_final_change_partition(lpt) ||
ERROR_INJECT_CRASH("crash_change_partition_4") ||
(not_completed= FALSE) ||
- abort_and_upgrade_lock(lpt) || /* Always returns 0 */
+ abort_and_upgrade_lock(lpt) ||
ERROR_INJECT_CRASH("crash_change_partition_5") ||
alter_close_tables(lpt) ||
ERROR_INJECT_CRASH("crash_change_partition_6") ||
diff --git a/sql/sql_plist.h b/sql/sql_plist.h
new file mode 100644
index 00000000000..af2ed227ea1
--- /dev/null
+++ b/sql/sql_plist.h
@@ -0,0 +1,125 @@
+#ifndef SQL_PLIST_H
+#define SQL_PLIST_H
+/* Copyright (C) 2008 MySQL AB
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+
+#include <my_global.h>
+
+template <typename T, typename B> class I_P_List_iterator;
+
+
+/**
+ Intrusive parameterized list.
+
+ Unlike I_List does not require its elements to be descendant of ilink
+ class and therefore allows them to participate in several such lists
+ simultaneously.
+
+ Unlike List is doubly-linked list and thus supports efficient deletion
+ of element without iterator.
+
+ @param T Type of elements which will belong to list.
+ @param B Class which via its methods specifies which members
+ of T should be used for participating in this list.
+ Here is typical layout of such class:
+
+ struct B
+ {
+ static inline T **next_ptr(T *el)
+ {
+ return &el->next;
+ }
+ static inline T ***prev_ptr(T *el)
+ {
+ return &el->prev;
+ }
+ };
+*/
+
+template <typename T, typename B>
+class I_P_List
+{
+ T *first;
+
+ /*
+ Do not prohibit copying of I_P_List object to simplify their usage in
+ backup/restore scenarios. Note that performing any operations on such
+ is a bad idea.
+ */
+public:
+ I_P_List() : first(NULL) { };
+ inline void empty() { first= NULL; }
+ inline bool is_empty() { return (first == NULL); }
+ inline void push_front(T* a)
+ {
+ *B::next_ptr(a)= first;
+ if (first)
+ *B::prev_ptr(first)= B::next_ptr(a);
+ first= a;
+ *B::prev_ptr(a)= &first;
+ }
+ inline void remove(T *a)
+ {
+ T *next= *B::next_ptr(a);
+ if (next)
+ *B::prev_ptr(next)= *B::prev_ptr(a);
+ **B::prev_ptr(a)= next;
+ }
+ inline T* head() { return first; }
+ void swap(I_P_List<T,B> &rhs)
+ {
+ swap_variables(T *, first, rhs.first);
+ if (first)
+ *B::prev_ptr(first)= &first;
+ if (rhs.first)
+ *B::prev_ptr(rhs.first)= &rhs.first;
+ }
+#ifndef _lint
+ friend class I_P_List_iterator<T, B>;
+#endif
+};
+
+
+/**
+ Iterator for I_P_List.
+*/
+
+template <typename T, typename B>
+class I_P_List_iterator
+{
+ I_P_List<T, B> *list;
+ T *current;
+public:
+ I_P_List_iterator(I_P_List<T, B> &a) : list(&a), current(a.first) {}
+ inline void init(I_P_List<T, B> &a)
+ {
+ list= &a;
+ current= a.first;
+ }
+ inline T* operator++(int)
+ {
+ T *result= current;
+ if (result)
+ current= *B::next_ptr(current);
+ return result;
+ }
+ inline void rewind()
+ {
+ current= list->first;
+ }
+};
+
+#endif
diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc
index 936c9ae8866..e9f4152f861 100644
--- a/sql/sql_plugin.cc
+++ b/sql/sql_plugin.cc
@@ -1366,6 +1366,7 @@ static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv)
tables.alias= tables.table_name= (char*)"plugin";
tables.lock_type= TL_READ;
tables.db= new_thd->db;
+ alloc_mdl_locks(&tables, tmp_root);
#ifdef EMBEDDED_LIBRARY
/*
@@ -1659,6 +1660,8 @@ bool mysql_install_plugin(THD *thd, const LEX_STRING *name, const LEX_STRING *dl
if (check_table_access(thd, INSERT_ACL, &tables, FALSE, 1, FALSE))
DBUG_RETURN(TRUE);
+ alloc_mdl_locks(&tables, thd->mem_root);
+
/* need to open before acquiring LOCK_plugin or it will deadlock */
if (! (table = open_ltable(thd, &tables, TL_WRITE, 0)))
DBUG_RETURN(TRUE);
@@ -1732,6 +1735,7 @@ bool mysql_uninstall_plugin(THD *thd, const LEX_STRING *name)
bzero(&tables, sizeof(tables));
tables.db= (char *)"mysql";
tables.table_name= tables.alias= (char *)"plugin";
+ alloc_mdl_locks(&tables, thd->mem_root);
/* need to open before acquiring LOCK_plugin or it will deadlock */
if (! (table= open_ltable(thd, &tables, TL_WRITE, 0)))
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index d624c22f43a..582e18a3abf 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -1673,7 +1673,7 @@ static bool mysql_test_create_table(Prepared_statement *stmt)
if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE))
{
lex->link_first_table_back(create_table, link_to_local);
- create_table->create= TRUE;
+ create_table->open_table_type= TABLE_LIST::OPEN_OR_CREATE;
}
if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0))
diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc
index dac96f2e9c4..857cccde50f 100644
--- a/sql/sql_rename.cc
+++ b/sql/sql_rename.cc
@@ -51,7 +51,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent)
DBUG_RETURN(1);
}
- mysql_ha_rm_tables(thd, table_list, FALSE);
+ mysql_ha_rm_tables(thd, table_list);
if (wait_if_global_read_lock(thd,0,1))
DBUG_RETURN(1);
@@ -133,12 +133,13 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent)
}
}
- pthread_mutex_lock(&LOCK_open);
- if (lock_table_names_exclusively(thd, table_list))
- {
- pthread_mutex_unlock(&LOCK_open);
+ if (lock_table_names(thd, table_list))
goto err;
- }
+
+ pthread_mutex_lock(&LOCK_open);
+
+ for (ren_table= table_list; ren_table; ren_table= ren_table->next_local)
+ expel_table_from_cache(0, ren_table->db, ren_table->table_name);
error=0;
if ((ren_table=rename_tables(thd,table_list,0)))
@@ -184,9 +185,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent)
if (!error)
query_cache_invalidate3(thd, table_list, 0);
- pthread_mutex_lock(&LOCK_open);
- unlock_table_names(thd, table_list, (TABLE_LIST*) 0);
- pthread_mutex_unlock(&LOCK_open);
+ unlock_table_names(thd);
err:
start_waiting_global_read_lock(thd);
diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc
index e5fe06ce39b..5251a50cab9 100644
--- a/sql/sql_servers.cc
+++ b/sql/sql_servers.cc
@@ -224,12 +224,7 @@ bool servers_reload(THD *thd)
bool return_val= TRUE;
DBUG_ENTER("servers_reload");
- if (thd->locked_tables)
- { // Can't have locked tables here
- thd->lock=thd->locked_tables;
- thd->locked_tables=0;
- close_thread_tables(thd);
- }
+ unlock_locked_tables(thd); // Can't have locked tables here
DBUG_PRINT("info", ("locking servers_cache"));
rw_wrlock(&THR_LOCK_servers);
@@ -238,6 +233,7 @@ bool servers_reload(THD *thd)
tables[0].alias= tables[0].table_name= (char*) "servers";
tables[0].db= (char*) "mysql";
tables[0].lock_type= TL_READ;
+ alloc_mdl_locks(tables, thd->mem_root);
if (simple_open_n_lock_tables(thd, tables))
{
@@ -368,6 +364,7 @@ insert_server(THD *thd, FOREIGN_SERVER *server)
bzero((char*) &tables, sizeof(tables));
tables.db= (char*) "mysql";
tables.alias= tables.table_name= (char*) "servers";
+ alloc_mdl_locks(&tables, thd->mem_root);
/* need to open before acquiring THR_LOCK_plugin or it will deadlock */
if (! (table= open_ltable(thd, &tables, TL_WRITE, 0)))
@@ -586,6 +583,7 @@ int drop_server(THD *thd, LEX_SERVER_OPTIONS *server_options)
bzero((char*) &tables, sizeof(tables));
tables.db= (char*) "mysql";
tables.alias= tables.table_name= (char*) "servers";
+ alloc_mdl_locks(&tables, thd->mem_root);
rw_wrlock(&THR_LOCK_servers);
@@ -710,6 +708,7 @@ int update_server(THD *thd, FOREIGN_SERVER *existing, FOREIGN_SERVER *altered)
bzero((char*) &tables, sizeof(tables));
tables.db= (char*)"mysql";
tables.alias= tables.table_name= (char*)"servers";
+ alloc_mdl_locks(&tables, thd->mem_root);
if (!(table= open_ltable(thd, &tables, TL_WRITE, 0)))
{
diff --git a/sql/sql_show.cc b/sql/sql_show.cc
index babadc34842..c83a6981166 100644
--- a/sql/sql_show.cc
+++ b/sql/sql_show.cc
@@ -2963,7 +2963,7 @@ fill_schema_show_cols_or_idxs(THD *thd, TABLE_LIST *tables,
table, res, db_name,
table_name));
thd->temporary_tables= 0;
- close_tables_for_reopen(thd, &show_table_list);
+ close_tables_for_reopen(thd, &show_table_list, FALSE);
DBUG_RETURN(error);
}
@@ -3106,6 +3106,9 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
char key[MAX_DBKEY_LENGTH];
uint key_length;
char db_name_buff[NAME_LEN + 1], table_name_buff[NAME_LEN + 1];
+ MDL_LOCK mdl_lock;
+ char mdlkey[MAX_DBKEY_LENGTH];
+ bool retry;
bzero((char*) &table_list, sizeof(TABLE_LIST));
bzero((char*) &tbl, sizeof(TABLE));
@@ -3130,6 +3133,34 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
table_list.db= db_name->str;
}
+ mdl_init_lock(&mdl_lock, mdlkey, 0, db_name->str, table_name->str);
+ table_list.mdl_lock= &mdl_lock;
+ mdl_add_lock(&thd->mdl_context, &mdl_lock);
+ mdl_set_lock_priority(&mdl_lock, MDL_HIGH_PRIO);
+
+ /*
+ TODO: investigate if in this particular situation we can get by
+ simply obtaining internal lock of data-dictionary (ATM it
+ is LOCK_open) instead of obtaning full-blown metadata lock.
+ */
+ while (1)
+ {
+ if (mdl_acquire_shared_lock(&mdl_lock, &retry))
+ {
+ if (!retry || mdl_wait_for_locks(&thd->mdl_context))
+ {
+ /*
+ Some error occured or we have been killed while waiting
+ for conflicting locks to go away, let the caller to handle
+ the situation.
+ */
+ return 1;
+ }
+ continue;
+ }
+ break;
+ }
+
key_length= create_table_def_key(thd, key, &table_list, 0);
pthread_mutex_lock(&LOCK_open);
share= get_table_share(thd, &table_list, key,
@@ -3137,7 +3168,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
if (!share)
{
res= 0;
- goto err;
+ goto err_unlock;
}
if (share->is_view)
@@ -3146,7 +3177,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
{
/* skip view processing */
res= 0;
- goto err1;
+ goto err_share;
}
else if (schema_table->i_s_requested_object & OPEN_VIEW_FULL)
{
@@ -3155,7 +3186,7 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
open_normal_and_derived_tables()
*/
res= 1;
- goto err1;
+ goto err_share;
}
}
@@ -3171,14 +3202,17 @@ static int fill_schema_table_from_frm(THD *thd,TABLE *table,
res= schema_table->process_table(thd, &table_list, table,
res, db_name, table_name);
closefrm(&tbl, true);
- goto err;
+ goto err_unlock;
}
-err1:
+err_share:
release_table_share(share, RELEASE_NORMAL);
-err:
+err_unlock:
pthread_mutex_unlock(&LOCK_open);
+
+err:
+ mdl_release_lock(&thd->mdl_context, &mdl_lock);
thd->clear_error();
return res;
}
@@ -3425,7 +3459,7 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
res= schema_table->process_table(thd, show_table_list, table,
res, &orig_db_name,
&tmp_lex_string);
- close_tables_for_reopen(thd, &show_table_list);
+ close_tables_for_reopen(thd, &show_table_list, FALSE);
}
DBUG_ASSERT(!lex->query_tables_own_last);
if (res)
@@ -7273,6 +7307,8 @@ bool show_create_trigger(THD *thd, const sp_name *trg_name)
uint num_tables; /* NOTE: unused, only to pass to open_tables(). */
+ alloc_mdl_locks(lst, thd->mem_root);
+
if (open_tables(thd, &lst, &num_tables, 0))
{
my_error(ER_TRG_CANT_OPEN_TABLE, MYF(0),
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
index dc0c876e882..f91bae8b76c 100644
--- a/sql/sql_table.cc
+++ b/sql/sql_table.cc
@@ -53,6 +53,7 @@ static bool
mysql_prepare_alter_table(THD *thd, TABLE *table,
HA_CREATE_INFO *create_info,
Alter_info *alter_info);
+static bool close_cached_table(THD *thd, TABLE *table);
#ifndef DBUG_OFF
@@ -1791,11 +1792,6 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists,
DBUG_RETURN(TRUE);
}
- /*
- Acquire LOCK_open after wait_if_global_read_lock(). If we would hold
- LOCK_open during wait_if_global_read_lock(), other threads could not
- close their tables. This would make a pretty deadlock.
- */
thd->push_internal_handler(&err_handler);
error= mysql_rm_table_part2(thd, tables, if_exists, drop_temporary, 0, 0);
thd->pop_internal_handler();
@@ -1867,16 +1863,14 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
built_query.append("DROP TABLE ");
}
- mysql_ha_rm_tables(thd, tables, FALSE);
-
- pthread_mutex_lock(&LOCK_open);
+ mysql_ha_rm_tables(thd, tables);
/*
If we have the table in the definition cache, we don't have to check the
.frm file to find if the table is a normal table (not view) and what
engine to use.
*/
-
+ pthread_mutex_lock(&LOCK_open);
for (table= tables; table; table= table->next_local)
{
TABLE_SHARE *share;
@@ -1889,16 +1883,32 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
check_if_log_table(table->db_length, table->db,
table->table_name_length, table->table_name, 1))
{
- my_error(ER_BAD_LOG_STATEMENT, MYF(0), "DROP");
pthread_mutex_unlock(&LOCK_open);
+ my_error(ER_BAD_LOG_STATEMENT, MYF(0), "DROP");
DBUG_RETURN(1);
}
}
+ pthread_mutex_unlock(&LOCK_open);
- if (!drop_temporary && lock_table_names_exclusively(thd, tables))
+ if (!drop_temporary)
{
- pthread_mutex_unlock(&LOCK_open);
- DBUG_RETURN(1);
+ if (!thd->locked_tables)
+ {
+ if (lock_table_names(thd, tables))
+ DBUG_RETURN(1);
+ pthread_mutex_lock(&LOCK_open);
+ for (table= tables; table; table= table->next_local)
+ expel_table_from_cache(0, table->db, table->table_name);
+ pthread_mutex_unlock(&LOCK_open);
+ }
+ else if (thd->locked_tables)
+ {
+ for (table= tables; table; table= table->next_local)
+ if (!find_temporary_table(thd, table->db, table->table_name) &&
+ !find_write_locked_table(thd->open_tables, table->db,
+ table->table_name))
+ DBUG_RETURN(1);
+ }
}
for (table= tables; table; table= table->next_local)
@@ -1973,17 +1983,21 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
table_type= table->db_type;
if (!drop_temporary)
{
- TABLE *locked_table;
- abort_locked_tables(thd, db, table->table_name);
- remove_table_from_cache(thd, db, table->table_name,
- RTFC_WAIT_OTHER_THREAD_FLAG |
- RTFC_CHECK_KILLED_FLAG);
- /*
- If the table was used in lock tables, remember it so that
- unlock_table_names can free it
- */
- if ((locked_table= drop_locked_tables(thd, db, table->table_name)))
- table->table= locked_table;
+ if (thd->locked_tables)
+ {
+ TABLE *tab= find_locked_table(thd->open_tables, db, table->table_name);
+ if (close_cached_table(thd, tab))
+ {
+ error= -1;
+ goto err_with_placeholders;
+ }
+ /*
+ Leave LOCK TABLES mode if we managed to drop all tables
+ which were locked.
+ */
+ if (thd->locked_tables->table_count == 0)
+ unlock_locked_tables(thd);
+ }
if (thd->killed)
{
@@ -1997,6 +2011,11 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
table->internal_tmp_table ?
FN_IS_TMP : 0);
}
+ /*
+ TODO: Investigate what should be done to remove this lock completely.
+ Is exclusive meta-data lock enough ?
+ */
+ pthread_mutex_lock(&LOCK_open);
if (drop_temporary ||
((table_type == NULL &&
access(path, F_OK) &&
@@ -2049,6 +2068,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
error|= new_error;
}
}
+ pthread_mutex_unlock(&LOCK_open);
if (error)
{
if (wrong_tables.length())
@@ -2064,11 +2084,6 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
table->table_name););
}
- /*
- It's safe to unlock LOCK_open: we have an exclusive lock
- on the table name.
- */
- pthread_mutex_unlock(&LOCK_open);
thd->thread_specific_used|= tmp_table_deleted;
error= 0;
if (wrong_tables.length())
@@ -2151,10 +2166,19 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
*/
}
}
- pthread_mutex_lock(&LOCK_open);
err_with_placeholders:
- unlock_table_names(thd, tables, (TABLE_LIST*) 0);
- pthread_mutex_unlock(&LOCK_open);
+ if (!drop_temporary)
+ {
+ /*
+ Under LOCK TABLES we should release meta-data locks on the tables
+ which were dropped. Otherwise we can rely on close_thread_tables()
+ doing this. Unfortunately in this case we are likely to get more
+ false positives in lock_table_name_if_not_cached() function. So
+ it makes sense to remove exclusive meta-data locks in all cases.
+ */
+ mdl_release_exclusive_locks(&thd->mdl_context);
+ }
+
DBUG_RETURN(error);
}
@@ -4005,6 +4029,34 @@ warn:
}
+/**
+ Auxiliary function which obtains exclusive meta-data lock on the
+ table if there are no shared or exclusive on it already.
+
+ See mdl_try_acquire_exclusive_lock() function for more info.
+
+ TODO: This function is here mostly to simplify current patch
+ and probably should be removed.
+ TODO: Investigate if it is kosher to leave lock request in the
+ context in the case when we fail to obtain the lock.
+*/
+
+static bool lock_table_name_if_not_cached(THD *thd, const char *db,
+ const char *table_name,
+ MDL_LOCK **lock)
+{
+ if (!(*lock= mdl_alloc_lock(0, db, table_name, thd->mem_root)))
+ return TRUE;
+ mdl_set_lock_type(*lock, MDL_EXCLUSIVE);
+ mdl_add_lock(&thd->mdl_context, *lock);
+ if (mdl_try_acquire_exclusive_lock(&thd->mdl_context, *lock))
+ {
+ *lock= 0;
+ }
+ return FALSE;
+}
+
+
/*
Database and name-locking aware wrapper for mysql_create_table_no_lock(),
*/
@@ -4015,7 +4067,7 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name,
bool internal_tmp_table,
uint select_field_count)
{
- TABLE *name_lock= 0;
+ MDL_LOCK *target_lock= 0;
bool result;
DBUG_ENTER("mysql_create_table");
@@ -4038,12 +4090,12 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name,
if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
{
- if (lock_table_name_if_not_cached(thd, db, table_name, &name_lock))
+ if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock))
{
result= TRUE;
goto unlock;
}
- if (!name_lock)
+ if (!target_lock)
{
if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
{
@@ -4069,12 +4121,8 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name,
select_field_count);
unlock:
- if (name_lock)
- {
- pthread_mutex_lock(&LOCK_open);
- unlink_open_table(thd, name_lock, FALSE);
- pthread_mutex_unlock(&LOCK_open);
- }
+ if (target_lock)
+ mdl_release_exclusive_locks(&thd->mdl_context);
pthread_mutex_lock(&LOCK_lock_db);
if (!--creating_table && creating_database)
pthread_cond_signal(&COND_refresh);
@@ -4212,80 +4260,83 @@ mysql_rename_table(handlerton *base, const char *old_db,
}
-/*
- Force all other threads to stop using the table
+/**
+ Force all other threads to stop using the table by upgrading
+ metadata lock on it and remove unused TABLE instances from cache.
- SYNOPSIS
- wait_while_table_is_used()
- thd Thread handler
- table Table to remove from cache
- function HA_EXTRA_PREPARE_FOR_DROP if table is to be deleted
- HA_EXTRA_FORCE_REOPEN if table is not be used
- HA_EXTRA_PREPARE_FOR_RENAME if table is to be renamed
- NOTES
- When returning, the table will be unusable for other threads until
- the table is closed.
+ @param thd Thread handler
+ @param table Table to remove from cache
+ @param function HA_EXTRA_PREPARE_FOR_DROP if table is to be deleted
+ HA_EXTRA_FORCE_REOPEN if table is not be used
+ HA_EXTRA_PREPARE_FOR_RENAME if table is to be renamed
- PREREQUISITES
- Lock on LOCK_open
- Win32 clients must also have a WRITE LOCK on the table !
+ @note When returning, the table will be unusable for other threads
+ until metadata lock is downgraded.
+
+ @retval FALSE Success.
+ @retval TRUE Failure (e.g. because thread was killed).
*/
-void wait_while_table_is_used(THD *thd, TABLE *table,
+bool wait_while_table_is_used(THD *thd, TABLE *table,
enum ha_extra_function function)
{
+ enum thr_lock_type old_lock_type;
+
DBUG_ENTER("wait_while_table_is_used");
DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu",
table->s->table_name.str, (ulong) table->s,
table->db_stat, table->s->version));
- safe_mutex_assert_owner(&LOCK_open);
-
(void) table->file->extra(function);
- /* Mark all tables that are in use as 'old' */
+
+ old_lock_type= table->reginfo.lock_type;
mysql_lock_abort(thd, table, TRUE); /* end threads waiting on lock */
- /* Wait until all there are no other threads that has this table open */
- remove_table_from_cache(thd, table->s->db.str,
- table->s->table_name.str,
- RTFC_WAIT_OTHER_THREAD_FLAG);
- DBUG_VOID_RETURN;
+ if (mdl_upgrade_shared_lock_to_exclusive(&thd->mdl_context, 0,
+ table->s->db.str,
+ table->s->table_name.str))
+ {
+ mysql_lock_downgrade_write(thd, table, old_lock_type);
+ DBUG_RETURN(TRUE);
+ }
+
+ pthread_mutex_lock(&LOCK_open);
+ expel_table_from_cache(thd, table->s->db.str, table->s->table_name.str);
+ pthread_mutex_unlock(&LOCK_open);
+ DBUG_RETURN(FALSE);
}
-/*
- Close a cached table
- SYNOPSIS
- close_cached_table()
- thd Thread handler
- table Table to remove from cache
+/**
+ Upgrade metadata lock on the table and close all its instances.
- NOTES
- Function ends by signaling threads waiting for the table to try to
- reopen the table.
+ @param thd Thread handler
+ @param table Table to remove from cache
- PREREQUISITES
- Lock on LOCK_open
- Win32 clients must also have a WRITE LOCK on the table !
+ @retval FALSE Success.
+ @retval TRUE Failure (e.g. because thread was killed).
*/
-void close_cached_table(THD *thd, TABLE *table)
+static bool close_cached_table(THD *thd, TABLE *table)
{
DBUG_ENTER("close_cached_table");
- wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
+ /* FIXME: check if we pass proper parameters everywhere. */
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ DBUG_RETURN(TRUE);
+
/* Close lock if this is not got with LOCK TABLES */
if (thd->lock)
{
mysql_unlock_tables(thd, thd->lock);
thd->lock=0; // Start locked threads
}
+
+ pthread_mutex_lock(&LOCK_open);
/* Close all copies of 'table'. This also frees all LOCK TABLES lock */
unlink_open_table(thd, table, TRUE);
-
- /* When lock on LOCK_open is freed other threads can continue */
- broadcast_refresh();
- DBUG_VOID_RETURN;
+ pthread_mutex_unlock(&LOCK_open);
+ DBUG_RETURN(FALSE);
}
static int send_check_errmsg(THD *thd, TABLE_LIST* table,
@@ -4308,6 +4359,7 @@ static int send_check_errmsg(THD *thd, TABLE_LIST* table,
static int prepare_for_restore(THD* thd, TABLE_LIST* table,
HA_CHECK_OPT *check_opt)
{
+ MDL_LOCK *mdl_lock= 0;
DBUG_ENTER("prepare_for_restore");
if (table->table) // do not overwrite existing tables on restore
@@ -4331,22 +4383,25 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table,
build_table_filename(dst_path, sizeof(dst_path) - 1,
db, table_name, reg_ext, 0);
- if (lock_and_wait_for_table_name(thd,table))
- DBUG_RETURN(-1);
+ mdl_lock= mdl_alloc_lock(0, table->db, table->table_name,
+ thd->mem_root);
+ mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE);
+ mdl_add_lock(&thd->mdl_context, mdl_lock);
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+ DBUG_RETURN(TRUE);
+ pthread_mutex_lock(&LOCK_open);
+ expel_table_from_cache(0, table->db, table->table_name);
+ pthread_mutex_unlock(&LOCK_open);
if (my_copy(src_path, dst_path, MYF(MY_WME)))
{
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, table);
- pthread_mutex_unlock(&LOCK_open);
+ mdl_release_lock(&thd->mdl_context, mdl_lock);
DBUG_RETURN(send_check_errmsg(thd, table, "restore",
"Failed copying .frm file"));
}
if (mysql_truncate(thd, table, 1))
{
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, table);
- pthread_mutex_unlock(&LOCK_open);
+ mdl_release_lock(&thd->mdl_context, mdl_lock);
DBUG_RETURN(send_check_errmsg(thd, table, "restore",
"Failed generating table from .frm file"));
}
@@ -4357,10 +4412,11 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table,
to finish the restore in the handler later on
*/
pthread_mutex_lock(&LOCK_open);
- if (reopen_name_locked_table(thd, table, TRUE))
+ if (reopen_name_locked_table(thd, table))
{
- unlock_table_name(thd, table);
pthread_mutex_unlock(&LOCK_open);
+ if (mdl_lock)
+ mdl_release_lock(&thd->mdl_context, mdl_lock);
DBUG_RETURN(send_check_errmsg(thd, table, "restore",
"Failed to open partially restored table"));
}
@@ -4380,6 +4436,7 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
char from[FN_REFLEN],tmp[FN_REFLEN+32];
const char **ext;
MY_STAT stat_info;
+ MDL_LOCK *mdl_lock;
DBUG_ENTER("prepare_for_repair");
if (!(check_opt->sql_flags & TT_USEFRM))
@@ -4391,6 +4448,17 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
uint key_length;
key_length= create_table_def_key(thd, key, table_list, 0);
+ /*
+ TODO: Check that REPAIR's code also conforms to meta-data
+ locking protocol. Fix if it is not.
+ */
+ mdl_lock= mdl_alloc_lock(0, table_list->db, table_list->table_name,
+ thd->mem_root);
+ mdl_set_lock_type(mdl_lock, MDL_EXCLUSIVE);
+ mdl_add_lock(&thd->mdl_context, mdl_lock);
+ if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+ DBUG_RETURN(0);
+
pthread_mutex_lock(&LOCK_open);
if (!(share= (get_table_share(thd, table_list, key, key_length, 0,
&error))))
@@ -4457,41 +4525,29 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
my_snprintf(tmp, sizeof(tmp), "%s-%lx_%lx",
from, current_pid, thd->thread_id);
- /* If we could open the table, close it */
if (table_list->table)
{
- pthread_mutex_lock(&LOCK_open);
- close_cached_table(thd, table);
- pthread_mutex_unlock(&LOCK_open);
- }
- if (lock_and_wait_for_table_name(thd,table_list))
- {
- error= -1;
- goto end;
+ /* If we could open the table, close it */
+ if (close_cached_table(thd, table))
+ goto end;
+ table_list->table= 0;
}
+ // After this point we have X mdl lock in both cases
+
if (my_rename(from, tmp, MYF(MY_WME)))
{
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, table_list);
- pthread_mutex_unlock(&LOCK_open);
error= send_check_errmsg(thd, table_list, "repair",
"Failed renaming data file");
goto end;
}
if (mysql_truncate(thd, table_list, 1))
{
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, table_list);
- pthread_mutex_unlock(&LOCK_open);
error= send_check_errmsg(thd, table_list, "repair",
"Failed generating table from .frm file");
goto end;
}
if (my_rename(tmp, from, MYF(MY_WME)))
{
- pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, table_list);
- pthread_mutex_unlock(&LOCK_open);
error= send_check_errmsg(thd, table_list, "repair",
"Failed restoring .MYD file");
goto end;
@@ -4502,9 +4558,8 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
to finish the repair in the handler later on.
*/
pthread_mutex_lock(&LOCK_open);
- if (reopen_name_locked_table(thd, table_list, TRUE))
+ if (reopen_name_locked_table(thd, table_list))
{
- unlock_table_name(thd, table_list);
pthread_mutex_unlock(&LOCK_open);
error= send_check_errmsg(thd, table_list, "repair",
"Failed to open partially repaired table");
@@ -4519,6 +4574,8 @@ end:
closefrm(table, 1); // Free allocated memory
pthread_mutex_unlock(&LOCK_open);
}
+ if (error)
+ mdl_release_exclusive_locks(&thd->mdl_context);
DBUG_RETURN(error);
}
@@ -4566,7 +4623,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
DBUG_RETURN(TRUE);
- mysql_ha_rm_tables(thd, tables, FALSE);
+ mysql_ha_rm_tables(thd, tables);
for (table= tables; table; table= table->next_local)
{
@@ -5063,6 +5120,7 @@ bool mysql_restore_table(THD* thd, TABLE_LIST* table_list)
bool mysql_repair_table(THD* thd, TABLE_LIST* tables, HA_CHECK_OPT* check_opt)
{
DBUG_ENTER("mysql_repair_table");
+ set_all_mdl_upgradable(tables);
DBUG_RETURN(mysql_admin_table(thd, tables, check_opt,
"repair", TL_WRITE, 1,
test(check_opt->sql_flags & TT_USEFRM),
@@ -5250,7 +5308,7 @@ bool mysql_create_like_schema_frm(THD* thd, TABLE_LIST* schema_table,
bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table,
HA_CREATE_INFO *create_info)
{
- TABLE *name_lock= 0;
+ MDL_LOCK *target_lock= 0;
char src_path[FN_REFLEN], dst_path[FN_REFLEN + 1];
uint dst_path_length;
char *db= table->db;
@@ -5307,9 +5365,9 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table,
}
else
{
- if (lock_table_name_if_not_cached(thd, db, table_name, &name_lock))
+ if (lock_table_name_if_not_cached(thd, db, table_name, &target_lock))
goto err;
- if (!name_lock)
+ if (!target_lock)
goto table_exists;
dst_path_length= build_table_filename(dst_path, sizeof(dst_path) - 1,
db, table_name, reg_ext, 0);
@@ -5453,9 +5511,8 @@ binlog:
The table will be closed by unlink_open_table() at the end
of this function.
*/
- table->table= name_lock;
pthread_mutex_lock(&LOCK_open);
- if (reopen_name_locked_table(thd, table, FALSE))
+ if (reopen_name_locked_table(thd, table))
{
pthread_mutex_unlock(&LOCK_open);
goto err;
@@ -5468,6 +5525,10 @@ binlog:
DBUG_ASSERT(result == 0); // store_create_info() always return 0
write_bin_log(thd, TRUE, query.ptr(), query.length());
+
+ pthread_mutex_lock(&LOCK_open);
+ unlink_open_table(thd, table->table, FALSE);
+ pthread_mutex_unlock(&LOCK_open);
}
else // Case 1
write_bin_log(thd, TRUE, thd->query(), thd->query_length());
@@ -5482,12 +5543,8 @@ binlog:
res= FALSE;
err:
- if (name_lock)
- {
- pthread_mutex_lock(&LOCK_open);
- unlink_open_table(thd, name_lock, FALSE);
- pthread_mutex_unlock(&LOCK_open);
- }
+ if (target_lock)
+ mdl_release_exclusive_locks(&thd->mdl_context);
DBUG_RETURN(res);
}
@@ -5570,7 +5627,7 @@ mysql_discard_or_import_tablespace(THD *thd,
err:
ha_autocommit_or_rollback(thd, error);
thd->tablespace_op=FALSE;
-
+
if (error == 0)
{
my_ok(thd);
@@ -5578,7 +5635,7 @@ err:
}
table->file->print_error(error, MYF(0));
-
+
DBUG_RETURN(-1);
}
@@ -6424,7 +6481,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
Alter_info *alter_info,
uint order_num, ORDER *order, bool ignore)
{
- TABLE *table, *new_table= 0, *name_lock= 0;
+ TABLE *table, *new_table= 0;
+ MDL_LOCK *target_lock= 0;
int error= 0;
char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN + 1];
char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias;
@@ -6507,7 +6565,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
build_table_filename(reg_path, sizeof(reg_path) - 1, db, table_name, reg_ext, 0);
build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0);
- mysql_ha_rm_tables(thd, table_list, FALSE);
+ mysql_ha_rm_tables(thd, table_list);
/* DISCARD/IMPORT TABLESPACE is always alone in an ALTER TABLE */
if (alter_info->tablespace_op != NO_TABLESPACE_OP)
@@ -6563,13 +6621,14 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
if (wait_if_global_read_lock(thd,0,1))
DBUG_RETURN(TRUE);
- pthread_mutex_lock(&LOCK_open);
if (lock_table_names(thd, table_list))
{
error= 1;
goto view_err;
}
-
+
+ pthread_mutex_lock(&LOCK_open);
+
if (!do_rename(thd, table_list, new_db, new_name, new_name, 1))
{
if (mysql_bin_log.is_open())
@@ -6581,15 +6640,17 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
}
my_ok(thd);
}
+ pthread_mutex_unlock(&LOCK_open);
- unlock_table_names(thd, table_list, (TABLE_LIST*) 0);
+ unlock_table_names(thd);
view_err:
- pthread_mutex_unlock(&LOCK_open);
start_waiting_global_read_lock(thd);
DBUG_RETURN(error);
}
+ table_list->mdl_upgradable= TRUE;
+
if (!(table= open_n_lock_single_table(thd, table_list, TL_WRITE_ALLOW_READ)))
DBUG_RETURN(TRUE);
table->use_all_columns();
@@ -6644,9 +6705,9 @@ view_err:
}
else
{
- if (lock_table_name_if_not_cached(thd, new_db, new_name, &name_lock))
+ if (lock_table_name_if_not_cached(thd, new_db, new_name, &target_lock))
DBUG_RETURN(TRUE);
- if (!name_lock)
+ if (!target_lock)
{
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias);
DBUG_RETURN(TRUE);
@@ -6737,26 +6798,15 @@ view_err:
case LEAVE_AS_IS:
break;
case ENABLE:
- /*
- wait_while_table_is_used() ensures that table being altered is
- opened only by this thread and that TABLE::TABLE_SHARE::version
- of TABLE object corresponding to this table is 0.
- The latter guarantees that no DML statement will open this table
- until ALTER TABLE finishes (i.e. until close_thread_tables())
- while the fact that the table is still open gives us protection
- from concurrent DDL statements.
- */
- pthread_mutex_lock(&LOCK_open);
- wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
- pthread_mutex_unlock(&LOCK_open);
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ goto err;
DBUG_EXECUTE_IF("sleep_alter_enable_indexes", my_sleep(6000000););
error= table->file->ha_enable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
/* COND_refresh will be signaled in close_thread_tables() */
break;
case DISABLE:
- pthread_mutex_lock(&LOCK_open);
- wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
- pthread_mutex_unlock(&LOCK_open);
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+ goto err;
error=table->file->ha_disable_indexes(HA_KEY_SWITCH_NONUNIQ_SAVE);
/* COND_refresh will be signaled in close_thread_tables() */
break;
@@ -6773,24 +6823,19 @@ view_err:
table->alias);
}
- pthread_mutex_lock(&LOCK_open);
- /*
- Unlike to the above case close_cached_table() below will remove ALL
- instances of TABLE from table cache (it will also remove table lock
- held by this thread). So to make actual table renaming and writing
- to binlog atomic we have to put them into the same critical section
- protected by LOCK_open mutex. This also removes gap for races between
- access() and mysql_rename_table() calls.
- */
-
if (!error && (new_name != table_name || new_db != db))
{
thd_proc_info(thd, "rename");
/*
Then do a 'simple' rename of the table. First we need to close all
instances of 'source' table.
+ Note that if close_cached_table() returns error here (i.e. if
+ this thread was killed) then it must be that previous step of
+ simple rename did nothing and therefore we can safely reture
+ without additional clean-up.
*/
- close_cached_table(thd, table);
+ if (close_cached_table(thd, table))
+ goto err;
/*
Then, we want check once again that target table does not exist.
Actually the order of these two steps does not matter since
@@ -6807,6 +6852,7 @@ view_err:
else
{
*fn_ext(new_name)=0;
+ pthread_mutex_lock(&LOCK_open);
if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias, 0))
error= -1;
else if (Table_triggers_list::change_table_name(thd, db, table_name,
@@ -6816,6 +6862,7 @@ view_err:
table_name, 0);
error= -1;
}
+ pthread_mutex_unlock(&LOCK_open);
}
}
@@ -6837,11 +6884,24 @@ view_err:
table->file->print_error(error, MYF(0));
error= -1;
}
- if (name_lock)
- unlink_open_table(thd, name_lock, FALSE);
- pthread_mutex_unlock(&LOCK_open);
table_list->table= NULL; // For query cache
query_cache_invalidate3(thd, table_list, 0);
+
+ if (thd->locked_tables)
+ {
+ /*
+ Under LOCK TABLES we should adjust meta-data locks before finishing
+ statement. Otherwise we can rely on close_thread_tables() releasing
+ them.
+
+ TODO: Investigate what should be done with upgraded table-level
+ lock here...
+ */
+ if (new_name != table_name || new_db != db)
+ mdl_release_exclusive_locks(&thd->mdl_context);
+ else
+ mdl_downgrade_exclusive_locks(&thd->mdl_context);
+ }
DBUG_RETURN(error);
}
@@ -7073,7 +7133,7 @@ view_err:
#ifdef WITH_PARTITION_STORAGE_ENGINE
if (fast_alter_partition)
{
- DBUG_ASSERT(!name_lock);
+ DBUG_ASSERT(!target_lock);
DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info,
create_info, table_list,
db, table_name,
@@ -7158,7 +7218,7 @@ view_err:
tbl.db= new_db;
tbl.table_name= tbl.alias= tmp_name;
/* Table is in thd->temporary_tables */
- new_table= open_table(thd, &tbl, thd->mem_root, (bool*) 0,
+ new_table= open_table(thd, &tbl, thd->mem_root, (enum_open_table_action*) 0,
MYSQL_LOCK_IGNORE_FLUSH);
}
else
@@ -7168,10 +7228,10 @@ view_err:
build_table_filename(path, sizeof(path) - 1, new_db, tmp_name, "",
FN_IS_TMP);
/* Open our intermediate table */
- new_table=open_temporary_table(thd, path, new_db, tmp_name,0);
+ new_table= open_temporary_table(thd, path, new_db, tmp_name, 1);
}
if (!new_table)
- goto err1;
+ goto err_new_table_cleanup;
/*
Note: In case of MERGE table, we do not attach children. We do not
copy data for MERGE tables. Only the children have data.
@@ -7200,9 +7260,8 @@ view_err:
}
else
{
- pthread_mutex_lock(&LOCK_open);
- wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN);
- pthread_mutex_unlock(&LOCK_open);
+ if (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(),
alter_info->keys_onoff);
@@ -7253,7 +7312,7 @@ view_err:
table->key_info= key_info;
table->file->print_error(error, MYF(0));
table->key_info= save_key_info;
- goto err1;
+ goto err_new_table_cleanup;
}
}
/*end of if (index_add_count)*/
@@ -7276,14 +7335,14 @@ view_err:
index_drop_count)))
{
table->file->print_error(error, MYF(0));
- goto err1;
+ goto err_new_table_cleanup;
}
/* Tell the handler to finally drop the indexes. */
if ((error= table->file->final_drop_index(table)))
{
table->file->print_error(error, MYF(0));
- goto err1;
+ goto err_new_table_cleanup;
}
}
/*end of if (index_drop_count)*/
@@ -7296,16 +7355,16 @@ view_err:
/* Need to commit before a table is unlocked (NDB requirement). */
DBUG_PRINT("info", ("Committing before unlocking table"));
if (ha_autocommit_or_rollback(thd, 0) || end_active_trans(thd))
- goto err1;
+ goto err_new_table_cleanup;
committed= 1;
}
/*end of if (! new_table) for add/drop index*/
+ if (error)
+ goto err_new_table_cleanup;
+
if (table->s->tmp_table != NO_TMP_TABLE)
{
- /* We changed a temporary table */
- if (error)
- goto err1;
/* Close lock if this is a transactional table */
if (thd->lock)
{
@@ -7316,28 +7375,22 @@ view_err:
close_temporary_table(thd, table, 1, 1);
/* Should pass the 'new_name' as we store table name in the cache */
if (rename_temporary_table(thd, new_table, new_db, new_name))
- goto err1;
+ goto err_new_table_cleanup;
/* We don't replicate alter table statement on temporary tables */
if (!thd->current_stmt_binlog_row_based)
write_bin_log(thd, TRUE, thd->query(), thd->query_length());
goto end_temporary;
}
+ /*
+ Close the intermediate table that will be the new table, but do
+ not delete it! Even altough MERGE tables do not have their children
+ attached here it is safe to call close_temporary_table().
+ */
if (new_table)
{
- /*
- Close the intermediate table that will be the new table.
- Note that MERGE tables do not have their children attached here.
- */
- intern_close_table(new_table);
- my_free(new_table,MYF(0));
- }
- pthread_mutex_lock(&LOCK_open);
- if (error)
- {
- (void) quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP);
- pthread_mutex_unlock(&LOCK_open);
- goto err;
+ close_temporary_table(thd, new_table, 1, 0);
+ new_table= 0;
}
/*
@@ -7362,7 +7415,11 @@ view_err:
if (lower_case_table_names)
my_casedn_str(files_charset_info, old_name);
- wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME);
+ if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME))
+ goto err_new_table_cleanup;
+
+ pthread_mutex_lock(&LOCK_open);
+
close_data_files_and_morph_locks(thd, db, table_name);
error=0;
@@ -7431,8 +7488,7 @@ view_err:
table_list->table_name_length= strlen(new_name);
table_list->db= new_db;
table_list->db_length= strlen(new_db);
- table_list->table= name_lock;
- if (reopen_name_locked_table(thd, table_list, FALSE))
+ if (reopen_name_locked_table(thd, table_list))
goto err_with_placeholders;
t_table= table_list->table;
}
@@ -7446,16 +7502,24 @@ view_err:
if (t_table->file->ha_create_handler_files(path, NULL, CHF_INDEX_FLAG,
create_info))
goto err_with_placeholders;
- if (thd->locked_tables && new_name == table_name && new_db == db)
+ if (thd->locked_tables)
{
- /*
- We are going to reopen table down on the road, so we have to restore
- state of the TABLE object which we used for obtaining of handler
- object to make it suitable for reopening.
- */
- DBUG_ASSERT(t_table == table);
- table->open_placeholder= 1;
- close_handle_and_leave_table_as_lock(table);
+ if (new_name == table_name && new_db == db)
+ {
+ /*
+ We are going to reopen table down on the road, so we have to restore
+ state of the TABLE object which we used for obtaining of handler
+ object to make it suitable for reopening.
+ */
+ DBUG_ASSERT(t_table == table);
+ table->open_placeholder= 1;
+ close_handle_and_leave_table_as_lock(table);
+ }
+ else
+ {
+ /* Unlink the new name from the list of locked tables. */
+ unlink_open_table(thd, t_table, FALSE);
+ }
}
}
@@ -7464,7 +7528,7 @@ view_err:
if (thd->locked_tables && new_name == table_name && new_db == db)
{
thd->in_lock_tables= 1;
- error= reopen_tables(thd, 1, 1);
+ error= reopen_tables(thd, 1);
thd->in_lock_tables= 0;
if (error)
goto err_with_placeholders;
@@ -7508,18 +7572,17 @@ view_err:
table_list->table=0; // For query cache
query_cache_invalidate3(thd, table_list, 0);
- if (thd->locked_tables && (new_name != table_name || new_db != db))
+ if (thd->locked_tables)
{
- /*
- If are we under LOCK TABLES and did ALTER TABLE with RENAME we need
- to remove placeholders for the old table and for the target table
- from the list of open tables and table cache. If we are not under
- LOCK TABLES we can rely on close_thread_tables() doing this job.
- */
- pthread_mutex_lock(&LOCK_open);
- unlink_open_table(thd, table, FALSE);
- unlink_open_table(thd, name_lock, FALSE);
- pthread_mutex_unlock(&LOCK_open);
+ if ((new_name != table_name || new_db != db))
+ {
+ pthread_mutex_lock(&LOCK_open);
+ unlink_open_table(thd, table, FALSE);
+ pthread_mutex_unlock(&LOCK_open);
+ mdl_release_exclusive_locks(&thd->mdl_context);
+ }
+ else
+ mdl_downgrade_exclusive_locks(&thd->mdl_context);
}
end_temporary:
@@ -7530,7 +7593,7 @@ end_temporary:
thd->some_tables_deleted=0;
DBUG_RETURN(FALSE);
-err1:
+err_new_table_cleanup:
if (new_table)
{
/* close_temporary_table() frees the new_table pointer. */
@@ -7574,12 +7637,8 @@ err:
alter_info->datetime_field->field_name);
thd->abort_on_warning= save_abort_on_warning;
}
- if (name_lock)
- {
- pthread_mutex_lock(&LOCK_open);
- unlink_open_table(thd, name_lock, FALSE);
- pthread_mutex_unlock(&LOCK_open);
- }
+ if (target_lock)
+ mdl_release_exclusive_locks(&thd->mdl_context);
DBUG_RETURN(TRUE);
err_with_placeholders:
@@ -7589,9 +7648,8 @@ err_with_placeholders:
from list of open tables list and table cache.
*/
unlink_open_table(thd, table, FALSE);
- if (name_lock)
- unlink_open_table(thd, name_lock, FALSE);
pthread_mutex_unlock(&LOCK_open);
+ mdl_release_exclusive_locks(&thd->mdl_context);
DBUG_RETURN(TRUE);
}
/* mysql_alter_table */
diff --git a/sql/sql_test.cc b/sql/sql_test.cc
index d9beb77f546..3907aa6a5ae 100644
--- a/sql/sql_test.cc
+++ b/sql/sql_test.cc
@@ -75,7 +75,8 @@ print_where(COND *cond,const char *info, enum_query_type query_type)
void print_cached_tables(void)
{
uint idx,count,unused;
- TABLE *start_link,*lnk;
+ TABLE_SHARE *share;
+ TABLE *start_link, *lnk, *entry;
compile_time_assert(TL_WRITE_ONLY+1 == array_elements(lock_descriptions));
@@ -83,16 +84,26 @@ void print_cached_tables(void)
pthread_mutex_lock(&LOCK_open);
puts("DB Table Version Thread Open Lock");
- for (idx=unused=0 ; idx < open_cache.records ; idx++)
+ for (idx=unused=0 ; idx < table_def_cache.records ; idx++)
{
- TABLE *entry=(TABLE*) my_hash_element(&open_cache,idx);
- printf("%-14.14s %-32s%6ld%8ld%6d %s\n",
- entry->s->db.str, entry->s->table_name.str, entry->s->version,
- entry->in_use ? entry->in_use->thread_id : 0L,
- entry->db_stat ? 1 : 0,
- entry->in_use ? lock_descriptions[(int)entry->reginfo.lock_type] : "Not in use");
- if (!entry->in_use)
+ share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx);
+
+ I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables);
+ while ((entry= it++))
+ {
+ printf("%-14.14s %-32s%6ld%8ld%6d %s\n",
+ entry->s->db.str, entry->s->table_name.str, entry->s->version,
+ entry->in_use->thread_id, entry->db_stat ? 1 : 0,
+ lock_descriptions[(int)entry->reginfo.lock_type]);
+ }
+ it.init(share->free_tables);
+ while ((entry= it++))
+ {
unused++;
+ printf("%-14.14s %-32s%6ld%8ld%6d %s\n",
+ entry->s->db.str, entry->s->table_name.str, entry->s->version,
+ 0L, entry->db_stat ? 1 : 0, "Not in use");
+ }
}
count=0;
if ((start_link=lnk=unused_tables))
@@ -104,17 +115,18 @@ void print_cached_tables(void)
printf("unused_links isn't linked properly\n");
return;
}
- } while (count++ < open_cache.records && (lnk=lnk->next) != start_link);
+ } while (count++ < table_cache_count && (lnk=lnk->next) != start_link);
if (lnk != start_link)
{
printf("Unused_links aren't connected\n");
}
}
if (count != unused)
- printf("Unused_links (%d) doesn't match open_cache: %d\n", count,unused);
+ printf("Unused_links (%d) doesn't match table_def_cache: %d\n", count,
+ unused);
printf("\nCurrent refresh version: %ld\n",refresh_version);
- if (my_hash_check(&open_cache))
- printf("Error: File hash table is corrupted\n");
+ if (my_hash_check(&table_def_cache))
+ printf("Error: Table definition hash table is corrupted\n");
fflush(stdout);
pthread_mutex_unlock(&LOCK_open);
/* purecov: end */
@@ -380,7 +392,7 @@ static void display_table_locks(void)
LIST *list;
DYNAMIC_ARRAY saved_table_locks;
- (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO),open_cache.records + 20,50);
+ (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO), table_cache_count + 20,50);
pthread_mutex_lock(&THR_LOCK_lock);
for (list= thr_lock_thread_list; list; list= list_rest(list))
{
diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc
index ecbb6473ec4..a623b3c80f3 100644
--- a/sql/sql_trigger.cc
+++ b/sql/sql_trigger.cc
@@ -387,8 +387,6 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
!(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1)))
DBUG_RETURN(TRUE);
- pthread_mutex_lock(&LOCK_open);
-
if (!create)
{
bool if_exists= thd->lex->drop_if_exists;
@@ -444,26 +442,28 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
tables->required_type= FRMTYPE_TABLE;
/* Keep consistent with respect to other DDL statements */
- mysql_ha_rm_tables(thd, tables, TRUE);
+ mysql_ha_rm_tables(thd, tables);
if (thd->locked_tables)
{
- /* Table must be write locked */
if (name_lock_locked_table(thd, tables))
goto end;
+ pthread_mutex_lock(&LOCK_open);
}
else
{
- /* Grab the name lock and insert the placeholder*/
+ /*
+ Obtain exlusive meta-data lock on the table and remove TABLE
+ instances from cache.
+ */
if (lock_table_names(thd, tables))
goto end;
- /* Convert the placeholder to a real table */
- if (reopen_name_locked_table(thd, tables, TRUE))
- {
- unlock_table_name(thd, tables);
- goto end;
- }
+ pthread_mutex_lock(&LOCK_open);
+ expel_table_from_cache(0, tables->db, tables->table_name);
+
+ if (reopen_name_locked_table(thd, tables))
+ goto end_unlock;
}
table= tables->table;
@@ -472,11 +472,11 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
if (!create)
{
my_error(ER_TRG_DOES_NOT_EXIST, MYF(0));
- goto end;
+ goto end_unlock;
}
if (!(table->triggers= new (&table->mem_root) Table_triggers_list(table)))
- goto end;
+ goto end_unlock;
}
result= (create ?
@@ -489,7 +489,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
/* Make table suitable for reopening */
close_data_files_and_morph_locks(thd, tables->db, tables->table_name);
thd->in_lock_tables= 1;
- if (reopen_tables(thd, 1, 1))
+ if (reopen_tables(thd, 1))
{
/* To be safe remove this table from the set of LOCKED TABLES */
unlink_open_table(thd, tables->table, FALSE);
@@ -503,14 +503,22 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
thd->in_lock_tables= 0;
}
-end:
+end_unlock:
+ pthread_mutex_unlock(&LOCK_open);
+end:
if (!result)
{
write_bin_log(thd, TRUE, stmt_query.ptr(), stmt_query.length());
}
- pthread_mutex_unlock(&LOCK_open);
+ /*
+ If we are under LOCK TABLES we should restore original state of meta-data
+ locks. Otherwise call to close_thread_tables() will take care about both
+ TABLE instance created by reopen_name_locked_table() and meta-data lock.
+ */
+ if (thd->locked_tables)
+ mdl_downgrade_exclusive_locks(&thd->mdl_context);
if (need_start_waiting)
start_waiting_global_read_lock(thd);
@@ -1879,11 +1887,7 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db,
In the future, only an exclusive table name lock will be enough.
*/
#ifndef DBUG_OFF
- uchar key[MAX_DBKEY_LENGTH];
- uint key_length= (uint) (strmov(strmov((char*)&key[0], db)+1,
- old_table)-(char*)&key[0])+1;
-
- if (!is_table_name_exclusively_locked_by_this_thread(thd, key, key_length))
+ if (mdl_is_exclusive_lock_owner(&thd->mdl_context, 0, db, old_table))
safe_mutex_assert_owner(&LOCK_open);
#endif
diff --git a/sql/sql_udf.cc b/sql/sql_udf.cc
index a1a0d9633b7..25a0db2fbb8 100644
--- a/sql/sql_udf.cc
+++ b/sql/sql_udf.cc
@@ -142,6 +142,7 @@ void udf_init()
tables.alias= tables.table_name= (char*) "func";
tables.lock_type = TL_READ;
tables.db= db;
+ alloc_mdl_locks(&tables, new_thd->mem_root);
if (simple_open_n_lock_tables(new_thd, &tables))
{
@@ -485,6 +486,7 @@ int mysql_create_function(THD *thd,udf_func *udf)
bzero((char*) &tables,sizeof(tables));
tables.db= (char*) "mysql";
tables.table_name= tables.alias= (char*) "func";
+ alloc_mdl_locks(&tables, thd->mem_root);
/* Allow creation of functions even if we can't open func table */
if (!(table = open_ltable(thd, &tables, TL_WRITE, 0)))
goto err;
@@ -563,6 +565,7 @@ int mysql_drop_function(THD *thd,const LEX_STRING *udf_name)
bzero((char*) &tables,sizeof(tables));
tables.db=(char*) "mysql";
tables.table_name= tables.alias= (char*) "func";
+ alloc_mdl_locks(&tables, thd->mem_root);
if (!(table = open_ltable(thd, &tables, TL_WRITE, 0)))
goto err;
table->use_all_columns();
diff --git a/sql/sql_update.cc b/sql/sql_update.cc
index da8b2d046bb..1ad39a2f838 100644
--- a/sql/sql_update.cc
+++ b/sql/sql_update.cc
@@ -226,7 +226,7 @@ int mysql_update(THD *thd,
break;
if (!need_reopen)
DBUG_RETURN(1);
- close_tables_for_reopen(thd, &table_list);
+ close_tables_for_reopen(thd, &table_list, FALSE);
}
if (mysql_handle_derived(thd->lex, &mysql_derived_prepare) ||
@@ -1149,7 +1149,7 @@ reopen_tables:
*/
cleanup_items(thd->free_list);
- close_tables_for_reopen(thd, &table_list);
+ close_tables_for_reopen(thd, &table_list, FALSE);
goto reopen_tables;
}
diff --git a/sql/sql_view.cc b/sql/sql_view.cc
index 83341a53c3e..a0acde600c7 100644
--- a/sql/sql_view.cc
+++ b/sql/sql_view.cc
@@ -178,40 +178,17 @@ err:
static bool
fill_defined_view_parts (THD *thd, TABLE_LIST *view)
{
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length;
LEX *lex= thd->lex;
- bool not_used;
TABLE_LIST decoy;
memcpy (&decoy, view, sizeof (TABLE_LIST));
+ key_length= create_table_def_key(thd, key, view, 0);
- /*
- Let's reset decoy.view before calling open_table(): when we start
- supporting ALTER VIEW in PS/SP that may save us from a crash.
- */
-
- decoy.view= NULL;
-
- /*
- open_table() will return NULL if 'decoy' is idenitifying a view *and*
- there is no TABLE object for that view in the table cache. However,
- decoy.view will be set to 1.
-
- If there is a TABLE-instance for the oject identified by 'decoy',
- open_table() will return that instance no matter if it is a table or
- a view.
-
- Thus, there is no need to check for the return value of open_table(),
- since the return value itself does not mean anything.
- */
-
- open_table(thd, &decoy, thd->mem_root, &not_used, OPEN_VIEW_NO_PARSE);
-
- if (!decoy.view)
- {
- /* It's a table. */
- my_error(ER_WRONG_OBJECT, MYF(0), view->db, view->table_name, "VIEW");
+ if (tdc_open_view(thd, &decoy, decoy.alias, key, key_length,
+ thd->mem_root, OPEN_VIEW_NO_PARSE))
return TRUE;
- }
if (!lex->definer)
{
@@ -400,6 +377,35 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
DBUG_ASSERT(!lex->proc_list.first && !lex->result &&
!lex->param_list.elements);
+ /*
+ We can't allow taking exclusive meta-data locks of unlocked view under
+ LOCK TABLES since this might lead to deadlock. Since at the moment we
+ can't really lock view with LOCK TABLES we simply prohibit creation/
+ alteration of views under LOCK TABLES.
+ */
+
+ if (thd->locked_tables)
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ res= TRUE;
+ goto err;
+ }
+
+ if ((res= create_view_precheck(thd, tables, view, mode)))
+ goto err;
+
+ lex->link_first_table_back(view, link_to_local);
+ view->open_table_type= TABLE_LIST::TAKE_EXCLUSIVE_MDL;
+
+ if (open_and_lock_tables(thd, lex->query_tables))
+ {
+ view= lex->unlink_first_table(&link_to_local);
+ res= TRUE;
+ goto err;
+ }
+
+ view= lex->unlink_first_table(&link_to_local);
+
if (mode != VIEW_CREATE_NEW)
{
if (mode == VIEW_ALTER &&
@@ -464,16 +470,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
}
}
#endif
-
- if ((res= create_view_precheck(thd, tables, view, mode)))
- goto err;
-
- if (open_and_lock_tables(thd, tables))
- {
- res= TRUE;
- goto err;
- }
-
/*
check that tables are not temporary and this VIEW do not used in query
(it is possible with ALTERing VIEW).
@@ -615,11 +611,13 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
}
#endif
+
if (wait_if_global_read_lock(thd, 0, 0))
{
res= TRUE;
goto err;
}
+
pthread_mutex_lock(&LOCK_open);
res= mysql_register_view(thd, view, mode);
@@ -1331,7 +1329,10 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table,
anyway.
*/
for (tbl= view_main_select_tables; tbl; tbl= tbl->next_local)
+ {
tbl->lock_type= table->lock_type;
+ tbl->mdl_upgradable= table->mdl_upgradable;
+ }
/*
If the view is mergeable, we might want to
INSERT/UPDATE/DELETE into tables of this view. Preserve the
@@ -1579,6 +1580,21 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode)
bool something_wrong= FALSE;
DBUG_ENTER("mysql_drop_view");
+ /*
+ We can't allow dropping of unlocked view under LOCK TABLES since this
+ might lead to deadlock. But since we can't really lock view with LOCK
+ TABLES we have to simply prohibit dropping of views.
+ */
+
+ if (thd->locked_tables)
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ DBUG_RETURN(TRUE);
+ }
+
+ if (lock_table_names(thd, views))
+ DBUG_RETURN(TRUE);
+
pthread_mutex_lock(&LOCK_open);
for (view= views; view; view= view->next_local)
{
diff --git a/sql/table.cc b/sql/table.cc
index 7ea04ed3e15..181014df9f4 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -316,6 +316,9 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key,
share->table_map_id= ~0UL;
share->cached_row_logging_check= -1;
+ share->used_tables.empty();
+ share->free_tables.empty();
+
memcpy((char*) &share->mem_root, (char*) &mem_root, sizeof(mem_root));
pthread_mutex_init(&share->mutex, MY_MUTEX_INIT_FAST);
pthread_cond_init(&share->cond, NULL);
@@ -382,6 +385,9 @@ void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key,
*/
share->table_map_id= (ulong) thd->query_id;
+ share->used_tables.empty();
+ share->free_tables.empty();
+
DBUG_VOID_RETURN;
}
@@ -4832,6 +4838,20 @@ size_t max_row_length(TABLE *table, const uchar *data)
return length;
}
+
+/**
+ Helper function which allows to allocate metadata lock request
+ objects for all elements of table list.
+*/
+
+void alloc_mdl_locks(TABLE_LIST *table_list, MEM_ROOT *root)
+{
+ for ( ; table_list ; table_list= table_list->next_global)
+ table_list->mdl_lock= mdl_alloc_lock(0, table_list->db,
+ table_list->table_name, root);
+}
+
+
/*****************************************************************************
** Instansiate templates
*****************************************************************************/
diff --git a/sql/table.h b/sql/table.h
index 76bebd3fdaa..b0598e07299 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -17,6 +17,8 @@
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+#include "sql_plist.h"
+
/* Structs that defines the TABLE */
class Item; /* Needed by ORDER */
@@ -28,6 +30,7 @@ class st_select_lex;
class partition_info;
class COND_EQUAL;
class Security_context;
+struct MDL_LOCK;
/*************************************************************************/
@@ -288,6 +291,9 @@ typedef enum enum_table_category TABLE_CATEGORY;
TABLE_CATEGORY get_table_category(const LEX_STRING *db,
const LEX_STRING *name);
+
+struct TABLE_share;
+
/*
This structure is shared between different table objects. There is one
instance of table share per one table in the database.
@@ -310,6 +316,13 @@ struct TABLE_SHARE
pthread_cond_t cond; /* To signal that share is ready */
TABLE_SHARE *next, **prev; /* Link to unused shares */
+ /*
+ Doubly-linked (back-linked) lists of used and unused TABLE objects
+ for this share.
+ */
+ I_P_List <TABLE, TABLE_share> used_tables;
+ I_P_List <TABLE, TABLE_share> free_tables;
+
/* The following is copied to each TABLE on OPEN */
Field **field;
Field **found_next_number_field;
@@ -613,6 +626,19 @@ struct TABLE
handler *file;
TABLE *next, *prev;
+private:
+ /**
+ Links for the lists of used/unused TABLE objects for this share.
+ Declared as private to avoid direct manipulation with those objects.
+ One should use methods of I_P_List template instead.
+ */
+ TABLE *share_next, **share_prev;
+
+ friend struct TABLE_share;
+ friend bool reopen_table(TABLE *table);
+
+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 */
@@ -814,6 +840,7 @@ struct TABLE
partition_info *part_info; /* Partition related information */
bool no_partitions_used; /* If true, all partitions have been pruned away */
#endif
+ MDL_LOCK *mdl_lock;
bool fill_item_list(List<Item> *item_list) const;
void reset_item_list(List<Item> *item_list) const;
@@ -859,6 +886,25 @@ struct TABLE
bool is_children_attached(void);
};
+
+/**
+ Helper class which specifies which members of TABLE are used for
+ participation in the list of used/unused TABLE objects for the share.
+*/
+
+struct TABLE_share
+{
+ static inline TABLE **next_ptr(TABLE *l)
+ {
+ return &l->share_next;
+ }
+ static inline TABLE ***prev_ptr(TABLE *l)
+ {
+ return &l->share_prev;
+ }
+};
+
+
enum enum_schema_table_state
{
NOT_PROCESSED= 0,
@@ -1326,11 +1372,18 @@ struct TABLE_LIST
*/
bool prelocking_placeholder;
/*
- This TABLE_LIST object corresponds to the table to be created
- so it is possible that it does not exist (used in CREATE TABLE
- ... SELECT implementation).
+ This TABLE_LIST object corresponds to the table/view which requires
+ special handling/meta-data locking. For example this is a target
+ table in CREATE TABLE ... SELECT so it is possible that it does not
+ exist and we should take exclusive meta-data lock on it in this
+ case.
+ */
+ enum {NORMAL_OPEN= 0, OPEN_OR_CREATE, TAKE_EXCLUSIVE_MDL} open_table_type;
+ /**
+ Indicates that for this table/view we need to take shared metadata
+ lock which should be upgradable to exclusive metadata lock.
*/
- bool create;
+ bool mdl_upgradable;
bool internal_tmp_table;
/** TRUE if an alias for this table was specified in the SQL. */
bool is_alias;
@@ -1379,6 +1432,9 @@ struct TABLE_LIST
bool has_table_lookup_value;
uint table_open_method;
enum enum_schema_table_state schema_table_state;
+
+ MDL_LOCK *mdl_lock;
+
void calc_md5(char *buffer);
void set_underlying_merge();
int view_check_option(THD *thd, bool ignore_failure);
@@ -1386,8 +1442,7 @@ struct TABLE_LIST
void cleanup_items();
bool placeholder()
{
- return derived || view || schema_table || (create && !table->db_stat) ||
- !table;
+ return derived || view || schema_table || !table;
}
void print(THD *thd, String *str, enum_query_type query_type);
bool check_single_table(TABLE_LIST **table, table_map map,
@@ -1746,4 +1801,17 @@ static inline void dbug_tmp_restore_column_maps(MY_BITMAP *read_set,
size_t max_row_length(TABLE *table, const uchar *data);
+
+void alloc_mdl_locks(TABLE_LIST *table_list, MEM_ROOT *root);
+
+/**
+ Helper function which allows to mark all elements in table list
+ as requiring upgradable metadata locks.
+*/
+
+inline void set_all_mdl_upgradable(TABLE_LIST *tables)
+{
+ for (; tables; tables= tables->next_global)
+ tables->mdl_upgradable= TRUE;
+}
#endif /* TABLE_INCLUDED */
diff --git a/storage/myisammrg/ha_myisammrg.cc b/storage/myisammrg/ha_myisammrg.cc
index 471e2243aac..b2181636492 100644
--- a/storage/myisammrg/ha_myisammrg.cc
+++ b/storage/myisammrg/ha_myisammrg.cc
@@ -270,6 +270,12 @@ static int myisammrg_parent_open_callback(void *callback_param,
/* 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();