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.
This commit is contained in:
parent
19bb7fdcd6
commit
7d2e283562
19
mysql-test/suite/sql_sequence/lock.result
Normal file
19
mysql-test/suite/sql_sequence/lock.result
Normal file
@ -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;
|
24
mysql-test/suite/sql_sequence/lock.test
Normal file
24
mysql-test/suite/sql_sequence/lock.test
Normal file
@ -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;
|
@ -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_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)
|
||||||
|
@ -2443,7 +2443,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)
|
||||||
|
@ -275,52 +275,18 @@ 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");
|
||||||
|
|
||||||
/* 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 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,
|
|
||||||
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
|
seq is 0 if sequence was created with CREATE TABLE instead of
|
||||||
CREATE SEQUENCE
|
CREATE SEQUENCE
|
||||||
@ -328,16 +294,80 @@ bool sequence_insert(THD *thd, LEX *lex, TABLE_LIST *table_list)
|
|||||||
if (!seq)
|
if (!seq)
|
||||||
{
|
{
|
||||||
if (!(seq= new (thd->mem_root) sequence_definition))
|
if (!(seq= new (thd->mem_root) sequence_definition))
|
||||||
DBUG_RETURN(TRUE); // EOM
|
DBUG_RETURN(TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If not temporary table */
|
||||||
|
if (!temporary_table)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
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;
|
||||||
|
/*
|
||||||
|
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;
|
||||||
|
lex->sql_command= SQLCOM_CREATE_SEQUENCE;
|
||||||
|
error= open_and_lock_tables(thd, &table_list, FALSE,
|
||||||
|
MYSQL_LOCK_IGNORE_TIMEOUT |
|
||||||
|
MYSQL_OPEN_HAS_MDL_LOCK);
|
||||||
|
thd->open_options&= ~HA_OPEN_FOR_CREATE;
|
||||||
|
thd->m_reprepare_observer= save_reprepare_observer;
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
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;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5221,6 +5221,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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user