Commit 061bf717 authored by Gleb Shchepa's avatar Gleb Shchepa

Bug #42037: Queries containing a subquery with DISTINCT and

            ORDER BY could cause a server crash

Dependent subqueries like

  SELECT COUNT(*) FROM t1, t2 WHERE t2.b
   IN (SELECT DISTINCT t2.b FROM t2 WHERE t2.b = t1.a)

caused a memory leak proportional to the
number of outer rows.


The make_simple_join() function has been modified to
JOIN class method to store join_tab_reexec and
table_reexec values in the parent join only
(make_simple_join of tmp_join may access these values
via 'this' pointer of the parent JOIN).

NOTE: this patch doesn't include standard test case (this is
"out of memory" bug). See bug #42037 page for test cases.


sql/sql_select.cc:
  Bug #42037: Queries containing a subquery with DISTINCT and
              ORDER BY could cause a server crash
  
  The make_simple_join() function has been modified to
  JOIN class method to store join_tab_reexec and
  table_reexec values in the parent join only.
sql/sql_select.h:
  Bug #42037: Queries containing a subquery with DISTINCT and
              ORDER BY could cause a server crash
  
  1. The make_simple_join() function has been modified to
     JOIN class method.
  
  2. Type of JOIN::table_reexec field has been changed from
     TABLE** to TABLE *table_reexec[1]: this field always was
     NULL or a pointer to one-element array of pointers, so
     a pointer to a pointer has been replaced with one pointer
     and unnecessary memory allocation has been eliminated.
