Commit b2ff3820 authored by konstantin@mysql.com's avatar konstantin@mysql.com

A fix and a test case for Bug#12736 "Server crash during a select".

The bug was in JOIN::join_free which was wrongly determining that
all joins have been already executed and therefore all used tables
can be closed.
parent 2c7505b2
...@@ -152,3 +152,23 @@ EXECUTE my_stmt; ...@@ -152,3 +152,23 @@ EXECUTE my_stmt;
b count(*) b count(*)
deallocate prepare my_stmt; deallocate prepare my_stmt;
drop table t1,t2; drop table t1,t2;
CREATE TABLE t1 (
school_name varchar(45) NOT NULL,
country varchar(45) NOT NULL,
funds_requested float NOT NULL,
schooltype varchar(45) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into t1 values ("the school", "USA", 1200, "Human");
select count(country) as countrycount, sum(funds_requested) as smcnt,
country, (select sum(funds_requested) from t1) as total_funds
from t1
group by country;
countrycount smcnt country total_funds
1 1200 USA 1200
select count(country) as countrycount, sum(funds_requested) as smcnt,
country, (select sum(funds_requested) from t1) as total_funds
from t1
group by country;
countrycount smcnt country total_funds
1 1200 USA 1200
drop table t1;
...@@ -161,3 +161,25 @@ deallocate prepare my_stmt; ...@@ -161,3 +161,25 @@ deallocate prepare my_stmt;
drop table t1,t2; drop table t1,t2;
# End of 4.1 tests # End of 4.1 tests
CREATE TABLE t1 (
school_name varchar(45) NOT NULL,
country varchar(45) NOT NULL,
funds_requested float NOT NULL,
schooltype varchar(45) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into t1 values ("the school", "USA", 1200, "Human");
select count(country) as countrycount, sum(funds_requested) as smcnt,
country, (select sum(funds_requested) from t1) as total_funds
from t1
group by country;
select count(country) as countrycount, sum(funds_requested) as smcnt,
country, (select sum(funds_requested) from t1) as total_funds
from t1
group by country;
drop table t1;
...@@ -1413,6 +1413,12 @@ void subselect_union_engine::cleanup() ...@@ -1413,6 +1413,12 @@ void subselect_union_engine::cleanup()
} }
bool subselect_union_engine::is_executed() const
{
return unit->executed;
}
void subselect_uniquesubquery_engine::cleanup() void subselect_uniquesubquery_engine::cleanup()
{ {
DBUG_ENTER("subselect_uniquesubquery_engine::cleanup"); DBUG_ENTER("subselect_uniquesubquery_engine::cleanup");
......
...@@ -109,6 +109,12 @@ public: ...@@ -109,6 +109,12 @@ public:
engine_changed= 1; engine_changed= 1;
return eng == 0; return eng == 0;
} }
/*
True if this subquery has been already evaluated. Implemented only for
single select and union subqueries only.
*/
bool is_evaluated() const;
/* /*
Used by max/min subquery to initialize value presence registration Used by max/min subquery to initialize value presence registration
mechanism. Engine call this method before rexecution query. mechanism. Engine call this method before rexecution query.
...@@ -317,6 +323,7 @@ public: ...@@ -317,6 +323,7 @@ public:
virtual void print(String *str)= 0; virtual void print(String *str)= 0;
virtual bool change_result(Item_subselect *si, select_subselect *result)= 0; virtual bool change_result(Item_subselect *si, select_subselect *result)= 0;
virtual bool no_tables()= 0; virtual bool no_tables()= 0;
virtual bool is_executed() const { return FALSE; }
}; };
...@@ -342,6 +349,7 @@ public: ...@@ -342,6 +349,7 @@ public:
void print (String *str); void print (String *str);
bool change_result(Item_subselect *si, select_subselect *result); bool change_result(Item_subselect *si, select_subselect *result);
bool no_tables(); bool no_tables();
bool is_executed() const { return executed; }
}; };
...@@ -363,6 +371,7 @@ public: ...@@ -363,6 +371,7 @@ public:
void print (String *str); void print (String *str);
bool change_result(Item_subselect *si, select_subselect *result); bool change_result(Item_subselect *si, select_subselect *result);
bool no_tables(); bool no_tables();
bool is_executed() const;
}; };
...@@ -411,3 +420,10 @@ public: ...@@ -411,3 +420,10 @@ public:
int exec(); int exec();
void print (String *str); void print (String *str);
}; };
inline bool Item_subselect::is_evaluated() const
{
return engine->is_executed();
}
...@@ -386,12 +386,12 @@ protected: ...@@ -386,12 +386,12 @@ protected:
select_result *result; select_result *result;
ulong found_rows_for_union; ulong found_rows_for_union;
bool res; bool res;
public:
bool prepared, // prepare phase already performed for UNION (unit) bool prepared, // prepare phase already performed for UNION (unit)
optimized, // optimize phase already performed for UNION (unit) optimized, // optimize phase already performed for UNION (unit)
executed, // already executed executed, // already executed
cleaned; cleaned;
public:
// list of fields which points to temporary table for union // list of fields which points to temporary table for union
List<Item> item_list; List<Item> item_list;
/* /*
...@@ -638,6 +638,11 @@ public: ...@@ -638,6 +638,11 @@ public:
SELECT_LEX and all nested SELECT_LEXes and SELECT_LEX_UNITs). SELECT_LEX and all nested SELECT_LEXes and SELECT_LEX_UNITs).
*/ */
bool cleanup(); bool cleanup();
/*
Recursively cleanup the join of this select lex and of all nested
select lexes.
*/
void cleanup_all_joins(bool full);
}; };
typedef class st_select_lex SELECT_LEX; typedef class st_select_lex SELECT_LEX;
......
...@@ -1377,7 +1377,7 @@ JOIN::exec() ...@@ -1377,7 +1377,7 @@ JOIN::exec()
DBUG_PRINT("info",("Creating group table")); DBUG_PRINT("info",("Creating group table"));
/* Free first data from old join */ /* Free first data from old join */
curr_join->join_free(0); curr_join->join_free();
if (make_simple_join(curr_join, curr_tmp_table)) if (make_simple_join(curr_join, curr_tmp_table))
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
calc_group_buffer(curr_join, group_list); calc_group_buffer(curr_join, group_list);
...@@ -1475,7 +1475,7 @@ JOIN::exec() ...@@ -1475,7 +1475,7 @@ JOIN::exec()
if (curr_tmp_table->distinct) if (curr_tmp_table->distinct)
curr_join->select_distinct=0; /* Each row is unique */ curr_join->select_distinct=0; /* Each row is unique */
curr_join->join_free(0); /* Free quick selects */ curr_join->join_free(); /* Free quick selects */
if (curr_join->select_distinct && ! curr_join->group_list) if (curr_join->select_distinct && ! curr_join->group_list)
{ {
thd->proc_info="Removing duplicates"; thd->proc_info="Removing duplicates";
...@@ -5718,34 +5718,88 @@ void JOIN_TAB::cleanup() ...@@ -5718,34 +5718,88 @@ void JOIN_TAB::cleanup()
end_read_record(&read_record); end_read_record(&read_record);
} }
/*
Partially cleanup JOIN after it has executed: close index or rnd read
(table cursors), free quick selects.
DESCRIPTION
This function is called in the end of execution of a JOIN, before the used
tables are unlocked and closed.
For a join that is resolved using a temporary table, the first sweep is
performed against actual tables and an intermediate result is inserted
into the temprorary table.
The last sweep is performed against the temporary table. Therefore,
the base tables and associated buffers used to fill the temporary table
are no longer needed, and this function is called to free them.
void JOIN::join_free(bool full) For a join that is performed without a temporary table, this function
is called after all rows are sent, but before EOF packet is sent.
For a simple SELECT with no subqueries this function performs a full
cleanup of the JOIN and calls mysql_unlock_read_tables to free used base
tables.
If a JOIN is executed for a subquery or if it has a subquery, we can't
do the full cleanup and need to do a partial cleanup only.
o If a JOIN is not the top level join, we must not unlock the tables
because the outer select may not have been evaluated yet, and we
can't unlock only selected tables of a query.
o Additionally, if this JOIN corresponds to a correlated subquery, we
should not free quick selects and join buffers because they will be
needed for the next execution of the correlated subquery.
o However, if this is a JOIN for a [sub]select, which is not
a correlated subquery itself, but has subqueries, we can free it
fully and also free JOINs of all its subqueries. The exception
is a subquery in SELECT list, e.g:
SELECT a, (select max(b) from t1) group by c
This subquery will not be evaluated at first sweep and its value will
not be inserted into the temporary table. Instead, it's evaluated
when selecting from the temporary table. Therefore, it can't be freed
here even though it's not correlated.
*/
void JOIN::join_free()
{ {
SELECT_LEX_UNIT *unit; SELECT_LEX_UNIT *unit;
SELECT_LEX *sl; SELECT_LEX *sl;
DBUG_ENTER("JOIN::join_free");
/* /*
Optimization: if not EXPLAIN and we are done with the JOIN, Optimization: if not EXPLAIN and we are done with the JOIN,
free all tables. free all tables.
*/ */
full= full || (!select_lex->uncacheable && !thd->lex->describe); bool full= (!select_lex->uncacheable && !thd->lex->describe);
bool can_unlock= full;
DBUG_ENTER("JOIN::join_free");
cleanup(full); cleanup(full);
for (unit= select_lex->first_inner_unit(); unit; unit= unit->next_unit()) for (unit= select_lex->first_inner_unit(); unit; unit= unit->next_unit())
for (sl= unit->first_select(); sl; sl= sl->next_select()) for (sl= unit->first_select(); sl; sl= sl->next_select())
{ {
JOIN *join= sl->join; Item_subselect *subselect= sl->master_unit()->item;
if (join) bool full_local= full && (!subselect || subselect->is_evaluated());
join->join_free(full); /*
If this join is evaluated, we can fully clean it up and clean up all
its underlying joins even if they are correlated -- they will not be
used any more anyway.
If this join is not yet evaluated, we still must clean it up to
close its table cursors -- it may never get evaluated, as in case of
... HAVING FALSE OR a IN (SELECT ...))
but all table cursors must be closed before the unlock.
*/
sl->cleanup_all_joins(full_local);
/* Can't unlock if at least one JOIN is still needed */
can_unlock= can_unlock && full_local;
} }
/* /*
We are not using tables anymore We are not using tables anymore
Unlock all tables. We may be in an INSERT .... SELECT statement. Unlock all tables. We may be in an INSERT .... SELECT statement.
*/ */
if (full && lock && thd->lock && !(select_options & SELECT_NO_UNLOCK) && if (can_unlock && lock && thd->lock &&
!(select_options & SELECT_NO_UNLOCK) &&
!select_lex->subquery_in_having && !select_lex->subquery_in_having &&
(select_lex == (thd->lex->unit.fake_select_lex ? (select_lex == (thd->lex->unit.fake_select_lex ?
thd->lex->unit.fake_select_lex : &thd->lex->select_lex))) thd->lex->unit.fake_select_lex : &thd->lex->select_lex)))
...@@ -6059,7 +6113,7 @@ return_zero_rows(JOIN *join, select_result *result,TABLE_LIST *tables, ...@@ -6059,7 +6113,7 @@ return_zero_rows(JOIN *join, select_result *result,TABLE_LIST *tables,
DBUG_RETURN(0); DBUG_RETURN(0);
} }
join->join_free(0); join->join_free();
if (send_row) if (send_row)
{ {
...@@ -9004,7 +9058,7 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure) ...@@ -9004,7 +9058,7 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure)
The following will unlock all cursors if the command wasn't an The following will unlock all cursors if the command wasn't an
update command update command
*/ */
join->join_free(0); // Unlock all cursors join->join_free(); // Unlock all cursors
if (join->result->send_eof()) if (join->result->send_eof())
rc= 1; // Don't send error rc= 1; // Don't send error
} }
......
...@@ -358,7 +358,7 @@ class JOIN :public Sql_alloc ...@@ -358,7 +358,7 @@ class JOIN :public Sql_alloc
the end of execution in order to increase concurrency and reduce the end of execution in order to increase concurrency and reduce
memory consumption. memory consumption.
*/ */
void join_free(bool full); void join_free();
/* Cleanup this JOIN, possibly for reuse */ /* Cleanup this JOIN, possibly for reuse */
void cleanup(bool full); void cleanup(bool full);
void clear(); void clear();
......
...@@ -720,3 +720,17 @@ bool st_select_lex::cleanup() ...@@ -720,3 +720,17 @@ bool st_select_lex::cleanup()
DBUG_RETURN(error); DBUG_RETURN(error);
} }
void st_select_lex::cleanup_all_joins(bool full)
{
SELECT_LEX_UNIT *unit;
SELECT_LEX *sl;
if (join)
join->cleanup(full);
for (unit= first_inner_unit(); unit; unit= unit->next_unit())
for (sl= unit->first_select(); sl; sl= sl->next_select())
sl->cleanup_all_joins(full);
}
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