Skip to content

Commit

Permalink
MDEV-32397 join_read_first, keyread SEGV crash
Browse files Browse the repository at this point in the history
Queries having the following form may cause a crash
  SELECT t1.a FROM ( SELECT a AS a1 FROM t1 ) dt
  JOIN t1 ON a1 LIKE EXISTS ( SELECT a + RAND () FROM t1 UNION SELECT a FROM t1);
because the table t1 has some cleanup operations performed prematurely
during the subselect, before the outer query has finished with the table.

In this particular case, the presence of RAND() makes the subquery
uncacheable, necessitating the need to execute the subquery multiple
times during join record evaluation.  Each time the subquery runs, it
creates its own JOIN structure which has references to the table t1.
When the subquery completes, JOIN::cleanup and functions called by it
result in ha_end_keyread() being called on table t1.  However, we are
not done with table t1 because the upper level `select t1.a from...`
query requires table t1 to be open.  To solve this, we make the executor
aware of when we're in subqueries like this and delay when we call
ha_end_keyread() until the end of the parent query.
  • Loading branch information
DaveGosselin-MariaDB committed Mar 28, 2024
1 parent e1876e7 commit b68a2b9
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 2 deletions.
116 changes: 116 additions & 0 deletions mysql-test/main/subselect_like_exists.result
@@ -0,0 +1,116 @@
CREATE TABLE t0 ( c38 DOUBLE ( 87 , 18 ) ) ;
INSERT INTO t0 VALUES ( DEFAULT ) , ( DEFAULT ) ;
CREATE INDEX i0 ON t0 ( c38 ) ;
INSERT INTO t0 VALUES ( 34 ) , ( -14 ) ;
SELECT t0 . c38 AS c5 FROM ( SELECT c38 AS c27 FROM t0 ) AS t1 JOIN t0 ON t0 . c38 >= LN ( c27 IS TRUE ) LIKE EXISTS ( SELECT SPACE ( c24 ) - UNHEX ( c38 ) + STD( ORD ( -26 ) & RADIANS ( 62 ) / TRIM( LEADING 110 FROM 'a4~mq;A825}$&%(C
{!}

.!
{S&pATQJb%F}

qU@UH?VHFyU4%))Gx' ) AND -19.704435 | ACOS ( -69 IS TRUE ) ) % + RAND ( CONVERT ( -75 , UNSIGNED ) IN ( -64 , 10 , -118 ) XOR -85 = MAKE_SET ( 11 , ROUND ( -89 , -20 ) SOUNDS LIKE TRIM( TRAILING FROM 34 ) AND RAND ( ) ) ) AS c17 FROM ( SELECT t2 . c38 AS c24 FROM t0 LEFT OUTER JOIN t0 AS t2 USING ( c38 ) ) AS t3 JOIN t0 ON t3 . c24 = t0 . c38 GROUP BY c38 , c24 UNION SELECT c38 + 35 AS c48 FROM t0 WHERE c38 IN ( SELECT c38 AS c12 FROM t0 ) ) ;
c5
34.000000000000000000
34.000000000000000000
Warnings:
Warning 1292 Truncated incorrect DOUBLE value: 'a4~mq;A825}$&%(C
{!}

.!
{S&pATQJb%F}

qU@UH?VHFyU4%))Gx'
Warning 1365 Division by 0
Warning 1365 Division by 0
Warning 1365 Division by 0
Warning 1292 Truncated incorrect DOUBLE value: 'a4~mq;A825}$&%(C
{!}

.!
{S&pATQJb%F}

qU@UH?VHFyU4%))Gx'
Warning 1365 Division by 0
Note 1105 Cast to unsigned converted negative integer to it's positive complement
Warning 1292 Truncated incorrect DOUBLE value: ''
Warning 1292 Truncated incorrect DOUBLE value: 'a4~mq;A825}$&%(C
{!}

.!
{S&pATQJb%F}

qU@UH?VHFyU4%))Gx'
Warning 1365 Division by 0
Note 1105 Cast to unsigned converted negative integer to it's positive complement
Warning 1292 Truncated incorrect DOUBLE value: ' '
Warning 1292 Truncated incorrect DOUBLE value: 'a4~mq;A825}$&%(C
{!}

.!
{S&pATQJb%F}

qU@UH?VHFyU4%))Gx'
Warning 1365 Division by 0
Note 1105 Cast to unsigned converted negative integer to it's positive complement
Warning 1292 Truncated incorrect DOUBLE value: ''
Warning 1292 Truncated incorrect DOUBLE value: 'a4~mq;A825}$&%(C
{!}

.!
{S&pATQJb%F}