parent 31d908d7
...@@ -78,7 +78,6 @@ static store_key *get_store_key(THD *thd, ...@@ -78,7 +78,6 @@ static store_key *get_store_key(THD *thd,
KEYUSE *keyuse, table_map used_tables, KEYUSE *keyuse, table_map used_tables,
KEY_PART_INFO *key_part, char *key_buff, KEY_PART_INFO *key_part, char *key_buff,
uint maybe_null); uint maybe_null);
static bool make_simple_join(JOIN *join,TABLE *tmp_table);
static void make_outerjoin_info(JOIN *join); static void make_outerjoin_info(JOIN *join);
static bool make_join_select(JOIN *join,SQL_SELECT *select,COND *item); static bool make_join_select(JOIN *join,SQL_SELECT *select,COND *item);
static void make_join_readinfo(JOIN *join, ulonglong options); static void make_join_readinfo(JOIN *join, ulonglong options);
...@@ -1809,7 +1808,7 @@ JOIN::exec() ...@@ -1809,7 +1808,7 @@ JOIN::exec()
/* Free first data from old join */ /* Free first data from old join */
curr_join->join_free(); curr_join->join_free();
if (make_simple_join(curr_join, curr_tmp_table)) if (curr_join->make_simple_join(this, curr_tmp_table))
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
calc_group_buffer(curr_join, group_list); calc_group_buffer(curr_join, group_list);
count_field_types(select_lex, &curr_join->tmp_table_param, count_field_types(select_lex, &curr_join->tmp_table_param,
...@@ -1929,7 +1928,7 @@ JOIN::exec() ...@@ -1929,7 +1928,7 @@ JOIN::exec()
curr_join->select_distinct=0; curr_join->select_distinct=0;
} }
curr_tmp_table->reginfo.lock_type= TL_UNLOCK; curr_tmp_table->reginfo.lock_type= TL_UNLOCK;
if (make_simple_join(curr_join, curr_tmp_table)) if (curr_join->make_simple_join(this, curr_tmp_table))
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
calc_group_buffer(curr_join, curr_join->group_list); calc_group_buffer(curr_join, curr_join->group_list);
count_field_types(select_lex, &curr_join->tmp_table_param, count_field_types(select_lex, &curr_join->tmp_table_param,
...@@ -5431,48 +5430,42 @@ store_val_in_field(Field *field, Item *item, enum_check_fields check_flag) ...@@ -5431,48 +5430,42 @@ store_val_in_field(Field *field, Item *item, enum_check_fields check_flag)
} }
static bool /**
make_simple_join(JOIN *join,TABLE *tmp_table) @details Initialize a JOIN as a query execution plan
that accesses a single table via a table scan.
@param parent contains JOIN_TAB and TABLE object buffers for this join
@param tmp_table temporary table
@retval FALSE success
@retval TRUE error occurred
*/
bool
JOIN::make_simple_join(JOIN *parent, TABLE *tmp_table)
{ {
TABLE **tableptr; DBUG_ENTER("JOIN::make_simple_join");
JOIN_TAB *join_tab;
DBUG_ENTER("make_simple_join");
/* /*
Reuse TABLE * and JOIN_TAB if already allocated by a previous call Reuse TABLE * and JOIN_TAB if already allocated by a previous call
to this function through JOIN::exec (may happen for sub-queries). to this function through JOIN::exec (may happen for sub-queries).
*/ */
if (!join->table_reexec) if (!parent->join_tab_reexec &&
{ !(parent->join_tab_reexec= (JOIN_TAB*) thd->alloc(sizeof(JOIN_TAB))))
if (!(join->table_reexec= (TABLE**) join->thd->alloc(sizeof(TABLE*)))) DBUG_RETURN(TRUE); /* purecov: inspected */
DBUG_RETURN(TRUE); /* purecov: inspected */
if (join->tmp_join) join_tab= parent->join_tab_reexec;
join->tmp_join->table_reexec= join->table_reexec; table= &parent->table_reexec[0]; parent->table_reexec[0]= tmp_table;
} tables= 1;
if (!join->join_tab_reexec) const_tables= 0;
{ const_table_map= 0;
if (!(join->join_tab_reexec= tmp_table_param.field_count= tmp_table_param.sum_func_count=
(JOIN_TAB*) join->thd->alloc(sizeof(JOIN_TAB)))) tmp_table_param.func_count= 0;
DBUG_RETURN(TRUE); /* purecov: inspected */ tmp_table_param.copy_field= tmp_table_param.copy_field_end=0;
if (join->tmp_join) first_record= sort_and_group=0;
join->tmp_join->join_tab_reexec= join->join_tab_reexec; send_records= (ha_rows) 0;
} group= 0;
tableptr= join->table_reexec; row_limit= unit->select_limit_cnt;
join_tab= join->join_tab_reexec; do_send_rows= row_limit ? 1 : 0;
join->join_tab=join_tab;
join->table=tableptr; tableptr[0]=tmp_table;
join->tables=1;
join->const_tables=0;
join->const_table_map=0;
join->tmp_table_param.field_count= join->tmp_table_param.sum_func_count=
join->tmp_table_param.func_count=0;
join->tmp_table_param.copy_field=join->tmp_table_param.copy_field_end=0;
join->first_record=join->sort_and_group=0;
join->send_records=(ha_rows) 0;
join->group=0;
join->row_limit=join->unit->select_limit_cnt;
join->do_send_rows = (join->row_limit) ? 1 : 0;
join_tab->cache.buff=0; /* No caching */ join_tab->cache.buff=0; /* No caching */
join_tab->table=tmp_table; join_tab->table=tmp_table;
...@@ -5489,7 +5482,7 @@ make_simple_join(JOIN *join,TABLE *tmp_table) ...@@ -5489,7 +5482,7 @@ make_simple_join(JOIN *join,TABLE *tmp_table)
join_tab->ref.key = -1; join_tab->ref.key = -1;
join_tab->not_used_in_distinct=0; join_tab->not_used_in_distinct=0;
join_tab->read_first_record= join_init_read_record; join_tab->read_first_record= join_init_read_record;
join_tab->join=join; join_tab->join= this;
join_tab->ref.key_parts= 0; join_tab->ref.key_parts= 0;
bzero((char*) &join_tab->read_record,sizeof(join_tab->read_record)); bzero((char*) &join_tab->read_record,sizeof(join_tab->read_record));
tmp_table->status=0; tmp_table->status=0;
......
...@@ -352,9 +352,12 @@ public: ...@@ -352,9 +352,12 @@ public:
cleared only at the end of the execution of the whole query and not caching cleared only at the end of the execution of the whole query and not caching
allocations that occur in repetition at execution time will result in allocations that occur in repetition at execution time will result in
excessive memory usage. excessive memory usage.
Note: make_simple_join always creates an execution plan that accesses
a single table, thus it is sufficient to have a one-element array for
table_reexec.
*/ */
SORT_FIELD *sortorder; // make_unireg_sortorder() SORT_FIELD *sortorder; // make_unireg_sortorder()
TABLE **table_reexec; // make_simple_join() TABLE *table_reexec[1]; // make_simple_join()
JOIN_TAB *join_tab_reexec; // make_simple_join() JOIN_TAB *join_tab_reexec; // make_simple_join()
/* end of allocation caching storage */ /* end of allocation caching storage */
...@@ -384,7 +387,7 @@ public: ...@@ -384,7 +387,7 @@ public:
exec_tmp_table1= 0; exec_tmp_table1= 0;
exec_tmp_table2= 0; exec_tmp_table2= 0;
sortorder= 0; sortorder= 0;
table_reexec= 0; table_reexec[0]= 0;
join_tab_reexec= 0; join_tab_reexec= 0;
thd= thd_arg; thd= thd_arg;
sum_funcs= sum_funcs2= 0; sum_funcs= sum_funcs2= 0;
...@@ -476,6 +479,8 @@ public: ...@@ -476,6 +479,8 @@ public:
return (unit == &thd->lex->unit && (unit->fake_select_lex == 0 || return (unit == &thd->lex->unit && (unit->fake_select_lex == 0 ||
select_lex == unit->fake_select_lex)); select_lex == unit->fake_select_lex));
} }
private:
bool make_simple_join(JOIN *join, TABLE *tmp_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