Commit 16347772 authored by Kristofer Pettersson's avatar Kristofer Pettersson

Bug#39843 DELETE requires write access to table in subquery in where clause

An unnecessarily restrictive lock were taken on sub-SELECTs during DELETE.

During parsing, a global structure is reused for sub-SELECTs and the attribute
keeping track of lock options were not reset properly.
This patch introduces a new attribute to keep track on the syntactical lock
option elements found in a sub-SELECT and then sets the lock options accordingly.

Now the sub-SELECTs will try to acquire a READ lock if possible
instead of a WRITE lock as inherited from the outer DELETE statement.
parent 3213d510
......@@ -166,4 +166,31 @@ ERROR HY000: View's SELECT refers to a temporary table 't2'
Cleanup.
drop table t2, t3;
#
# Bug#39843 DELETE requires write access to table in subquery in where clause
#
DROP TABLE IF EXISTS t1,t2;
CREATE TABLE t1 (
table1_rowid SMALLINT NOT NULL
);
CREATE TABLE t2 (
table2_rowid SMALLINT NOT NULL
);
INSERT INTO t1 VALUES (1);
INSERT INTO t2 VALUES (1);
LOCK TABLES t1 WRITE, t2 READ;
# Sub-select should not try to aquire a write lock.
DELETE FROM t1
WHERE EXISTS
(
SELECT 'x'
FROM t2
WHERE t1.table1_rowid = t2.table2_rowid
) ;
# While implementing the patch we didn't break old behavior;
# The following sub-select should still requires a write lock:
SELECT * FROM t1 WHERE 1 IN (SELECT * FROM t2 FOR UPDATE);
ERROR HY000: Table 't2' was locked with a READ lock and can't be updated
UNLOCK TABLES;
DROP TABLE t1,t2;
End of 5.1 tests.
......@@ -214,4 +214,34 @@ create view v_bug5719 as select * from t2;
--echo
drop table t2, t3;
--echo #
--echo # Bug#39843 DELETE requires write access to table in subquery in where clause
--echo #
--disable_warnings
DROP TABLE IF EXISTS t1,t2;
--enable_warnings
CREATE TABLE t1 (
table1_rowid SMALLINT NOT NULL
);
CREATE TABLE t2 (
table2_rowid SMALLINT NOT NULL
);
INSERT INTO t1 VALUES (1);
INSERT INTO t2 VALUES (1);
LOCK TABLES t1 WRITE, t2 READ;
--echo # Sub-select should not try to aquire a write lock.
DELETE FROM t1
WHERE EXISTS
(
SELECT 'x'
FROM t2
WHERE t1.table1_rowid = t2.table2_rowid
) ;
--echo # While implementing the patch we didn't break old behavior;
--echo # The following sub-select should still requires a write lock:
--error ER_TABLE_NOT_LOCKED_FOR_WRITE
SELECT * FROM t1 WHERE 1 IN (SELECT * FROM t2 FOR UPDATE);
UNLOCK TABLES;
DROP TABLE t1,t2;
--echo End of 5.1 tests.
......@@ -1559,6 +1559,7 @@ void st_select_lex::init_query()
exclude_from_table_unique_test= no_wrap_view_item= FALSE;
nest_level= 0;
link_next= 0;
lock_option= TL_READ_DEFAULT;
}
void st_select_lex::init_select()
......
......@@ -688,6 +688,15 @@ public:
int cur_pos_in_select_list;
List<udf_func> udf_list; /* udf function calls stack */
/**
Per sub-query locking strategy.
Note: This variable might interfer with the corresponding statement-level
variable Lex::lock_option because on how different parser rules depend
on eachother.
*/
thr_lock_type lock_option;
/*
This is a copy of the original JOIN USING list that comes from
the parser. The parser :
......
......@@ -5580,6 +5580,14 @@ void mysql_reset_thd_for_next_command(THD *thd)
}
/**
Resets the lex->current_select object.
@note It is assumed that lex->current_select != NULL
This function is a wrapper around select_lex->init_select() with an added
check for the special situation when using INTO OUTFILE and LOAD DATA.
*/
void
mysql_init_select(LEX *lex)
{
......@@ -5594,6 +5602,18 @@ mysql_init_select(LEX *lex)
}
/**
Used to allocate a new SELECT_LEX object on the current thd mem_root and
link it into the relevant lists.
This function is always followed by mysql_init_select.
@see mysql_init_select
@retval TRUE An error occurred
@retval FALSE The new SELECT_LEX was successfully allocated.
*/
bool
mysql_new_select(LEX *lex, bool move_down)
{
......@@ -6411,7 +6431,6 @@ void st_select_lex::set_lock_for_tables(thr_lock_type lock_type)
DBUG_ENTER("set_lock_for_tables");
DBUG_PRINT("enter", ("lock_type: %d for_update: %d", lock_type,
for_update));
for (TABLE_LIST *tables= (TABLE_LIST*) table_list.first;
tables;
tables= tables->next_local)
......
......@@ -6489,7 +6489,8 @@ select_option:
{
if (check_simple_select())
MYSQL_YYABORT;
Lex->lock_option= TL_READ_HIGH_PRIORITY;
Lex->lock_option= TL_READ_HIGH_PRIORITY;
Lex->current_select->lock_option= TL_READ_HIGH_PRIORITY;
}
| DISTINCT { Select->options|= SELECT_DISTINCT; }
| SQL_SMALL_RESULT { Select->options|= SELECT_SMALL_RESULT; }
......@@ -6535,6 +6536,7 @@ select_lock_type:
{
LEX *lex=Lex;
lex->current_select->set_lock_for_tables(TL_WRITE);
lex->current_select->lock_option= TL_WRITE;
lex->safe_to_cache_query=0;
}
| LOCK_SYM IN_SYM SHARE_SYM MODE_SYM
......@@ -6542,6 +6544,7 @@ select_lock_type:
LEX *lex=Lex;
lex->current_select->
set_lock_for_tables(TL_READ_WITH_SHARED_LOCKS);
lex->current_select->lock_option= TL_READ_WITH_SHARED_LOCKS;
lex->safe_to_cache_query=0;
}
;
......@@ -12909,6 +12912,18 @@ subselect_start:
subselect_end:
{
LEX *lex=Lex;
/*
Set the required lock level for the tables associated with the
current sub-select. This will overwrite previous lock options set
using st_select_lex::add_table_to_list in any of the following
rules: single_multi, table_wild_one, load_data, table_alias_ref,
table_factor.
The default lock level is TL_READ_DEFAULT but it can be modified
with query options specific for a certain (sub-)SELECT.
*/
lex->current_select->
set_lock_for_tables(lex->current_select->lock_option);
lex->pop_context();
SELECT_LEX *child= lex->current_select;
lex->current_select = lex->current_select->return_after_parsing();
......
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