qU@UH?VHFyU4%))Gx'
Warning 1365 Division by 0
Note 1105 Cast to unsigned converted negative integer to it's positive complement
Warning 1292 Truncated incorrect DOUBLE value: ' '
Warning 1365 Division by 0
Warning 1365 Division by 0
Warning 1292 Truncated incorrect DOUBLE value: 'a4~mq;A825}$&%(C
{!}

.!
{S&pATQJb%F}

qU@UH?VHFyU4%))Gx'
Warning 1365 Division by 0
Note 1105 Cast to unsigned converted negative integer to it's positive complement
Warning 1292 Truncated incorrect DOUBLE value: ''
Warning 1292 Truncated incorrect DOUBLE value: 'a4~mq;A825}$&%(C
{!}

.!
{S&pATQJb%F}

qU@UH?VHFyU4%))Gx'
Warning 1365 Division by 0
Note 1105 Cast to unsigned converted negative integer to it's positive complement
Warning 1292 Truncated incorrect DOUBLE value: ' '
Warning 1292 Truncated incorrect DOUBLE value: 'a4~mq;A825}$&%(C
{!}

.!
{S&pATQJb%F}

qU@UH?VHFyU4%))Gx'
Warning 1365 Division by 0
Note 1105 Cast to unsigned converted negative integer to it's positive complement
Warning 1292 Truncated incorrect DOUBLE value: ''
Warning 1292 Truncated incorrect DOUBLE value: 'a4~mq;A825}$&%(C
{!}

.!
{S&pATQJb%F}

qU@UH?VHFyU4%))Gx'
Warning 1365 Division by 0
Note 1105 Cast to unsigned converted negative integer to it's positive complement
Warning 1292 Truncated incorrect DOUBLE value: ' '
CREATE TABLE t1 ( a double, key (a)) ;
INSERT INTO t1 VALUES (1),(2),(-3);
SELECT t1.a FROM ( SELECT a AS a1 FROM t1 ) dt
JOIN t1 ON a1 LIKE EXISTS ( SELECT a + RAND () FROM t1 UNION SELECT a FROM t1) ;
a
-3
1
2
drop table t0, t1;
23 changes: 23 additions & 0 deletions mysql-test/main/subselect_like_exists.test
@@ -0,0 +1,23 @@
#
# MDEV-32397 join_read_first, keyread SEGV crash
#

CREATE TABLE t0 ( c38 DOUBLE ( 87 , 18 ) ) ;
INSERT INTO t0 VALUES ( DEFAULT ) , ( DEFAULT ) ;
CREATE INDEX i0 ON t0 ( c38 ) ;
INSERT INTO t0 VALUES ( 34 ) , ( -14 ) ;
SELECT t0 . c38 AS c5 FROM ( SELECT c38 AS c27 FROM t0 ) AS t1 JOIN t0 ON t0 . c38 >= LN ( c27 IS TRUE ) LIKE EXISTS ( SELECT SPACE ( c24 ) - UNHEX ( c38 ) + STD( ORD ( -26 ) & RADIANS ( 62 ) / TRIM( LEADING 110 FROM 'a4~mq;A825}$&%(C
{!}

.!
{S&pATQJb%F}

qU@UH?VHFyU4%))Gx' ) AND -19.704435 | ACOS ( -69 IS TRUE ) ) % + RAND ( CONVERT ( -75 , UNSIGNED ) IN ( -64 , 10 , -118 ) XOR -85 = MAKE_SET ( 11 , ROUND ( -89 , -20 ) SOUNDS LIKE TRIM( TRAILING FROM 34 ) AND RAND ( ) ) ) AS c17 FROM ( SELECT t2 . c38 AS c24 FROM t0 LEFT OUTER JOIN t0 AS t2 USING ( c38 ) ) AS t3 JOIN t0 ON t3 . c24 = t0 . c38 GROUP BY c38 , c24 UNION SELECT c38 + 35 AS c48 FROM t0 WHERE c38 IN ( SELECT c38 AS c12 FROM t0 ) ) ;


CREATE TABLE t1 ( a double, key (a)) ;
INSERT INTO t1 VALUES (1),(2),(-3);
SELECT t1.a FROM ( SELECT a AS a1 FROM t1 ) dt
JOIN t1 ON a1 LIKE EXISTS ( SELECT a + RAND () FROM t1 UNION SELECT a FROM t1) ;

drop table t0, t1;
2 changes: 2 additions & 0 deletions sql/item_subselect.cc
Expand Up @@ -4096,7 +4096,9 @@ int subselect_single_select_engine::exec()
int subselect_union_engine::exec()
{
char const *save_where= thd->where;
unit->in_subselect= true;
int res= unit->exec();
unit->in_subselect= false;
thd->where= save_where;
return res;
}
Expand Down
27 changes: 27 additions & 0 deletions sql/sql_lex.cc
Expand Up @@ -9957,15 +9957,42 @@ void st_select_lex_unit::fix_distinct()
}


