summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mysql-test/r/create.result94
-rw-r--r--mysql-test/r/create_select-big.result164
-rw-r--r--mysql-test/r/trigger.result35
-rw-r--r--mysql-test/t/create.test112
-rw-r--r--mysql-test/t/create_select-big.test268
-rw-r--r--mysql-test/t/trigger.test26
-rw-r--r--sql/lock.cc26
-rw-r--r--sql/mysql_priv.h22
-rw-r--r--sql/sql_base.cc449
-rw-r--r--sql/sql_handler.cc25
-rw-r--r--sql/sql_insert.cc179
-rw-r--r--sql/sql_parse.cc18
-rw-r--r--sql/sql_prepare.cc15
-rw-r--r--sql/sql_table.cc228
-rw-r--r--sql/sql_trigger.cc2
-rw-r--r--sql/sql_yacc.yy4
-rw-r--r--sql/table.h43
17 files changed, 1465 insertions, 245 deletions
diff --git a/mysql-test/r/create.result b/mysql-test/r/create.result
index c7f8ba17930..ef22b21e9fb 100644
--- a/mysql-test/r/create.result
+++ b/mysql-test/r/create.result
@@ -782,6 +782,100 @@ t1 CREATE TABLE `t1` (
`i` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 MAX_ROWS=4294967295
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
+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;
+ERROR 42000: Key column 'a' doesn't exist in table
+create table t1 (a int);
+create table if not exists t1 select 1 as a, 2 as b;
+ERROR 21S01: Column count doesn't match value count at row 1
+drop table t1;
+create table t1 (primary key (a)) (select 1 as a) union all (select 1 as a);
+ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
+create table t1 (i int);
+create table t1 select 1 as i;
+ERROR 42S01: Table 't1' already exists
+create table if not exists t1 select 1 as i;
+Warnings:
+Note 1050 Table 't1' already exists
+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;
+i
+1
+alter table t1 add primary key (i);
+create table if not exists t1 (select 2 as i) union all (select 2 as i);
+ERROR 23000: Duplicate entry '2' for key 'PRIMARY'
+select * from t1;
+i
+1
+2
+drop table t1;
+create temporary table t1 (j int);
+create table if not exists t1 select 1;
+Warnings:
+Note 1050 Table 't1' already exists
+select * from t1;
+j
+1
+drop temporary table t1;
+select * from t1;
+ERROR 42S02: Table 'test.t1' doesn't exist
+drop table t1;
+ERROR 42S02: Unknown table 't1'
+create table t1 (i int);
+insert into t1 values (1), (2);
+lock tables t1 read;
+create table t2 select * from t1;
+ERROR HY000: Table 't2' was not locked with LOCK TABLES
+create table if not exists t2 select * from t1;
+ERROR HY000: Table 't2' was not locked with LOCK TABLES
+unlock tables;
+create table t2 (j int);
+lock tables t1 read;
+create table t2 select * from t1;
+ERROR HY000: Table 't2' was not locked with LOCK TABLES
+create table if not exists t2 select * from t1;
+ERROR HY000: Table 't2' was not locked with LOCK TABLES
+unlock tables;
+lock table t1 read, t2 read;
+create table t2 select * from t1;
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+create table if not exists t2 select * from t1;
+ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
+unlock tables;
+lock table t1 read, t2 write;
+create table t2 select * from t1;
+ERROR 42S01: Table 't2' already exists
+create table if not exists t2 select * from t1;
+Warnings:
+Note 1050 Table 't2' already exists
+select * from t1;
+i
+1
+2
+unlock tables;
+drop table t2;
+lock tables t1 read;
+create temporary table t2 select * from t1;
+create temporary table if not exists t2 select * from t1;
+Warnings:
+Note 1050 Table 't2' already exists
+select * from t2;
+i
+1
+2
+1
+2
+unlock tables;
+drop table t1, t2;
create table t1 (upgrade int);
drop table t1;
End of 5.0 tests
diff --git a/mysql-test/r/create_select-big.result b/mysql-test/r/create_select-big.result
new file mode 100644
index 00000000000..1c393bd2224
--- /dev/null
+++ b/mysql-test/r/create_select-big.result
@@ -0,0 +1,164 @@
+drop table if exists t1,t2,t3,t4,t5;
+set session debug="+d,sleep_create_select_before_create";
+create table t1 select 1 as i;;
+create table t1 (j char(5));
+ERROR 42S01: Table 't1' already exists
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(1) NOT NULL DEFAULT '0'
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+drop table t1;
+create table t1 select 1 as i;;
+create table t1 select "Test" as j;
+ERROR 42S01: Table 't1' already exists
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(1) NOT NULL DEFAULT '0'
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+drop table t1;
+create table t3 (j char(5));
+create table t1 select 1 as i;;
+create table t1 like t3;
+ERROR 42S01: Table 't1' already exists
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(1) NOT NULL DEFAULT '0'
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+drop table t1;
+create table t1 select 1 as i;;
+rename table t3 to t1;
+ERROR 42S01: Table 't1' already exists
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(1) NOT NULL DEFAULT '0'
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+drop table t1;
+create table t1 select 1 as i;;
+alter table t3 rename to t1;
+ERROR 42S01: Table 't1' already exists
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(1) NOT NULL DEFAULT '0'
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+drop table t1;
+create table t1 select 1 as i;;
+alter table t3 rename to t1, add k int;
+ERROR 42S01: Table 't1' already exists
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `i` int(1) NOT NULL DEFAULT '0'
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+drop table t1, t3;
+set session debug="-d,sleep_create_select_before_create:+d,sleep_create_select_before_open";
+create table t1 select 1 as i;;
+drop table t1;
+create table t1 select 1 as i;;
+rename table t1 to t2;
+drop table t2;
+create table t1 select 1 as i;;
+select * from t1;
+i
+1
+drop table t1;
+create table t1 select 1 as i;;
+insert into t1 values (2);
+select * from t1;
+i
+1
+2
+drop table t1;
+set @a:=0;
+create table t1 select 1 as i;;
+create trigger t1_bi before insert on t1 for each row set @a:=1;
+select @a;
+@a
+0
+drop table t1;
+set session debug="-d,sleep_create_select_before_open:+d,sleep_create_select_before_lock";
+create table t1 select 1 as i;;
+drop table t1;
+create table t1 select 1 as i;;
+rename table t1 to t2;
+drop table t2;
+create table t1 select 1 as i;;
+select * from t1;
+i
+1
+drop table t1;
+create table t1 select 1 as i;;
+insert into t1 values (2);
+select * from t1;
+i
+1
+2
+drop table t1;
+set @a:=0;
+create table t1 select 1 as i;;
+create trigger t1_bi before insert on t1 for each row set @a:=1;
+select @a;
+@a
+0
+drop table t1;
+set session debug="-d,sleep_create_select_before_lock:+d,sleep_create_select_before_check_if_exists";
+create table t1 (i int);
+create table if not exists t1 select 1 as i;;
+drop table t1;
+Warnings:
+Note 1050 Table 't1' already exists
+create table t1 (i int);
+set @a:=0;
+create table if not exists t1 select 1 as i;;
+create trigger t1_bi before insert on t1 for each row set @a:=1;
+Warnings:
+Note 1050 Table 't1' already exists
+select @a;
+@a
+0
+select * from t1;
+i
+1
+drop table t1;
+set session debug="-d,sleep_create_select_before_check_if_exists";
+create table t2 (a int);
+create table t4 (b int);
+lock table t4 write;
+select 1;
+1
+1
+create table t3 as select * from t4;;
+create table t1 select * from t2, t3;;
+unlock tables;
+select * from t1;
+a b
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `a` int(11) DEFAULT NULL,
+ `b` int(11) DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+drop table t1, t3;
+lock table t4 read;
+select 1;
+1
+1
+rename table t4 to t3;;
+create table if not exists t1 select 1 as i from t2, t3;;
+create table t5 (j int);
+rename table t5 to t1;
+unlock tables;
+Warnings:
+Note 1050 Table 't1' already exists
+select * from t1;
+j
+show create table t1;
+Table Create Table
+t1 CREATE TABLE `t1` (
+ `j` int(11) DEFAULT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=latin1
+drop table t1, t2, t3;
diff --git a/mysql-test/r/trigger.result b/mysql-test/r/trigger.result
index c24f7b6b06f..15c325aba52 100644
--- a/mysql-test/r/trigger.result
+++ b/mysql-test/r/trigger.result
@@ -1414,4 +1414,39 @@ id val
DROP TRIGGER trg27006_a_insert;
DROP TRIGGER trg27006_a_update;
drop table t1,t2;
+drop table if exists t1, t2, t3;
+create table t1 (i int);
+create trigger t1_bi before insert on t1 for each row set new.i = 7;
+create trigger t1_ai after insert on t1 for each row set @a := 7;
+create table t2 (j int);
+insert into t2 values (1), (2);
+set @a:="";
+create table if not exists t1 select * from t2;
+Warnings:
+Note 1050 Table 't1' already exists
+select * from t1;
+i
+7
+7
+select @a;
+@a
+7
+drop trigger t1_bi;
+drop trigger t1_ai;
+create table t3 (isave int);
+create trigger t1_bi before insert on t1 for each row insert into t3 values (new.i);
+create table if not exists t1 select * from t2;
+Warnings:
+Note 1050 Table 't1' already exists
+select * from t1;
+i
+7
+7
+1
+2
+select * from t3;
+isave
+1
+2
+drop table t1, t2, t3;
End of 5.0 tests
diff --git a/mysql-test/t/create.test b/mysql-test/t/create.test
index 5fca63a295c..243cdea009e 100644
--- a/mysql-test/t/create.test
+++ b/mysql-test/t/create.test
@@ -673,6 +673,117 @@ alter table t1 max_rows=100000000000;
show create table t1;
drop table t1;
+
+#
+# Tests for errors happening at various stages of CREATE TABLES ... SELECT
+#
+# (Also checks that it behaves atomically in the sense that in case
+# of error it is automatically dropped if it has not existed before.)
+#
+# 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
+create table t1 select * from t1;
+# Error which happens before select_create::prepare()
+--error ER_CANT_AGGREGATE_2COLLATIONS
+create table t1 select coalesce('a' collate latin1_swedish_ci,'b' collate latin1_bin);
+# Error during table creation
+--error ER_KEY_COLUMN_DOES_NOT_EXITS
+create table t1 (primary key(a)) select "b" as b;
+# Error in select_create::prepare() which is not related to table creation
+create table t1 (a int);
+--error ER_WRONG_VALUE_COUNT_ON_ROW
+create table if not exists t1 select 1 as a, 2 as b;
+drop table t1;
+# Finally error which happens during insert
+--error ER_DUP_ENTRY_WITH_KEY_NAME
+create table t1 (primary key (a)) (select 1 as a) union all (select 1 as a);
+# What happens if table already exists ?
+create table t1 (i int);
+--error ER_TABLE_EXISTS_ERROR
+create table t1 select 1 as i;
+create table if not exists t1 select 1 as i;
+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);
+select * from t1;
+# Error which happens during insertion of rows
+alter table t1 add primary key (i);
+--error ER_DUP_ENTRY_WITH_KEY_NAME
+create table if not exists t1 (select 2 as i) union all (select 2 as i);
+select * from t1;
+drop table t1;
+
+
+# Base vs temporary tables dillema (a.k.a. bug#24508 "Inconsistent
+# results of CREATE TABLE ... SELECT when temporary table exists").
+# In this situation we either have to create non-temporary table and
+# insert data in it or insert data in temporary table without creation
+# of permanent table. Since currently temporary tables always shadow
+# permanent tables we adopt second approach.
+create temporary table t1 (j int);
+create table if not exists t1 select 1;
+select * from t1;
+drop temporary table t1;
+--error ER_NO_SUCH_TABLE
+select * from t1;
+--error ER_BAD_TABLE_ERROR
+drop table t1;
+
+
+#
+# CREATE TABLE ... SELECT and LOCK TABLES
+#
+# There is little sense in using CREATE TABLE ... SELECT under
+# LOCK TABLES as it mostly does not work. At least we check that
+# the server doesn't crash, hang and produces sensible errors.
+# Includes test for bug #20662 "Infinite loop in CREATE TABLE
+# IF NOT EXISTS ... SELECT with locked tables".
+create table t1 (i int);
+insert into t1 values (1), (2);
+lock tables t1 read;
+--error ER_TABLE_NOT_LOCKED
+create table t2 select * from t1;
+--error ER_TABLE_NOT_LOCKED
+create table if not exists t2 select * from t1;
+unlock tables;
+create table t2 (j int);
+lock tables t1 read;
+--error ER_TABLE_NOT_LOCKED
+create table t2 select * from t1;
+# This should not be ever allowed as it will undermine
+# lock-all-at-once approach
+--error ER_TABLE_NOT_LOCKED
+create table if not exists t2 select * from t1;
+unlock tables;
+lock table t1 read, t2 read;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+create table t2 select * from t1;
+--error ER_TABLE_NOT_LOCKED_FOR_WRITE
+create table if not exists t2 select * from t1;
+unlock tables;
+lock table t1 read, t2 write;
+--error ER_TABLE_EXISTS_ERROR
+create table t2 select * from t1;
+# This is the only case which really works.
+create table if not exists t2 select * from t1;
+select * from t1;
+unlock tables;
+drop table t2;
+
+# OTOH CREATE TEMPORARY TABLE ... SELECT should work
+# well under LOCK TABLES.
+lock tables t1 read;
+create temporary table t2 select * from t1;
+create temporary table if not exists t2 select * from t1;
+select * from t2;
+unlock tables;
+drop table t1, t2;
+
+
#
# Bug#21772: can not name a column 'upgrade' when create a table
#
@@ -715,6 +826,7 @@ INSERT INTO t2 select * from t1;
SELECT * from t2;
drop table t1,t2;
+
#
# Test incorrect database names
#
diff --git a/mysql-test/t/create_select-big.test b/mysql-test/t/create_select-big.test
new file mode 100644
index 00000000000..3fa655c5501
--- /dev/null
+++ b/mysql-test/t/create_select-big.test
@@ -0,0 +1,268 @@
+# Tests for various aspects of CREATE TABLE ... SELECT implementation
+#
+# Note that we don't test general CREATE TABLE ... SELECT functionality here as
+# it is already covered by create.test. We are more interested in extreme cases.
+#
+# This test takes rather long time so let us run it only in --big-test mode
+--source include/big_test.inc
+# We are using some debug-only features in this test
+--source include/have_debug.inc
+
+# Create auxilliary connections
+connect (addconroot1, localhost, root,,);
+connect (addconroot2, localhost, root,,);
+connect (addconroot3, localhost, root,,);
+connection default;
+
+--disable_warnings
+drop table if exists t1,t2,t3,t4,t5;
+--enable_warnings
+
+
+#
+# Tests for concurrency problems.
+#
+# We introduce delays between various stages of table creation
+# and check that other statements dealing with this table cannot
+# interfere during those delays.
+#
+# What happens in situation when other statement messes with
+# table to be created before it is created ?
+# Concurrent CREATE TABLE
+set session debug="+d,sleep_create_select_before_create";
+--send create table t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+--error ER_TABLE_EXISTS_ERROR
+create table t1 (j char(5));
+connection default;
+--reap
+show create table t1;
+drop table t1;
+# Concurrent CREATE TABLE ... SELECT
+--send create table t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+--error ER_TABLE_EXISTS_ERROR
+create table t1 select "Test" as j;
+connection default;
+--reap
+show create table t1;
+drop table t1;
+# Concurrent CREATE TABLE LIKE
+create table t3 (j char(5));
+--send create table t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+--error ER_TABLE_EXISTS_ERROR
+create table t1 like t3;
+connection default;
+--reap
+show create table t1;
+drop table t1;
+# Concurrent RENAME TABLE
+--send create table t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+--error ER_TABLE_EXISTS_ERROR
+rename table t3 to t1;
+connection default;
+--reap
+show create table t1;
+drop table t1;
+# Concurrent ALTER TABLE RENAME
+--send create table t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+--error ER_TABLE_EXISTS_ERROR
+alter table t3 rename to t1;
+connection default;
+--reap
+show create table t1;
+drop table t1;
+# Concurrent ALTER TABLE RENAME which also adds column
+--send create table t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+--error ER_TABLE_EXISTS_ERROR
+alter table t3 rename to t1, add k int;
+connection default;
+--reap
+show create table t1;
+drop table t1, t3;
+# What happens if other statement sneaks in after the table
+# creation but before its opening ?
+set session debug="-d,sleep_create_select_before_create:+d,sleep_create_select_before_open";
+# Concurrent DROP TABLE
+--send create table t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+drop table t1;
+connection default;
+--reap
+# Concurrent RENAME TABLE
+--send create table t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+rename table t1 to t2;
+connection default;
+--reap
+drop table t2;
+# Concurrent SELECT
+--send create table t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+select * from t1;
+connection default;
+--reap
+drop table t1;
+# Concurrent INSERT
+--send create table t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+insert into t1 values (2);
+connection default;
+--reap
+select * from t1;
+drop table t1;
+# Concurrent CREATE TRIGGER
+set @a:=0;
+--send create table t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+create trigger t1_bi before insert on t1 for each row set @a:=1;
+connection default;
+--reap
+select @a;
+drop table t1;
+# Okay, now the same tests for the potential gap between open and lock
+set session debug="-d,sleep_create_select_before_open:+d,sleep_create_select_before_lock";
+# Concurrent DROP TABLE
+--send create table t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+drop table t1;
+connection default;
+--reap
+# Concurrent RENAME TABLE
+--send create table t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+rename table t1 to t2;
+connection default;
+--reap
+drop table t2;
+# Concurrent SELECT
+--send create table t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+select * from t1;
+connection default;
+--reap
+drop table t1;
+# Concurrent INSERT
+--send create table t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+insert into t1 values (2);
+connection default;
+--reap
+select * from t1;
+drop table t1;
+# Concurrent CREATE TRIGGER
+set @a:=0;
+--send create table t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+create trigger t1_bi before insert on t1 for each row set @a:=1;
+connection default;
+--reap
+select @a;
+drop table t1;
+# Some tests for case with existing table
+set session debug="-d,sleep_create_select_before_lock:+d,sleep_create_select_before_check_if_exists";
+create table t1 (i int);
+# Concurrent DROP TABLE
+--send create table if not exists t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+drop table t1;
+connection default;
+--reap
+# Concurrent CREATE TRIGGER
+create table t1 (i int);
+set @a:=0;
+--send create table if not exists t1 select 1 as i;
+connection addconroot1;
+--sleep 2
+create trigger t1_bi before insert on t1 for each row set @a:=1;
+connection default;
+--reap
+select @a;
+select * from t1;
+drop table t1;
+set session debug="-d,sleep_create_select_before_check_if_exists";
+
+
+# Test for some details of CREATE TABLE ... SELECT implementation.
+#
+# We check that create placeholder is handled properly if we have
+# to reopen tables in open_tables().
+# This test heavily relies on current implementation of name-locking/
+# table cache so it may stop working if it changes. OTOH it such problem
+# will serve as warning that such changes should not be done lightly.
+create table t2 (a int);
+create table t4 (b int);
+connection addconroot2;
+lock table t4 write;
+select 1;
+connection addconroot1;
+# Create placeholder/name-lock for t3
+--send create table t3 as select * from t4;
+--sleep 2
+connection default;
+# This statement creates placeholder for t1, then opens t2,
+# then meets name-lock for t3 and then reopens all tables
+--send create table t1 select * from t2, t3;
+--sleep 2
+connection addconroot2;
+unlock tables;
+connection addconroot1;
+--reap
+connection default;
+--reap
+select * from t1;
+show create table t1;
+drop table t1, t3;
+# Now similar test which proves that we really temporarily
+# remove placeholder when we reopen tables.
+connection addconroot2;
+lock table t4 read;
+select 1;
+connection addconroot1;
+# Create name-lock for t3
+--send rename table t4 to t3;
+--sleep 2
+connection default;
+# This statement creates placeholder for t1, then opens t2,
+# then meets name-lock for t3 and then reopens all tables
+--send create table if not exists t1 select 1 as i from t2, t3;
+--sleep 2
+connection addconroot3;
+# We should be able to take name-lock on table t1 as we should not have
+# open placeholder for it at this point (otherwise it is possible to
+# come-up with situation which will lead to deadlock, e.g. think of
+# concurrent CREATE TABLE t1 SELECT * FROM t2 and RENAME TABLE t2 TO t1)
+create table t5 (j int);
+# This statement takes name-lock on t1 and therefore proves
+# that there is no active open placeholder for it.
+rename table t5 to t1;
+connection addconroot2;
+unlock tables;
+connection addconroot1;
+--reap
+connection default;
+--reap
+select * from t1;
+show create table t1;
+drop table t1, t2, t3;
diff --git a/mysql-test/t/trigger.test b/mysql-test/t/trigger.test
index 55b67adf7c6..616e184796e 100644
--- a/mysql-test/t/trigger.test
+++ b/mysql-test/t/trigger.test
@@ -1737,4 +1737,30 @@ DROP TRIGGER trg27006_a_insert;
DROP TRIGGER trg27006_a_update;
drop table t1,t2;
+#
+# Bug #20903 "Crash when using CREATE TABLE .. SELECT and triggers"
+#
+
+--disable_warnings
+drop table if exists t1, t2, t3;
+--enable_warnings
+create table t1 (i int);
+create trigger t1_bi before insert on t1 for each row set new.i = 7;
+create trigger t1_ai after insert on t1 for each row set @a := 7;
+create table t2 (j int);
+insert into t2 values (1), (2);
+set @a:="";
+create table if not exists t1 select * from t2;
+select * from t1;
+select @a;
+# Let us check that trigger that involves table also works ok.
+drop trigger t1_bi;
+drop trigger t1_ai;
+create table t3 (isave int);
+create trigger t1_bi before insert on t1 for each row insert into t3 values (new.i);
+create table if not exists t1 select * from t2;
+select * from t1;
+select * from t3;
+drop table t1, t2, t3;
+
--echo End of 5.0 tests
diff --git a/sql/lock.cc b/sql/lock.cc
index 4427e57a938..ac6e90f68ff 100644
--- a/sql/lock.cc
+++ b/sql/lock.cc
@@ -892,8 +892,6 @@ end:
int lock_table_name(THD *thd, TABLE_LIST *table_list, bool check_in_use)
{
TABLE *table;
- TABLE_SHARE *share;
- char *key_buff;
char key[MAX_DBKEY_LENGTH];
char *db= table_list->db;
uint key_length;
@@ -921,29 +919,11 @@ int lock_table_name(THD *thd, TABLE_LIST *table_list, bool check_in_use)
}
}
}
- /*
- 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(-1);
- 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;
- table_list->table=table;
- if (my_hash_insert(&open_cache, (byte*) table))
- {
- my_free((gptr) table,MYF(0));
+ 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,
diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h
index 213512cd69f..c245a976b45 100644
--- a/sql/mysql_priv.h
+++ b/sql/mysql_priv.h
@@ -794,8 +794,6 @@ check_and_unset_inject_value(int value)
#endif
-uint build_table_path(char *buff, size_t bufflen, const char *db,
- const char *table, const char *ext);
void write_bin_log(THD *thd, bool clear_error,
char const *query, ulong query_length);
@@ -970,6 +968,12 @@ bool mysql_create_table(THD *thd,const char *db, const char *table_name,
List<create_field> &fields, List<Key> &keys,
bool tmp_table, uint select_field_count,
bool use_copy_create_info);
+bool mysql_create_table_no_lock(THD *thd, const char *db,
+ const char *table_name,
+ HA_CREATE_INFO *create_info,
+ List<create_field> &fields, List<Key> &keys,
+ bool tmp_table, uint select_field_count,
+ bool use_copy_create_info);
bool mysql_alter_table(THD *thd, char *new_db, char *new_name,
HA_CREATE_INFO *create_info,
@@ -1027,7 +1031,11 @@ 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);
TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem,
bool *refresh, uint flags);
-bool reopen_name_locked_table(THD* thd, TABLE_LIST* table);
+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_tables(THD *thd,bool get_locks,bool in_refresh);
bool close_data_tables(THD *thd,const char *db, const char *table_name);
@@ -1195,7 +1203,9 @@ void add_join_on(TABLE_LIST *b,Item *expr);
void add_join_natural(TABLE_LIST *a,TABLE_LIST *b,List<String> *using_fields,
SELECT_LEX *lex);
bool add_proc_to_list(THD *thd, Item *item);
-TABLE *unlink_open_table(THD *thd,TABLE *list,TABLE *find);
+void unlink_open_table(THD *thd, TABLE *find, bool unlock);
+void drop_open_table(THD *thd, TABLE *table, const char *db_name,
+ const char *table_name);
void update_non_unique_table_error(TABLE_LIST *update,
const char *operation,
TABLE_LIST *duplicate);
@@ -1630,7 +1640,7 @@ extern double log_01[32];
extern ulonglong log_10_int[20];
extern ulonglong keybuff_size;
extern ulonglong thd_startup_options;
-extern ulong refresh_version, thread_id;
+extern ulong thread_id;
extern ulong binlog_cache_use, binlog_cache_disk_use;
extern ulong aborted_threads,aborted_connects;
extern ulong delayed_insert_timeout;
@@ -1773,7 +1783,7 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count,
#define MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK 0x0001
#define MYSQL_LOCK_IGNORE_FLUSH 0x0002
#define MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN 0x0004
-#define MYSQL_OPEN_IGNORE_LOCKED_TABLES 0x0008
+#define MYSQL_OPEN_TEMPORARY_ONLY 0x0008
void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock);
void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock);
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index f26d6e5777b..3032171dc19 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -97,7 +97,7 @@ static bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias,
uint db_stat, uint prgflag,
uint ha_open_flags, TABLE *outparam,
TABLE_LIST *table_desc, MEM_ROOT *mem_root);
-static void close_old_data_files(THD *thd, TABLE *table, bool abort_locks,
+static void close_old_data_files(THD *thd, TABLE *table, bool morph_locks,
bool send_refresh);
static bool reopen_table(TABLE *table);
static bool
@@ -688,6 +688,8 @@ static void close_handle_and_leave_table_as_lock(TABLE *table)
MEM_ROOT *mem_root= &table->mem_root;
DBUG_ENTER("close_handle_and_leave_table_as_lock");
+ DBUG_ASSERT(table->db_stat);
+
/*
Make a local copy of the table share and free the current one.
This has to be done to ensure that the table share is removed from
@@ -934,8 +936,22 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh,
for (uint idx=0 ; idx < open_cache.records ; idx++)
{
TABLE *table=(TABLE*) hash_element(&open_cache,idx);
+ /*
+ 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->s->log_table &&
- ((table->s->version) < refresh_version && table->db_stat))
+ (table->needs_reopen_or_name_lock() && table->db_stat))
{
found=1;
DBUG_PRINT("signal", ("Waiting for COND_refresh"));
@@ -1249,10 +1265,10 @@ bool close_thread_table(THD *thd, TABLE **table_ptr)
TABLE *table= *table_ptr;
DBUG_ENTER("close_thread_table");
DBUG_ASSERT(table->key_read == 0);
- DBUG_ASSERT(table->file->inited == handler::NONE);
+ DBUG_ASSERT(!table->file || table->file->inited == handler::NONE);
*table_ptr=table->next;
- if (table->s->version != refresh_version ||
+ if (table->needs_reopen_or_name_lock() ||
thd->version != refresh_version || !table->db_stat)
{
VOID(hash_delete(&open_cache,(byte*) table));
@@ -1260,6 +1276,12 @@ bool close_thread_table(THD *thd, TABLE **table_ptr)
}
else
{
+ /*
+ Open placeholders have TABLE::db_stat set to 0, so they should be
+ handled by the first alternative.
+ */
+ DBUG_ASSERT(!table->open_placeholder);
+
/* Free memory and reset for next loop */
table->file->ha_reset();
table->in_use=0;
@@ -1778,18 +1800,32 @@ static void relink_unused(TABLE *table)
}
-/*
- Remove all instances of table from the current open list
- Free all locks on tables that are done with LOCK TABLES
- */
+/**
+ @brief Remove all instances of table from thread's open list and
+ table cache.
+
+ @param thd Thread context
+ @param find Table to remove
+ @param unlock TRUE - free all locks on tables removed that are
+ done with LOCK TABLES
+ FALSE - otherwise
+
+ @note When unlock parameter is FALSE or current thread doesn't have
+ any tables locked with LOCK TABLES tables are assumed to be
+ not locked (for example already unlocked).
+*/
-TABLE *unlink_open_table(THD *thd, TABLE *list, TABLE *find)
+void unlink_open_table(THD *thd, TABLE *find, bool unlock)
{
char key[MAX_DBKEY_LENGTH];
uint key_length= find->s->table_cache_key.length;
- TABLE *start=list,**prev,*next;
- prev= &start;
+ TABLE *list, **prev, *next;
+ DBUG_ENTER("unlink_open_table");
+
+ safe_mutex_assert_owner(&LOCK_open);
+ list= thd->open_tables;
+ prev= &thd->open_tables;
memcpy(key, find->s->table_cache_key.str, key_length);
for (; list ; list=next)
{
@@ -1797,7 +1833,7 @@ TABLE *unlink_open_table(THD *thd, TABLE *list, TABLE *find)
if (list->s->table_cache_key.length == key_length &&
!memcmp(list->s->table_cache_key.str, key, key_length))
{
- if (thd->locked_tables)
+ if (unlock && thd->locked_tables)
mysql_lock_remove(thd, thd->locked_tables,list);
VOID(hash_delete(&open_cache,(byte*) list)); // Close table
}
@@ -1810,7 +1846,41 @@ TABLE *unlink_open_table(THD *thd, TABLE *list, TABLE *find)
*prev=0;
// Notify any 'refresh' threads
broadcast_refresh();
- return start;
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ @brief Auxiliary routine which closes and drops open table.
+
+ @param thd Thread handle
+ @param table TABLE object for table to be dropped
+ @param db_name Name of database for this table
+ @param table_name Name of this table
+
+ @note This routine assumes that table to be closed is open only
+ by calling thread so we needn't wait until other threads
+ will close the table. It also assumes that table to be
+ dropped is already unlocked.
+*/
+
+void drop_open_table(THD *thd, TABLE *table, const char *db_name,
+ const char *table_name)
+{
+ if (table->s->tmp_table)
+ close_temporary_table(thd, table, 1, 1);
+ else
+ {
+ handlerton *table_type= table->s->db_type;
+ VOID(pthread_mutex_lock(&LOCK_open));
+ /*
+ unlink_open_table() also tells threads waiting for refresh or close
+ that something has happened.
+ */
+ unlink_open_table(thd, table, FALSE);
+ quick_rm_table(table_type, db_name, table_name, 0);
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ }
}
@@ -1867,6 +1937,11 @@ void wait_for_condition(THD *thd, pthread_mutex_t *mutex, pthread_cond_t *cond)
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.
NOTE
This function assumes that its caller already acquired LOCK_open mutex.
@@ -1876,7 +1951,7 @@ void wait_for_condition(THD *thd, pthread_mutex_t *mutex, pthread_cond_t *cond)
TRUE - Error
*/
-bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list)
+bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in)
{
TABLE *table= table_list->table;
TABLE_SHARE *share;
@@ -1907,11 +1982,32 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list)
}
share= table->s;
+ /*
+ We want to prevent other connections from opening this table until end
+ of statement as it is likely that modifications of table's metadata are
+ not yet finished (for example CREATE TRIGGER have to change .TRG file,
+ or we might want to drop table if CREATE TABLE ... SELECT fails).
+ This also allows us to assume that no other connection will sneak in
+ before we will get table-level lock on this table.
+ */
share->version=0;
table->in_use = thd;
check_unused();
- table->next = thd->open_tables;
- thd->open_tables = table;
+
+ 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->tablenr=thd->current_tablenr++;
table->used_fields=0;
table->const_table=0;
@@ -1921,6 +2017,173 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list)
}
+/**
+ @brief 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, (byte*)table))
+ {
+ my_free((gptr) table, MYF(0));
+ DBUG_RETURN(NULL);
+ }
+
+ DBUG_RETURN(table);
+}
+
+
+/**
+ @brief 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;
+ VOID(pthread_mutex_lock(&LOCK_open));
+
+ if (hash_search(&open_cache, (byte *)key, key_length))
+ {
+ VOID(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)))
+ {
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ DBUG_RETURN(TRUE);
+ }
+ (*table)->open_placeholder= 1;
+ (*table)->next= thd->open_tables;
+ thd->open_tables= *table;
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ @brief Check that table exists in table definition cache, on disk
+ or in some storage engine.
+
+ @param thd Thread context
+ @param table Table list element
+ @param exists[out] Out parameter which is set to TRUE if table
+ exists and to FALSE otherwise.
+
+ @note This function assumes that caller owns LOCK_open mutex.
+ It also assumes that the fact that there are no name-locks
+ on the table was checked beforehand.
+
+ @note If there is no .FRM file for the table but it exists in one
+ of engines (e.g. it was created on another node of NDB cluster)
+ this function will fetch and create proper .FRM file for it.
+
+ @retval TRUE Some error occured
+ @retval FALSE No error. 'exists' out parameter set accordingly.
+*/
+
+bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists)
+{
+ char path[FN_REFLEN];
+ int rc;
+ DBUG_ENTER("check_if_table_exists");
+
+ safe_mutex_assert_owner(&LOCK_open);
+
+ *exists= TRUE;
+
+ if (get_cached_table_share(table->db, table->table_name))
+ DBUG_RETURN(FALSE);
+
+ build_table_filename(path, sizeof(path) - 1, table->db, table->table_name,
+ reg_ext, 0);
+
+ if (!access(path, F_OK))
+ DBUG_RETURN(FALSE);
+
+ /* .FRM file doesn't exist. Check if some engine can provide it. */
+
+ rc= ha_create_table_from_engine(thd, table->db, table->table_name);
+
+ if (rc < 0)
+ {
+ /* Table does not exists in engines as well. */
+ *exists= FALSE;
+ DBUG_RETURN(FALSE);
+ }
+ else if (!rc)
+ {
+ /* Table exists in some engine and .FRM for it was created. */
+ DBUG_RETURN(FALSE);
+ }
+ else /* (rc > 0) */
+ {
+ my_printf_error(ER_UNKNOWN_ERROR, "Failed to open '%-.64s', error while "
+ "unpacking from engine", MYF(0), table->table_name);
+ DBUG_RETURN(TRUE);
+ }
+}
+
+
/*
Open a table.
@@ -1936,12 +2199,17 @@ bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list)
MYSQL_LOCK_IGNORE_FLUSH - Open table even if
someone has done a flush or namelock on it.
No version number checking is done.
- MYSQL_OPEN_IGNORE_LOCKED_TABLES - Open table
- ignoring set of locked tables and prelocked mode.
+ MYSQL_OPEN_TEMPORARY_ONLY - Open only temporary
+ table not the base table or view.
IMPLEMENTATION
Uses a cache of open tables to find a table not in use.
+ If table list element for the table to be opened has "create" flag
+ set and table does not exist, this function will automatically insert
+ a placeholder for exclusive name lock into the open tables cache and
+ will return the TABLE instance that corresponds to this placeholder.
+
RETURN
NULL Open failed. If refresh is set then one should close
all other tables and retry the open.
@@ -2014,6 +2282,12 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
}
}
+ if (flags & MYSQL_OPEN_TEMPORARY_ONLY)
+ {
+ my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->table_name);
+ DBUG_RETURN(0);
+ }
+
/*
The table is not temporary - if we're in pre-locked or LOCK TABLES
mode, let's try to find the requested table in the list of pre-opened
@@ -2021,8 +2295,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
open not pre-opened tables in pre-locked/LOCK TABLES mode.
TODO: move this block into a separate function.
*/
- if (!(flags & MYSQL_OPEN_IGNORE_LOCKED_TABLES) &&
- (thd->locked_tables || thd->prelocked_mode))
+ if (thd->locked_tables || thd->prelocked_mode)
{ // Using table locks
TABLE *best_table= 0;
int best_distance= INT_MIN;
@@ -2204,7 +2477,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
c1: name lock t2; -- blocks
c2: open t1; -- blocks
*/
- if (table->s->version != refresh_version && !table->s->log_table)
+ if (table->needs_reopen_or_name_lock() && !table->s->log_table)
{
DBUG_PRINT("note",
("Found table '%s.%s' with different refresh version",
@@ -2217,6 +2490,14 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
continue;
}
+ /* Avoid self-deadlocks by detecting self-dependencies. */
+ if (table->open_placeholder && table->in_use == thd)
+ {
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ my_error(ER_UPDATE_TABLE_USED, MYF(0), table->s->table_name.str);
+ DBUG_RETURN(0);
+ }
+
/*
Back off, part 1: mark the table as "unused" for the
purpose of name-locking by setting table->db_stat to 0. Do
@@ -2233,6 +2514,14 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
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.
*/
if (table->in_use != thd)
{
@@ -2273,6 +2562,40 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
while (open_cache.records > table_cache_size && unused_tables)
VOID(hash_delete(&open_cache,(byte*) unused_tables)); /* purecov: tested */
+ if (table_list->create)
+ {
+ bool exists;
+
+ if (check_if_table_exists(thd, table_list, &exists))
+ {
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ DBUG_RETURN(NULL);
+ }
+
+ if (!exists)
+ {
+ /*
+ Table to be created, so we need to create placeholder in table-cache.
+ */
+ if (!(table= table_cache_insert_placeholder(thd, key, key_length)))
+ {
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ DBUG_RETURN(NULL);
+ }
+ /*
+ 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;
+ VOID(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))))
{
@@ -2489,9 +2812,24 @@ bool close_data_tables(THD *thd,const char *db, const char *table_name)
}
-/*
- Reopen all tables with closed data files
- One should have lock on LOCK_open when calling this
+/**
+ @brief Reopen all tables with closed data files.
+
+ @param thd Thread context
+ @param get_locks Should we get locks after reopening tables ?
+ @param in_refresh Are we in FLUSH TABLES ? TODO: It seems that
+ we can remove this parameter.
+
+ @note Since this function can't properly handle prelocking and
+ create placeholders it should be used in very special
+ situations like FLUSH TABLES or ALTER TABLE. In general
+ case one should just repeat open_tables()/lock_tables()
+ combination when one needs tables to be reopened (for
+ example see open_and_lock_tables()).
+
+ @note One should have lock on LOCK_open when calling this.
+
+ @return FALSE in case of success, TRUE - otherwise.
*/
bool reopen_tables(THD *thd,bool get_locks,bool in_refresh)
@@ -2537,7 +2875,7 @@ bool reopen_tables(THD *thd,bool get_locks,bool in_refresh)
if (in_refresh)
{
table->s->version=0;
- table->locked_by_flush=0;
+ table->open_placeholder= 0;
}
}
}
@@ -2564,13 +2902,21 @@ bool reopen_tables(THD *thd,bool get_locks,bool in_refresh)
}
-/*
- Close handlers for tables in list, but leave the TABLE structure
- intact so that we can re-open these quickly
- abort_locks is set if called from flush_tables.
+/**
+ @brief Close handlers for tables in list, but leave the TABLE structure
+ intact so that we can re-open these quickly.
+
+ @param thd Thread context
+ @param table Head of the list of TABLE objects
+ @param morph_locks TRUE - remove locks which we have on tables being closed
+ but ensure that no DML or DDL will sneak in before
+ we will re-open the table (i.e. temporarily morph
+ our table-level locks into name-locks).
+ FALSE - otherwise
+ @param send_refresh Should we awake waiters even if we didn't close any tables?
*/
-void close_old_data_files(THD *thd, TABLE *table, bool abort_locks,
+void close_old_data_files(THD *thd, TABLE *table, bool morph_locks,
bool send_refresh)
{
bool found= send_refresh;
@@ -2582,19 +2928,41 @@ void close_old_data_files(THD *thd, TABLE *table, bool abort_locks,
Reopen marked for flush. But close log tables. They are flushed only
explicitly on FLUSH LOGS
*/
- if (table->s->version != refresh_version && !table->s->log_table)
+ if (table->needs_reopen_or_name_lock() && !table->s->log_table)
{
found=1;
if (table->db_stat)
{
- if (abort_locks)
+ if (morph_locks)
{
- mysql_lock_abort(thd,table, TRUE); // Close waiting threads
- mysql_lock_remove(thd, thd->locked_tables,table);
- table->locked_by_flush=1; // Will be reopened with locks
+ /*
+ Wake up threads waiting for table-level lock on this table
+ so they won't sneak in when we will temporarily remove our
+ lock on it. This will also give them a chance to close their
+ instances of this table.
+ */
+ mysql_lock_abort(thd, table, TRUE);
+ mysql_lock_remove(thd, thd->locked_tables, table);
+ /*
+ We want to protect the table from concurrent DDL operations
+ (like RENAME TABLE) until we will re-open and re-lock it.
+ */
+ table->open_placeholder= 1;
}
close_handle_and_leave_table_as_lock(table);
}
+ else if (table->open_placeholder)
+ {
+ /*
+ We come here only in close-for-back-off scenario. So we have to
+ "close" create placeholder here to avoid deadlocks (for example,
+ in case of concurrent execution of CREATE TABLE t1 SELECT * FROM t2
+ and RENAME TABLE t2 TO t1). In close-for-re-open scenario we will
+ probably want to let it stay.
+ */
+ DBUG_ASSERT(!morph_locks);
+ table->open_placeholder= 0;
+ }
}
}
if (found)
@@ -2630,10 +2998,10 @@ bool table_is_used(TABLE *table, bool wait_for_name_lock)
key_length, &state))
{
DBUG_PRINT("info", ("share: 0x%lx locked_by_logger: %d "
- "locked_by_flush: %d locked_by_name: %d "
+ "open_placeholder: %d locked_by_name: %d "
"db_stat: %u version: %lu",
(ulong) search->s, search->locked_by_logger,
- search->locked_by_flush, search->locked_by_name,
+ search->open_placeholder, search->locked_by_name,
search->db_stat,
search->s->version));
if (search->in_use == table->in_use)
@@ -2649,8 +3017,7 @@ bool table_is_used(TABLE *table, bool wait_for_name_lock)
*/
if (!search->locked_by_logger &&
(search->locked_by_name && wait_for_name_lock ||
- search->locked_by_flush ||
- (search->db_stat && search->s->version < refresh_version)))
+ (search->is_name_opened() && search->needs_reopen_or_name_lock())))
DBUG_RETURN(1);
}
} while ((table=table->next));
@@ -6637,7 +7004,7 @@ bool remove_table_from_cache(THD *thd, const char *db, const char *table_name,
{
DBUG_PRINT("info", ("Table was in use by other thread"));
in_use->some_tables_deleted=1;
- if (table->db_stat)
+ if (table->is_name_opened())
{
DBUG_PRINT("info", ("Found another active instance of the table"));
result=1;
@@ -7012,7 +7379,7 @@ has_two_write_locked_tables_with_auto_increment(TABLE_LIST *tables)
for (TABLE_LIST *table= tables; table; table= table->next_global)
{
/* we must do preliminary checks as table->table may be NULL */
- if (!table->placeholder() && !table->schema_table &&
+ if (!table->placeholder() &&
table->table->found_next_number_field &&
(table->lock_type >= TL_WRITE_ALLOW_WRITE))
{
diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc
index cd87330cedb..aaca6373f37 100644
--- a/sql/sql_handler.cc
+++ b/sql/sql_handler.cc
@@ -682,7 +682,7 @@ int mysql_ha_flush(THD *thd, TABLE_LIST *tables, uint mode_flags,
while (*table_ptr)
{
if ((mode_flags & MYSQL_HA_FLUSH_ALL) ||
- ((*table_ptr)->s->version != refresh_version))
+ (*table_ptr)->needs_reopen_or_name_lock())
{
/* The first time it is required, lock for close_thread_table(). */
if (! did_lock && ! is_locked)
@@ -783,15 +783,22 @@ void mysql_ha_mark_tables_for_reopen(THD *thd, TABLE *table)
safe_mutex_assert_owner(&LOCK_open);
for (; table; table= table->next)
{
- TABLE_LIST *hash_tables;
- if ((hash_tables= (TABLE_LIST*) hash_search(&thd->handler_tables_hash,
- (byte*) table->alias,
- strlen(table->alias) + 1)))
+ /*
+ Some elements in open table list, for example placeholders used for
+ name-locking, can have alias set to 0.
+ */
+ if (table->alias)
{
- /* Mark table as ready for reopen. */
- hash_tables->table= NULL;
- /* End open index/table scans. */
- table->file->ha_index_or_rnd_end();
+ TABLE_LIST *hash_tables;
+ if ((hash_tables= (TABLE_LIST*) hash_search(&thd->handler_tables_hash,
+ (byte*) table->alias,
+ strlen(table->alias) + 1)))
+ {
+ /* Mark table as ready for reopen. */
+ hash_tables->table= NULL;
+ /* End open index/table scans. */
+ table->file->ha_index_or_rnd_end();
+ }
}
}
DBUG_VOID_RETURN;
diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc
index c9814b3595d..ae12baae89b 100644
--- a/sql/sql_insert.cc
+++ b/sql/sql_insert.cc
@@ -2337,7 +2337,7 @@ bool Delayed_insert::handle_inserts(void)
thd.proc_info="insert";
max_rows= delayed_insert_limit;
- if (thd.killed || table->s->version != refresh_version)
+ if (thd.killed || table->needs_reopen_or_name_lock())
{
thd.killed= THD::KILL_CONNECTION;
max_rows= ULONG_MAX; // Do as much as possible
@@ -3038,8 +3038,8 @@ bool select_insert::send_eof()
***************************************************************************/
/*
- Create table from lists of fields and items (or open existing table
- with same name).
+ Create table from lists of fields and items (or just return TABLE
+ object for pre-opened existing table).
SYNOPSIS
create_table_from_items()
@@ -3054,19 +3054,25 @@ bool select_insert::send_eof()
of fields for the table (corresponding fields will
be added to the end of 'extra_fields' list)
lock out Pointer to the MYSQL_LOCK object for table created
- (open) will be returned in this parameter. Since
- this table is not included in THD::lock caller is
- responsible for explicitly unlocking this table.
+ (or open temporary table) will be returned in this
+ parameter. Since this table is not included in
+ THD::lock caller is responsible for explicitly
+ unlocking this table.
hooks
NOTES
- If 'create_info->options' bitmask has HA_LEX_CREATE_IF_NOT_EXISTS
- flag and table with name provided already exists then this function will
- simply open existing table.
- Also note that create, open and lock sequence in this function is not
- atomic and thus contains gap for deadlock and can cause other troubles.
- Since this function contains some logic specific to CREATE TABLE ... SELECT
- it should be changed before it can be used in other contexts.
+ This function behaves differently for base and temporary tables:
+ - For base table we assume that either table exists and was pre-opened
+ and locked at open_and_lock_tables() stage (and in this case we just
+ emit error or warning and return pre-opened TABLE object) or special
+ placeholder was put in table cache that guarantees that this table
+ won't be created or opened until the placeholder will be removed
+ (so there is an exclusive lock on this table).
+ - We don't pre-open existing temporary table, instead we either open
+ or create and then open table in this function.
+
+ Since this function contains some logic specific to CREATE TABLE ...
+ SELECT it should be changed before it can be used in other contexts.
RETURN VALUES
non-zero Pointer to TABLE object for table created or opened
@@ -3092,6 +3098,25 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
bool not_used;
DBUG_ENTER("create_table_from_items");
+ 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->db_stat)
+ {
+ /* Table already exists and was open at open_and_lock_tables() stage. */
+ if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
+ {
+ create_info->table_existed= 1; // Mark that table existed
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
+ ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
+ create_table->table_name);
+ DBUG_RETURN(create_table->table);
+ }
+
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), create_table->table_name);
+ DBUG_RETURN(0);
+ }
+
tmp_table.alias= 0;
tmp_table.timestamp_field= 0;
tmp_table.s= &share;
@@ -3123,8 +3148,15 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
cr_field->flags &= ~NOT_NULL_FLAG;
extra_fields->push_back(cr_field);
}
+
+ DBUG_EXECUTE_IF("sleep_create_select_before_create", my_sleep(6000000););
+
/*
- create and lock table
+ Create and lock table.
+
+ Note that we either creating (or opening existing) temporary table or
+ creating base table on which name we have exclusive lock. So code below
+ should not cause deadlocks or races.
We don't log the statement, it will be logged later.
@@ -3134,63 +3166,74 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
don't want to delete from it) 2) it would be written before the CREATE
TABLE, which is a wrong order. So we keep binary logging disabled when we
open_table().
- NOTE: By locking table which we just have created (or for which we just
- have have found that it already exists) separately from other tables used
- by the statement we create potential window for deadlock.
- TODO: create and open should be done atomic !
*/
{
tmp_disable_binlog(thd);
- if (!mysql_create_table(thd, create_table->db, create_table->table_name,
- create_info, *extra_fields, *keys, 0,
- select_field_count, 0))
+ if (!mysql_create_table_no_lock(thd, create_table->db,
+ create_table->table_name,
+ create_info, *extra_fields, *keys, 0,
+ select_field_count, 0))
{
- /*
- If we are here in prelocked mode we either create temporary table
- or prelocked mode is caused by the SELECT part of this statement.
- */
- DBUG_ASSERT(!thd->prelocked_mode ||
- create_info->options & HA_LEX_CREATE_TMP_TABLE ||
- thd->lex->requires_prelocking());
- /*
- NOTE: We don't want to ignore set of locked tables here if we are
- under explicit LOCK TABLES since it will open gap for deadlock
- too wide (and also is not backward compatible).
- */
+ if (create_info->table_existed &&
+ !(create_info->options & HA_LEX_CREATE_TMP_TABLE))
+ {
+ /*
+ This means that someone created table underneath server
+ or it was created via different mysqld front-end to the
+ cluster. We don't have much options but throw an error.
+ */
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), create_table->table_name);
+ DBUG_RETURN(0);
+ }
+
+ DBUG_EXECUTE_IF("sleep_create_select_before_open", my_sleep(6000000););
+
+ if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
+ {
+ VOID(pthread_mutex_lock(&LOCK_open));
+ if (reopen_name_locked_table(thd, create_table, FALSE))
+ {
+ quick_rm_table(create_info->db_type, create_table->db,
+ table_case_name(create_info, create_table->table_name),
+ 0);
+ }
+ else
+ table= create_table->table;
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ }
+ else
+ {
+ if (!(table= open_table(thd, create_table, thd->mem_root, (bool*) 0,
+ MYSQL_OPEN_TEMPORARY_ONLY)) &&
+ !create_info->table_existed)
+ {
+ /*
+ This shouldn't happen as creation of temporary table should make
+ it preparable for open. But let us do close_temporary_table() here
+ just in case.
+ */
+ close_temporary_table(thd, create_table);
+ }
+ }
- if (! (table= open_table(thd, create_table, thd->mem_root, (bool*) 0,
- (MYSQL_LOCK_IGNORE_FLUSH |
- ((thd->prelocked_mode == PRELOCKED) ?
- MYSQL_OPEN_IGNORE_LOCKED_TABLES:0)))))
- quick_rm_table(create_info->db_type, create_table->db,
- table_case_name(create_info, create_table->table_name),
- 0);
}
reenable_binlog(thd);
if (!table) // open failed
DBUG_RETURN(0);
}
- /*
- FIXME: What happens if trigger manages to be created while we are
- obtaining this lock ? May be it is sensible just to disable
- trigger execution in this case ? Or will MYSQL_LOCK_IGNORE_FLUSH
- save us from that ?
- */
+ DBUG_EXECUTE_IF("sleep_create_select_before_lock", my_sleep(6000000););
+
table->reginfo.lock_type=TL_WRITE;
hooks->prelock(&table, 1); // Call prelock hooks
if (! ((*lock)= mysql_lock_tables(thd, &table, 1,
MYSQL_LOCK_IGNORE_FLUSH, &not_used)))
{
- VOID(pthread_mutex_lock(&LOCK_open));
- hash_delete(&open_cache,(byte*) table);
- VOID(pthread_mutex_unlock(&LOCK_open));
- quick_rm_table(create_info->db_type, create_table->db,
- table_case_name(create_info, create_table->table_name), 0);
+ if (!create_info->table_existed)
+ drop_open_table(thd, table, create_table->db, create_table->table_name);
DBUG_RETURN(0);
}
- table->file->extra(HA_EXTRA_WRITE_CACHE);
DBUG_RETURN(table);
}
@@ -3293,6 +3336,7 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
if (check_that_all_fields_are_given_values(thd, table, table_list))
DBUG_RETURN(1);
table->mark_columns_needed_for_insert();
+ table->file->extra(HA_EXTRA_WRITE_CACHE);
DBUG_RETURN(0);
}
@@ -3395,24 +3439,19 @@ bool select_create::send_eof()
table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
- VOID(pthread_mutex_lock(&LOCK_open));
- mysql_unlock_tables(thd, thd->extra_lock);
- if (!table->s->tmp_table)
+ if (thd->extra_lock)
{
- if (close_thread_table(thd, &table))
- broadcast_refresh();
+ mysql_unlock_tables(thd, thd->extra_lock);
+ thd->extra_lock=0;
}
- thd->extra_lock=0;
- table=0;
- VOID(pthread_mutex_unlock(&LOCK_open));
}
return tmp;
}
+
void select_create::abort()
{
DBUG_ENTER("select_create::abort");
- VOID(pthread_mutex_lock(&LOCK_open));
/*
We roll back the statement, including truncating the transaction
@@ -3440,24 +3479,10 @@ void select_create::abort()
{
table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
- handlerton *table_type=table->s->db_type;
- if (!table->s->tmp_table)
- {
- ulong version= table->s->version;
- table->s->version= 0;
- hash_delete(&open_cache,(byte*) table);
- if (!create_info->table_existed)
- quick_rm_table(table_type, create_table->db,
- create_table->table_name, 0);
- /* Tell threads waiting for refresh that something has happened */
- if (version != refresh_version)
- broadcast_refresh();
- }
- else if (!create_info->table_existed)
- close_temporary_table(thd, table, 1, 1);
+ if (!create_info->table_existed)
+ drop_open_table(thd, table, create_table->db, create_table->table_name);
table=0; // Safety
}
- VOID(pthread_mutex_unlock(&LOCK_open));
DBUG_VOID_RETURN;
}
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 702deac376b..f6924e67057 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -2164,7 +2164,13 @@ mysql_execute_command(THD *thd)
select_lex->options|= SELECT_NO_UNLOCK;
unit->set_limit(select_lex);
- if (!(res= open_and_lock_tables(thd, select_tables)))
+ if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE))
+ {
+ lex->link_first_table_back(create_table, link_to_local);
+ create_table->create= TRUE;
+ }
+
+ if (!(res= open_and_lock_tables(thd, lex->query_tables)))
{
/*
Is table which we are changing used somewhere in other parts
@@ -2173,6 +2179,7 @@ mysql_execute_command(THD *thd)
if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE))
{
TABLE_LIST *duplicate;
+ create_table= lex->unlink_first_table(&link_to_local);
if ((duplicate= unique_table(thd, create_table, select_tables, 0)))
{
update_non_unique_table_error(create_table, "CREATE", duplicate);
@@ -2198,6 +2205,12 @@ mysql_execute_command(THD *thd)
}
}
+ /*
+ FIXME Temporary hack which will go away once Kostja pushes
+ his uber-fix for ALTER/CREATE TABLE.
+ */
+ lex->create_info.table_existed= 0;
+
if ((result= new select_create(create_table,
&lex->create_info,
lex->create_list,
@@ -2217,6 +2230,9 @@ mysql_execute_command(THD *thd)
lex->create_list.empty();
lex->key_list.empty();
}
+ else if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE))
+ create_table= lex->unlink_first_table(&link_to_local);
+
}
else
{
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index 194f4a9dc1d..dde118e300d 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -1492,8 +1492,21 @@ static bool mysql_test_create_table(Prepared_statement *stmt)
if (select_lex->item_list.elements)
{
+ if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE))
+ {
+ lex->link_first_table_back(create_table, link_to_local);
+ create_table->create= TRUE;
+ }
+
+ if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0))
+ DBUG_RETURN(TRUE);
+
+ if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE))
+ create_table= lex->unlink_first_table(&link_to_local);
+
select_lex->context.resolve_in_select_list= TRUE;
- res= select_like_stmt_test_with_open(stmt, tables, 0, 0);
+
+ res= select_like_stmt_test(stmt, 0, 0);
}
/* put tables back for PS rexecuting */
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
index 1047b1ec35d..6f59e326814 100644
--- a/sql/sql_table.cc
+++ b/sql/sql_table.cc
@@ -3196,7 +3196,7 @@ static HA_CREATE_INFO *copy_create_info(HA_CREATE_INFO *lex_create_info)
Create a table
SYNOPSIS
- mysql_create_table_internal()
+ mysql_create_table_no_lock()
thd Thread object
db Database
table_name Table name
@@ -3213,6 +3213,11 @@ static HA_CREATE_INFO *copy_create_info(HA_CREATE_INFO *lex_create_info)
DESCRIPTION
If one creates a temporary table, this is automatically opened
+ Note that this function assumes that caller already have taken
+ name-lock on table being created or used some other way to ensure
+ that concurrent operations won't intervene. mysql_create_table()
+ is a wrapper that can be used for this.
+
no_log is needed for the case of CREATE ... SELECT,
as the logging will be done later in sql_insert.cc
select_field_count is also used for CREATE ... SELECT,
@@ -3223,7 +3228,7 @@ static HA_CREATE_INFO *copy_create_info(HA_CREATE_INFO *lex_create_info)
TRUE error
*/
-bool mysql_create_table_internal(THD *thd,
+bool mysql_create_table_no_lock(THD *thd,
const char *db, const char *table_name,
HA_CREATE_INFO *lex_create_info,
List<create_field> &fields,
@@ -3239,7 +3244,7 @@ bool mysql_create_table_internal(THD *thd,
HA_CREATE_INFO *create_info;
handler *file;
bool error= TRUE;
- DBUG_ENTER("mysql_create_table_internal");
+ DBUG_ENTER("mysql_create_table_no_lock");
DBUG_PRINT("enter", ("db: '%s' table: '%s' tmp: %d",
db, table_name, internal_tmp_table));
@@ -3581,7 +3586,7 @@ warn:
/*
- Database locking aware wrapper for mysql_create_table_internal(),
+ Database and name-locking aware wrapper for mysql_create_table_no_lock(),
*/
bool mysql_create_table(THD *thd, const char *db, const char *table_name,
@@ -3591,6 +3596,7 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name,
uint select_field_count,
bool use_copy_create_info)
{
+ TABLE *name_lock= 0;
bool result;
DBUG_ENTER("mysql_create_table");
@@ -3611,11 +3617,44 @@ bool mysql_create_table(THD *thd, const char *db, const char *table_name,
creating_table++;
pthread_mutex_unlock(&LOCK_lock_db);
- result= mysql_create_table_internal(thd, db, table_name, create_info,
- fields, keys, internal_tmp_table,
- select_field_count,
- use_copy_create_info);
+ if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
+ {
+ if (lock_table_name_if_not_cached(thd, db, table_name, &name_lock))
+ {
+ result= TRUE;
+ goto unlock;
+ }
+ if (!name_lock)
+ {
+ if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
+ {
+ push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
+ ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
+ table_name);
+ create_info->table_existed= 1;
+ result= FALSE;
+ }
+ else
+ {
+ my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name);
+ result= TRUE;
+ }
+ goto unlock;
+ }
+ }
+
+ result= mysql_create_table_no_lock(thd, db, table_name, create_info,
+ fields, keys, internal_tmp_table,
+ select_field_count,
+ use_copy_create_info);
+unlock:
+ if (name_lock)
+ {
+ pthread_mutex_lock(&LOCK_open);
+ unlink_open_table(thd, name_lock, FALSE);
+ pthread_mutex_unlock(&LOCK_open);
+ }
pthread_mutex_lock(&LOCK_lock_db);
if (!--creating_table && creating_database)
pthread_cond_signal(&COND_refresh);
@@ -3818,7 +3857,7 @@ void close_cached_table(THD *thd, TABLE *table)
thd->lock=0; // Start locked threads
}
/* Close all copies of 'table'. This also frees all LOCK TABLES lock */
- thd->open_tables=unlink_open_table(thd,thd->open_tables,table);
+ unlink_open_table(thd, table, TRUE);
/* When lock on LOCK_open is freed other threads can continue */
broadcast_refresh();
@@ -3894,7 +3933,7 @@ 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))
+ if (reopen_name_locked_table(thd, table, TRUE))
{
unlock_table_name(thd, table);
pthread_mutex_unlock(&LOCK_open);
@@ -4026,7 +4065,7 @@ 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))
+ if (reopen_name_locked_table(thd, table_list, TRUE))
{
unlock_table_name(thd, table_list);
pthread_mutex_unlock(&LOCK_open);
@@ -4632,7 +4671,7 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table,
HA_CREATE_INFO *lex_create_info,
Table_ident *table_ident)
{
- TABLE *tmp_table;
+ TABLE *tmp_table, *name_lock= 0;
char src_path[FN_REFLEN], dst_path[FN_REFLEN];
char src_table_name_buff[FN_REFLEN], src_db_name_buff[FN_REFLEN];
uint dst_path_length;
@@ -4641,14 +4680,14 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table,
char *src_db;
char *src_table= table_ident->table.str;
int err;
- bool res= TRUE, unlock_dst_table= FALSE;
+ bool res= TRUE;
enum legacy_db_type not_used;
HA_CREATE_INFO *create_info;
#ifdef WITH_PARTITION_STORAGE_ENGINE
char tmp_path[FN_REFLEN];
#endif
char ts_name[FN_LEN];
- TABLE_LIST src_tables_list, dst_tables_list;
+ TABLE_LIST src_tables_list;
DBUG_ENTER("mysql_create_like_table");
if (!(create_info= copy_create_info(lex_create_info)))
@@ -4756,6 +4795,10 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table,
}
else
{
+ if (lock_table_name_if_not_cached(thd, db, table_name, &name_lock))
+ goto err;
+ if (!name_lock)
+ goto table_exists;
dst_path_length= build_table_filename(dst_path, sizeof(dst_path),
db, table_name, reg_ext, 0);
if (!access(dst_path, F_OK))
@@ -4843,28 +4886,21 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* table,
char buf[2048];
String query(buf, sizeof(buf), system_charset_info);
query.length(0); // Have to zero it since constructor doesn't
- uint counter;
/*
- Here we open the destination table. This is needed for
- store_create_info() to work. The table will be closed
- by close_thread_tables() at the end of the statement.
+ Here we open the destination table, on which we already have
+ name-lock. This is needed for store_create_info() to work.
+ The table will be closed by unlink_open_table() at the end
+ of this function.
*/
- if (open_tables(thd, &table, &counter, 0))
- goto err;
-
- bzero((gptr)&dst_tables_list, sizeof(dst_tables_list));
- dst_tables_list.db= table->db;
- dst_tables_list.table_name= table->table_name;
-
- /*
- lock destination table name, to make sure that nobody
- can drop/alter the table while we execute store_create_info()
- */
- if (lock_and_wait_for_table_name(thd, &dst_tables_list))
+ table->table= name_lock;
+ VOID(pthread_mutex_lock(&LOCK_open));
+ if (reopen_name_locked_table(thd, table, FALSE))
+ {
+ VOID(pthread_mutex_unlock(&LOCK_open));
goto err;
- else
- unlock_dst_table= TRUE;
+ }
+ VOID(pthread_mutex_unlock(&LOCK_open));
IF_DBUG(int result=) store_create_info(thd, table, &query,
create_info);
@@ -4899,10 +4935,10 @@ table_exists:
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name);
err:
- if (unlock_dst_table)
+ if (name_lock)
{
pthread_mutex_lock(&LOCK_open);
- unlock_table_name(thd, &dst_tables_list);
+ unlink_open_table(thd, name_lock, FALSE);
pthread_mutex_unlock(&LOCK_open);
}
DBUG_RETURN(res);
@@ -5349,7 +5385,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
uint order_num, ORDER *order, bool ignore,
ALTER_INFO *alter_info, bool do_send_ok)
{
- TABLE *table,*new_table=0;
+ TABLE *table, *new_table= 0, *name_lock= 0;
int error= 0;
char tmp_name[80],old_name[32],new_name_buff[FN_REFLEN];
char new_alias_buff[FN_REFLEN], *table_name, *db, *new_alias, *alias;
@@ -5486,6 +5522,18 @@ view_err:
DBUG_RETURN(TRUE);
table->use_all_columns();
+ List_iterator<Alter_drop> drop_it(alter_info->drop_list);
+ List_iterator<create_field> def_it(fields);
+ List_iterator<Alter_column> alter_it(alter_info->alter_list);
+ List<create_field> create_list; // Add new fields here
+ List<Key> key_list; // Add new keys here
+ List_iterator<create_field> find_it(create_list);
+ List_iterator<Key> key_it(keys);
+ List_iterator<create_field> field_it(create_list);
+ List<key_part_spec> key_parts;
+
+ KEY *key_info=table->key_info;
+
/* Check that we are not trying to rename to an existing table */
if (new_name)
{
@@ -5522,13 +5570,21 @@ view_err:
}
else
{
+ if (lock_table_name_if_not_cached(thd, new_db, new_name, &name_lock))
+ DBUG_RETURN(TRUE);
+ if (!name_lock)
+ {
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias);
+ DBUG_RETURN(TRUE);
+ }
+
build_table_filename(new_name_buff, sizeof(new_name_buff),
new_db, new_name_buff, reg_ext, 0);
if (!access(new_name_buff, F_OK))
{
/* Table will be closed in do_command() */
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias);
- DBUG_RETURN(TRUE);
+ goto err;
}
}
}
@@ -5563,12 +5619,10 @@ view_err:
#ifdef WITH_PARTITION_STORAGE_ENGINE
if (prep_alter_part_table(thd, table, alter_info, create_info, old_db_type,
&partition_changed, &fast_alter_partition))
- {
- DBUG_RETURN(TRUE);
- }
+ goto err;
#endif
if (check_engine(thd, new_name, create_info))
- DBUG_RETURN(TRUE);
+ goto err;
new_db_type= create_info->db_type;
if (create_info->row_type == ROW_TYPE_NOT_USED)
create_info->row_type= table->s->row_type;
@@ -5581,7 +5635,7 @@ view_err:
{
DBUG_PRINT("info", ("doesn't support alter"));
my_error(ER_ILLEGAL_HA, MYF(0), table_name);
- DBUG_RETURN(TRUE);
+ goto err;
}
thd->proc_info="setup";
@@ -5640,7 +5694,19 @@ view_err:
if (!error && (new_name != table_name || new_db != db))
{
thd->proc_info="rename";
- /* Then do a 'simple' rename of the table */
+ /*
+ Then do a 'simple' rename of the table. First we need to close all
+ instances of 'source' table.
+ */
+ close_cached_table(thd, table);
+ /*
+ Then, we want check once again that target table does not exist.
+ Actually the order of these two steps does not matter since
+ earlier we took name-lock on the target table, so we do them
+ in this particular order only to be consistent with 5.0, in which
+ we don't take this name-lock and where this order really matters.
+ TODO: Investigate if we need this access() check at all.
+ */
if (!access(new_name_buff,F_OK))
{
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name);
@@ -5649,8 +5715,6 @@ view_err:
else
{
*fn_ext(new_name)=0;
- table->s->version= 0; // Force removal of table def
- close_cached_table(thd, table);
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,
@@ -5682,6 +5746,8 @@ view_err:
table->file->print_error(error, MYF(0));
error= -1;
}
+ if (name_lock)
+ unlink_open_table(thd, name_lock, FALSE);
VOID(pthread_mutex_unlock(&LOCK_open));
table_list->table= NULL; // For query cache
query_cache_invalidate3(thd, table_list, 0);
@@ -5718,11 +5784,6 @@ view_err:
create_info->tablespace= tablespace;
}
restore_record(table, s->default_values); // Empty record for DEFAULT
- List_iterator<Alter_drop> drop_it(alter_info->drop_list);
- List_iterator<create_field> def_it(fields);
- List_iterator<Alter_column> alter_it(alter_info->alter_list);
- List<create_field> create_list; // Add new fields here
- List<Key> key_list; // Add new keys here
create_field *def;
/*
@@ -5793,7 +5854,7 @@ view_err:
if (def->sql_type == MYSQL_TYPE_BLOB)
{
my_error(ER_BLOB_CANT_HAVE_DEFAULT, MYF(0), def->change);
- DBUG_RETURN(TRUE);
+ goto err;
}
if ((def->def=alter->def)) // Use new default
def->flags&= ~NO_DEFAULT_VALUE_FLAG;
@@ -5804,13 +5865,12 @@ view_err:
}
}
def_it.rewind();
- List_iterator<create_field> find_it(create_list);
while ((def=def_it++)) // Add new columns
{
if (def->change && ! def->field)
{
my_error(ER_BAD_FIELD_ERROR, MYF(0), def->change, table_name);
- DBUG_RETURN(TRUE);
+ goto err;
}
if (!def->after)
create_list.push_back(def);
@@ -5828,7 +5888,7 @@ view_err:
if (!find)
{
my_error(ER_BAD_FIELD_ERROR, MYF(0), def->after, table_name);
- DBUG_RETURN(TRUE);
+ goto err;
}
find_it.after(def); // Put element after this
}
@@ -5837,13 +5897,13 @@ view_err:
{
my_error(ER_BAD_FIELD_ERROR, MYF(0),
alter_info->alter_list.head()->name, table_name);
- DBUG_RETURN(TRUE);
+ goto err;
}
if (!create_list.elements)
{
my_message(ER_CANT_REMOVE_ALL_FIELDS, ER(ER_CANT_REMOVE_ALL_FIELDS),
MYF(0));
- DBUG_RETURN(TRUE);
+ goto err;
}
/*
@@ -5851,11 +5911,6 @@ view_err:
for which some fields exists.
*/
- List_iterator<Key> key_it(keys);
- List_iterator<create_field> field_it(create_list);
- List<key_part_spec> key_parts;
-
- KEY *key_info=table->key_info;
for (uint i=0 ; i < table->s->keys ; i++,key_info++)
{
char *key_name= key_info->name;
@@ -5959,7 +6014,7 @@ view_err:
!my_strcasecmp(system_charset_info,key->name,primary_key_name))
{
my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key->name);
- DBUG_RETURN(TRUE);
+ goto err;
}
}
}
@@ -6201,6 +6256,7 @@ view_err:
#ifdef WITH_PARTITION_STORAGE_ENGINE
if (fast_alter_partition)
{
+ DBUG_ASSERT(!name_lock);
DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info,
create_info, table_list,
&create_list, &key_list,
@@ -6261,11 +6317,12 @@ view_err:
We don't log the statement, it will be logged later.
*/
tmp_disable_binlog(thd);
- error= mysql_create_table(thd, new_db, tmp_name,
- create_info,create_list,key_list,1,0,0);
+ error= mysql_create_table_no_lock(thd, new_db, tmp_name,
+ create_info, create_list,
+ key_list, 1, 0, 0);
reenable_binlog(thd);
if (error)
- DBUG_RETURN(error);
+ goto err;
/* Open the table if we need to copy the data. */
if (need_copy_table)
@@ -6526,17 +6583,6 @@ view_err:
current_pid, thd->thread_id);
if (lower_case_table_names)
my_casedn_str(files_charset_info, old_name);
- if (new_name != table_name || new_db != db)
- {
- if (!access(new_name_buff,F_OK))
- {
- error=1;
- my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name_buff);
- VOID(quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP));
- VOID(pthread_mutex_unlock(&LOCK_open));
- goto err;
- }
- }
#if !defined( __WIN__)
if (table->file->has_transactions())
@@ -6546,7 +6592,6 @@ view_err:
Win32 and InnoDB can't drop a table that is in use, so we must
close the original table before doing the rename
*/
- table->s->version= 0; // Force removal of table def
close_cached_table(thd, table);
table=0; // Marker that table is closed
no_table_reopen= TRUE;
@@ -6556,6 +6601,21 @@ view_err:
table->file->extra(HA_EXTRA_FORCE_REOPEN); // Don't use this file anymore
#endif
+ if (new_name != table_name || new_db != db)
+ {
+ /*
+ Check that there is no table with target name. See the
+ comment describing code for 'simple' ALTER TABLE ... RENAME.
+ */
+ if (!access(new_name_buff,F_OK))
+ {
+ error=1;
+ my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_name_buff);
+ VOID(quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP));
+ VOID(pthread_mutex_unlock(&LOCK_open));
+ goto err;
+ }
+ }
error=0;
save_old_db_type= old_db_type;
@@ -6606,7 +6666,6 @@ view_err:
*/
if (table)
{
- table->s->version= 0; // Force removal of table def
close_cached_table(thd,table);
}
VOID(pthread_mutex_unlock(&LOCK_open));
@@ -6647,7 +6706,6 @@ view_err:
*/
if (table)
{
- table->s->version= 0; // Force removal of table def
close_cached_table(thd,table);
}
VOID(quick_rm_table(old_db_type, db, old_name, FN_IS_TMP));
@@ -6673,7 +6731,6 @@ view_err:
{ // This shouldn't happen
if (table)
{
- table->s->version= 0; // Force removal of table def
close_cached_table(thd,table); // Remove lock for table
}
VOID(pthread_mutex_unlock(&LOCK_open));
@@ -6730,6 +6787,13 @@ view_err:
table_list->table=0; // For query cache
query_cache_invalidate3(thd, table_list, 0);
+ if (name_lock)
+ {
+ pthread_mutex_lock(&LOCK_open);
+ unlink_open_table(thd, name_lock, FALSE);
+ pthread_mutex_unlock(&LOCK_open);
+ }
+
end_temporary:
my_snprintf(tmp_name, sizeof(tmp_name), ER(ER_INSERT_INFO),
(ulong) (copied + deleted), (ulong) deleted,
@@ -6749,6 +6813,12 @@ err1:
VOID(quick_rm_table(new_db_type, new_db, tmp_name, FN_IS_TMP));
err:
+ if (name_lock)
+ {
+ pthread_mutex_lock(&LOCK_open);
+ unlink_open_table(thd, name_lock, FALSE);
+ pthread_mutex_unlock(&LOCK_open);
+ }
DBUG_RETURN(TRUE);
}
/* mysql_alter_table */
diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc
index 89ac444efa9..bdfcef23101 100644
--- a/sql/sql_trigger.cc
+++ b/sql/sql_trigger.cc
@@ -279,7 +279,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
/* We also don't allow creation of triggers on views. */
tables->required_type= FRMTYPE_TABLE;
- if (reopen_name_locked_table(thd, tables))
+ if (reopen_name_locked_table(thd, tables, TRUE))
{
unlock_table_name(thd, tables);
goto end;
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index dbebbfc0e1b..56b1013856d 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -1567,9 +1567,7 @@ create:
lex->sql_command= SQLCOM_CREATE_TABLE;
if (!lex->select_lex.add_table_to_list(thd, $5, NULL,
TL_OPTION_UPDATING,
- (using_update_log ?
- TL_READ_NO_INSERT:
- TL_READ)))
+ TL_WRITE))
MYSQL_YYABORT;
lex->create_list.empty();
lex->key_list.empty();
diff --git a/sql/table.h b/sql/table.h
index 5b6aa2a8a7c..685cb67dfbc 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -299,6 +299,8 @@ typedef struct st_table_share
} TABLE_SHARE;
+extern ulong refresh_version;
+
/* Information for one open table */
enum index_hint_type
{
@@ -314,8 +316,8 @@ struct st_table {
handler *file;
#ifdef NOT_YET
struct st_table *used_next, **used_prev; /* Link to used tables */
-#endif
struct st_table *open_next, **open_prev; /* Link to open tables */
+#endif
struct st_table *next, *prev;
THD *in_use; /* Which thread uses this */
@@ -431,7 +433,24 @@ struct st_table {
my_bool force_index;
my_bool distinct,const_table,no_rows;
my_bool key_read, no_keyread;
- my_bool locked_by_flush;
+ /*
+ Placeholder for an open table which prevents other connections
+ from taking name-locks on this table. Typically used with
+ TABLE_SHARE::version member to take an exclusive name-lock on
+ this table name -- a name lock that not only prevents other
+ threads from opening the table, but also blocks other name
+ locks. This is achieved by:
+ - setting open_placeholder to 1 - this will block other name
+ locks, as wait_for_locked_table_name will be forced to wait,
+ see table_is_used for details.
+ - setting version to 0 - this will force other threads to close
+ the instance of this table and wait (this is the same approach
+ as used for usual name locks).
+ An exclusively name-locked table currently can have no handler
+ object associated with it (db_stat is always 0), but please do
+ not rely on that.
+ */
+ my_bool open_placeholder;
my_bool locked_by_logger;
my_bool no_replicate;
my_bool locked_by_name;
@@ -492,7 +511,13 @@ struct st_table {
read_set= &def_read_set;
write_set= &def_write_set;
}
-
+ /* Is table open or should be treated as such by name-locking? */
+ inline bool is_name_opened() { return db_stat || open_placeholder; }
+ /*
+ Is this instance of the table should be reopen or represents a name-lock?
+ */
+ inline bool needs_reopen_or_name_lock()
+ { return s->version != refresh_version; }
};
enum enum_schema_table_state
@@ -888,6 +913,12 @@ typedef struct st_table_list
used for implicit LOCK TABLES only and won't be used in real statement.
*/
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).
+ */
+ bool create;
enum enum_schema_table_state schema_table_state;
void calc_md5(char *buffer);
@@ -895,7 +926,11 @@ typedef struct st_table_list
int view_check_option(THD *thd, bool ignore_failure);
bool setup_underlying(THD *thd);
void cleanup_items();
- bool placeholder() {return derived || view || schema_table || !table; }
+ bool placeholder()
+ {
+ return derived || view || schema_table || create && !table->db_stat ||
+ !table;
+ }
void print(THD *thd, String *str);
bool check_single_table(st_table_list **table, table_map map,
st_table_list *view);