diff options
author | Monty <monty@mariadb.org> | 2018-04-02 12:40:02 +0300 |
---|---|---|
committer | Monty <monty@mariadb.org> | 2018-04-02 12:42:48 +0300 |
commit | 7d2e283562f8c8c0ee060f4010f96de80fef6748 (patch) | |
tree | 0fd75c2826d3cf3bbc0d3f01d09671fdda345969 /sql | |
parent | 19bb7fdcd6e68aca6e41a2e30ffb3c2ad1d14cb6 (diff) | |
download | mariadb-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.
Diffstat (limited to 'sql')
-rw-r--r-- | sql/lock.cc | 1 | ||||
-rw-r--r-- | sql/sql_base.cc | 2 | ||||
-rw-r--r-- | sql/sql_sequence.cc | 86 | ||||
-rw-r--r-- | sql/sql_table.cc | 1 |
4 files changed, 61 insertions, 29 deletions
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. |