/*
If the table list of previous_sel has any tables found in the table
list of sel, then mark both previous_sel and sel such that the call
to ha_end_keyread() during JOIN cleanup may be delayed. However,
delaying the call to ha_end_keyread() only be be considered when
we're executing within the context of a subquery. The call will
be delayed until the end of the parent query and its corresponding
JOIN cleanup.
*/
static void mark_for_delayed_end_keyread(SELECT_LEX *previous_sel,
SELECT_LEX *sel)
{
if (!previous_sel)
return;
DBUG_ASSERT(previous_sel && sel);
for (auto tlm= previous_sel->table_list.first; tlm; tlm= tlm->next_local)
for (auto tlm2= sel->table_list.first; tlm2; tlm2= tlm2->next_local)
if (!cmp(tlm2->db, tlm->db) && !cmp(tlm2->table_name, tlm->table_name)) {
previous_sel->delay_keyread_end= true;
sel->delay_keyread_end= true;
}
}


void st_select_lex_unit::register_select_chain(SELECT_LEX *first_sel)
{
DBUG_ASSERT(first_sel != 0);
slave= first_sel;
first_sel->prev= &slave;
SELECT_LEX *previous_sel= nullptr;
for(SELECT_LEX *sel=first_sel; sel; sel= sel->next_select())
{
sel->master= (st_select_lex_node *)this;
uncacheable|= sel->uncacheable;
mark_for_delayed_end_keyread(previous_sel, sel);
previous_sel= sel;
}
}

Expand Down
19 changes: 19 additions & 0 deletions sql/sql_lex.h
Expand Up @@ -878,6 +878,14 @@ class st_select_lex_unit: public st_select_lex_node {
public:
bool join_union_item_types(THD *thd, List<Item> &types, uint count);
public:
/*
This value is true whenever we're withing the
subselect_union_engine::exec() method call, to
indicate that the current execution context for
this unit is a subselect.
*/
bool in_subselect{false};

// Ensures that at least all members used during cleanup() are initialized.
st_select_lex_unit()
: union_result(NULL), table(NULL), result(NULL),
Expand Down Expand Up @@ -1081,6 +1089,17 @@ Field_pair *find_matching_field_pair(Item *item, List<Field_pair> pair_list);
class st_select_lex: public st_select_lex_node
{
public:
/*
When true, don't invoke ha_end_keyread for a given table
during JOIN cleanup (but only when that cleanup happens
during a subselect), avoiding a premature reset of the
corresponding handler's keyread value. Other parts of
the query may not yet be done reading from the table
and we don't want to call ha_end_keyread until all parts
of the query have finished executing.
*/
bool delay_keyread_end{false};

/*
Currently the field first_nested is used only by parser.
It containa either a reference to the first select
Expand Down
28 changes: 26 additions & 2 deletions sql/sql_select.cc
Expand Up @@ -13803,6 +13803,28 @@ bool JOIN_TAB::build_range_rowid_filter_if_needed()
}


static bool should_invoke_end_keyread(JOIN *join)
{
DBUG_ASSERT(join);
/*
- If there's no unit associated with the join, then we
cannot be within a subselect, so it's okay to call
ha_end_keyread().
- If we have a unit but we're not within a subselect,
then it's okay to call ha_end_keyread().
- If we have a unit and we're within a subselect and
delay_keyread_end is false, then it's okay to call
ha_end_keyread().

Put another way, do not call ha_end_keyread() when
we're in a subselect and delay_keyread_end is true.
*/
return !join->unit ||
!join->unit->in_subselect ||
!join->select_lex->delay_keyread_end;
}


/**
cleanup JOIN_TAB.

Expand Down Expand Up @@ -13842,12 +13864,14 @@ void JOIN_TAB::cleanup()
if (table &&
(table->s->tmp_table != INTERNAL_TMP_TABLE || table->is_created()))
{
table->file->ha_end_keyread();
if (should_invoke_end_keyread(join))
table->file->ha_end_keyread();
table->file->ha_index_or_rnd_end();
}
if (table)
{
table->file->ha_end_keyread();
if (should_invoke_end_keyread(join))
table->file->ha_end_keyread();
if (type == JT_FT)
table->file->ha_ft_end();
else
Expand Down

0 comments on commit b68a2b9

Please sign in to comment.