summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMonty <monty@mariadb.org>2018-04-02 12:40:02 +0300
committerMonty <monty@mariadb.org>2018-04-02 12:42:48 +0300
commit7d2e283562f8c8c0ee060f4010f96de80fef6748 (patch)
tree0fd75c2826d3cf3bbc0d3f01d09671fdda345969
parent19bb7fdcd6e68aca6e41a2e30ffb3c2ad1d14cb6 (diff)
downloadmariadb-git-7d2e283562f8c8c0ee060f4010f96de80fef6748.tar.gz
Fix for MDEV-14831
MDEV-14831 CREATE OR REPLACE SEQUENCE under LOCK TABLE corrupts the sequence, causes ER_KEY_NOT_FOUND The problem was that sequence_insert didn't properly handle the case where there where there was a LOCK TABLE while creating the sequence. Fixed by opening the sequence table, for inserting the first record, in a new environment without any other open tables. Found also a bug in Locked_tables_list::reopen_tables() where the lock structure for the new tables was allocated in THD::mem_root, which causes crashes. This could cause problems with other create tables done under LOCK TABLES.
-rw-r--r--mysql-test/suite/sql_sequence/lock.result19
-rw-r--r--mysql-test/suite/sql_sequence/lock.test24
-rw-r--r--sql/lock.cc1
-rw-r--r--sql/sql_base.cc2
-rw-r--r--sql/sql_sequence.cc86
-rw-r--r--sql/sql_table.cc1
6 files changed, 104 insertions, 29 deletions
diff --git a/mysql-test/suite/sql_sequence/lock.result b/mysql-test/suite/sql_sequence/lock.result
new file mode 100644
index 00000000000..a3bde97d3a3
--- /dev/null
+++ b/mysql-test/suite/sql_sequence/lock.result
@@ -0,0 +1,19 @@
+drop table if exists s1, t1, t2;
+CREATE SEQUENCE s1;
+create table t1 (a int);
+create table t2 (a int);
+LOCK TABLE s1 WRITE, t1 write;
+create or replace sequence s1;
+select * from s1;
+next_not_cached_value minimum_value maximum_value start_value increment cache_size cycle_option cycle_count
+1 1 9223372036854775806 1 1 1000 0 0
+select * from t1;
+a
+select * from t2;
+ERROR HY000: Table 't2' was not locked with LOCK TABLES
+unlock tables;
+select * from t1;
+a
+select * from t2;
+a
+drop tables s1, t1, t2;
diff --git a/mysql-test/suite/sql_sequence/lock.test b/mysql-test/suite/sql_sequence/lock.test
new file mode 100644
index 00000000000..caa8843bb54
--- /dev/null
+++ b/mysql-test/suite/sql_sequence/lock.test
@@ -0,0 +1,24 @@
+--source include/have_sequence.inc
+--source include/have_innodb.inc
+
+--disable_warnings
+drop table if exists s1, t1, t2;
+--enable_warnings
+
+#
+# MDEV-14831 CREATE OR REPLACE SEQUENCE under LOCK TABLE corrupts the
+# sequence, causes ER_KEY_NOT_FOUND
+#
+CREATE SEQUENCE s1;
+create table t1 (a int);
+create table t2 (a int);
+LOCK TABLE s1 WRITE, t1 write;
+create or replace sequence s1;
+select * from s1;
+select * from t1;
+--error ER_TABLE_NOT_LOCKED
+select * from t2;
+unlock tables;
+select * from t1;
+select * from t2;
+drop tables s1, t1, t2;
diff --git a/sql/lock.cc b/sql/lock.cc
index 864e9f76293..2d9409fc7c3 100644
--- a/sql/lock.cc
+++ b/sql/lock.cc
@@ -747,6 +747,7 @@ static int unlock_external(THD *thd, TABLE **table,uint count)
- GET_LOCK_UNLOCK : If we should send TL_IGNORE to store lock
- GET_LOCK_STORE_LOCKS : Store lock info in TABLE
- GET_LOCK_SKIP_SEQUENCES : Ignore sequences (for temporary unlock)
+ - GET_LOCK_ON_THD : Store lock in thd->mem_root
*/
MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, uint flags)
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index 2cfac847b6f..9f7e97dc3ff 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -2443,7 +2443,7 @@ Locked_tables_list::reopen_tables(THD *thd)
break something else.
*/
lock= mysql_lock_tables(thd, m_reopen_array, reopen_count,
- MYSQL_OPEN_REOPEN);
+ MYSQL_OPEN_REOPEN | MYSQL_LOCK_USE_MALLOC);
thd->in_lock_tables= 0;
if (lock == NULL || (merged_lock=
mysql_lock_merge(thd->lock, lock)) == NULL)
diff --git a/sql/sql_sequence.cc b/sql/sql_sequence.cc
index 3ef1e668667..18f0028908f 100644
--- a/sql/sql_sequence.cc
+++ b/sql/sql_sequence.cc
@@ -275,69 +275,99 @@ bool prepare_sequence_fields(THD *thd, List<Create_field> *fields)
There is also a MDL lock on the table.
*/
-bool sequence_insert(THD *thd, LEX *lex, TABLE_LIST *table_list)
+bool sequence_insert(THD *thd, LEX *lex, TABLE_LIST *org_table_list)
{
int error;
TABLE *table;
- TABLE_LIST::enum_open_strategy save_open_strategy;
Reprepare_observer *save_reprepare_observer;
sequence_definition *seq= lex->create_info.seq_create_info;
- bool temporary_table= table_list->table != 0;
- TABLE_LIST *org_next_global= table_list->next_global;
+ bool temporary_table= org_table_list->table != 0;
+ Open_tables_backup open_tables_backup;
+ Query_tables_list query_tables_list_backup;
+ TABLE_LIST table_list; // For sequence table
DBUG_ENTER("sequence_insert");
+ /*
+ seq is 0 if sequence was created with CREATE TABLE instead of
+ CREATE SEQUENCE
+ */
+ if (!seq)
+ {
+ if (!(seq= new (thd->mem_root) sequence_definition))
+ DBUG_RETURN(TRUE);
+ }
+
/* If not temporary table */
if (!temporary_table)
{
- /* Table was locked as part of create table. Free it but keep MDL locks */
- close_thread_tables(thd);
- table_list->next_global= 0; // Close LIKE TABLE
- table_list->lock_type= TL_WRITE_DEFAULT;
- table_list->updating= 1;
+ /*
+ The following code works like open_system_tables_for_read() and
+ close_system_tables()
+ The idea is:
+ - Copy the table_list object for the sequence that was created
+ - Backup the current state of open tables and create a new
+ environment for open tables without any tables opened
+ - open the newly sequence table for write
+ This is safe as the sequence table has a mdl lock thanks to the
+ create sequence statement that is calling this function
+ */
+
+ table_list.init_one_table(&org_table_list->db,
+ &org_table_list->table_name,
+ NULL, TL_WRITE_DEFAULT);
+ table_list.updating= 1;
+ table_list.open_strategy= TABLE_LIST::OPEN_IF_EXISTS;
+ table_list.open_type= OT_BASE_ONLY;
+
+ DBUG_ASSERT(!thd->locked_tables_mode ||
+ (thd->variables.option_bits & OPTION_TABLE_LOCK));
+ lex->reset_n_backup_query_tables_list(&query_tables_list_backup);
+ thd->reset_n_backup_open_tables_state(&open_tables_backup);
+
/*
The FOR CREATE flag is needed to ensure that ha_open() doesn't try to
read the not yet existing row in the sequence table
*/
thd->open_options|= HA_OPEN_FOR_CREATE;
- save_open_strategy= table_list->open_strategy;
/*
We have to reset the reprepare observer to be able to open the
table under prepared statements.
*/
save_reprepare_observer= thd->m_reprepare_observer;
thd->m_reprepare_observer= 0;
- table_list->open_strategy= TABLE_LIST::OPEN_IF_EXISTS;
- table_list->open_type= OT_BASE_ONLY;
- error= open_and_lock_tables(thd, table_list, FALSE,
+ lex->sql_command= SQLCOM_CREATE_SEQUENCE;
+ error= open_and_lock_tables(thd, &table_list, FALSE,
MYSQL_LOCK_IGNORE_TIMEOUT |
MYSQL_OPEN_HAS_MDL_LOCK);
- table_list->open_strategy= save_open_strategy;
thd->open_options&= ~HA_OPEN_FOR_CREATE;
thd->m_reprepare_observer= save_reprepare_observer;
- table_list->next_global= org_next_global;
if (error)
- DBUG_RETURN(TRUE); /* purify inspected */
- }
-
- table= table_list->table;
-
- /*
- seq is 0 if sequence was created with CREATE TABLE instead of
- CREATE SEQUENCE
- */
- if (!seq)
- {
- if (!(seq= new (thd->mem_root) sequence_definition))
- DBUG_RETURN(TRUE); // EOM
+ {
+ lex->restore_backup_query_tables_list(&query_tables_list_backup);
+ thd->restore_backup_open_tables_state(&open_tables_backup);
+ DBUG_RETURN(error);
+ }
+ table= table_list.table;
}
+ else
+ table= org_table_list->table;
seq->reserved_until= seq->start;
error= seq->write_initial_sequence(table);
trans_commit_stmt(thd);
trans_commit_implicit(thd);
+
if (!temporary_table)
+ {
close_thread_tables(thd);
+ lex->restore_backup_query_tables_list(&query_tables_list_backup);
+ thd->restore_backup_open_tables_state(&open_tables_backup);
+
+ /* OPTION_TABLE_LOCK was reset in trans_commit_implicit */
+ if (thd->locked_tables_mode)
+ thd->variables.option_bits|= OPTION_TABLE_LOCK;
+ }
DBUG_RETURN(error);
}
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
index 0e33a8457b0..2dd45221771 100644
--- a/sql/sql_table.cc
+++ b/sql/sql_table.cc
@@ -5221,6 +5221,7 @@ bool mysql_create_table(THD *thd, TABLE_LIST *create_table,
if (thd->locked_tables_mode && pos_in_locked_tables &&
create_info->or_replace())
{
+ DBUG_ASSERT(thd->variables.option_bits & OPTION_TABLE_LOCK);
/*
Add back the deleted table and re-created table as a locked table
This should always work as we have a meta lock on the table.