Commit a66a74df authored by Monty's avatar Monty Committed by Michael Widenius

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.
parent 95756d29
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;
--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;
...@@ -746,6 +746,7 @@ static int unlock_external(THD *thd, TABLE **table,uint count) ...@@ -746,6 +746,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_UNLOCK : If we should send TL_IGNORE to store lock
- GET_LOCK_STORE_LOCKS : Store lock info in TABLE - GET_LOCK_STORE_LOCKS : Store lock info in TABLE
- GET_LOCK_SKIP_SEQUENCES : Ignore sequences (for temporary unlock) - 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) MYSQL_LOCK *get_lock_data(THD *thd, TABLE **table_ptr, uint count, uint flags)
......
...@@ -2395,7 +2395,7 @@ Locked_tables_list::reopen_tables(THD *thd) ...@@ -2395,7 +2395,7 @@ Locked_tables_list::reopen_tables(THD *thd)
break something else. break something else.
*/ */
lock= mysql_lock_tables(thd, m_reopen_array, reopen_count, 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; thd->in_lock_tables= 0;
if (lock == NULL || (merged_lock= if (lock == NULL || (merged_lock=
mysql_lock_merge(thd->lock, lock)) == NULL) mysql_lock_merge(thd->lock, lock)) == NULL)
......
...@@ -274,69 +274,100 @@ bool prepare_sequence_fields(THD *thd, List<Create_field> *fields) ...@@ -274,69 +274,100 @@ bool prepare_sequence_fields(THD *thd, List<Create_field> *fields)
There is also a MDL lock on the table. 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; int error;
TABLE *table; TABLE *table;
TABLE_LIST::enum_open_strategy save_open_strategy;
Reprepare_observer *save_reprepare_observer; Reprepare_observer *save_reprepare_observer;
sequence_definition *seq= lex->create_info.seq_create_info; sequence_definition *seq= lex->create_info.seq_create_info;
bool temporary_table= table_list->table != 0; bool temporary_table= org_table_list->table != 0;
TABLE_LIST *org_next_global= table_list->next_global; Open_tables_backup open_tables_backup;
Query_tables_list query_tables_list_backup;
TABLE_LIST table_list; // For sequence table
DBUG_ENTER("sequence_insert"); 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 not temporary table */
if (!temporary_table) if (!temporary_table)
{ {
/* Table was locked as part of create table. Free it but keep MDL locks */ /*
close_thread_tables(thd); The following code works like open_system_tables_for_read() and
table_list->next_global= 0; // Close LIKE TABLE close_system_tables()
table_list->lock_type= TL_WRITE_DEFAULT; The idea is:
table_list->updating= 1; - 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->db_length,
org_table_list->table_name,
org_table_list->table_name_length,
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 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 read the not yet existing row in the sequence table
*/ */
thd->open_options|= HA_OPEN_FOR_CREATE; 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 We have to reset the reprepare observer to be able to open the
table under prepared statements. table under prepared statements.
*/ */
save_reprepare_observer= thd->m_reprepare_observer; save_reprepare_observer= thd->m_reprepare_observer;
thd->m_reprepare_observer= 0; thd->m_reprepare_observer= 0;
table_list->open_strategy= TABLE_LIST::OPEN_IF_EXISTS; lex->sql_command= SQLCOM_CREATE_SEQUENCE;
table_list->open_type= OT_BASE_ONLY; error= open_and_lock_tables(thd, &table_list, FALSE,
error= open_and_lock_tables(thd, table_list, FALSE,
MYSQL_LOCK_IGNORE_TIMEOUT | MYSQL_LOCK_IGNORE_TIMEOUT |
MYSQL_OPEN_HAS_MDL_LOCK); MYSQL_OPEN_HAS_MDL_LOCK);
table_list->open_strategy= save_open_strategy;
thd->open_options&= ~HA_OPEN_FOR_CREATE; thd->open_options&= ~HA_OPEN_FOR_CREATE;
thd->m_reprepare_observer= save_reprepare_observer; thd->m_reprepare_observer= save_reprepare_observer;
table_list->next_global= org_next_global;
if (error) if (error)
DBUG_RETURN(TRUE); /* purify inspected */ {
} lex->restore_backup_query_tables_list(&query_tables_list_backup);
thd->restore_backup_open_tables_state(&open_tables_backup);
table= table_list->table; DBUG_RETURN(error);
}
/* 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
} }
else
table= org_table_list->table;
seq->reserved_until= seq->start; seq->reserved_until= seq->start;
error= seq->write_initial_sequence(table); error= seq->write_initial_sequence(table);
trans_commit_stmt(thd); trans_commit_stmt(thd);
trans_commit_implicit(thd); trans_commit_implicit(thd);
if (!temporary_table) if (!temporary_table)
{
close_thread_tables(thd); 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); DBUG_RETURN(error);
} }
......
...@@ -4966,6 +4966,7 @@ bool mysql_create_table(THD *thd, TABLE_LIST *create_table, ...@@ -4966,6 +4966,7 @@ bool mysql_create_table(THD *thd, TABLE_LIST *create_table,
if (thd->locked_tables_mode && pos_in_locked_tables && if (thd->locked_tables_mode && pos_in_locked_tables &&
create_info->or_replace()) 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 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. This should always work as we have a meta lock on the table.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment