List:Internals« Previous MessageNext Message »
From:konstantin Date:October 11 2005 4:43pm
Subject:bk commit into 5.0 tree (konstantin:1.2032) BUG#12736
View as plain text  
Below is the list of changes that have just been committed into a local
5.0 repository of kostja. When kostja does a push these changes will
be propagated to the main repository and, within 24 hours after the
push, to the public repository.
For information on how to access the public repository
see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html

ChangeSet
  1.2032 05/10/11 20:43:13 konstantin@stripped +8 -0
  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.

  sql/sql_union.cc
    1.130 05/10/11 20:43:06 konstantin@stripped +14 -0
    - implement a helper method st_select_lex::cleanup_all_joins, which
      recursively walks over a tree of joins and calls cleanup() for
      each join.

  sql/sql_select.h
    1.100 05/10/11 20:43:06 konstantin@stripped +1 -1
    - JOIN::join_free signature changed

  sql/sql_select.cc
    1.377 05/10/11 20:43:06 konstantin@stripped +66 -12
    - remove an argument from JOIN::join_free, it's now not used
    - reimplement JOIN::join_free to not unlock tables if there
      is a subquery that has not yet been evaluated. Make sure that the
      new implementation calls ha_index_or_rnd_end for every table in
      the join and inner joins, because all table cursors must be closed
      before mysql_unlock_tables.

  sql/sql_lex.h
    1.200 05/10/11 20:43:06 konstantin@stripped +6 -1
    - declare st_select_lex::cleanup_all_joins

  sql/item_subselect.h
    1.75 05/10/11 20:43:06 konstantin@stripped +16 -0
    - implement Item_subselect::is_evaluated. This function is used
    to check whether we can clean up a non-correlated join of a subquery
    when cleaning up the join of the outer query

  sql/item_subselect.cc
    1.120 05/10/11 20:43:05 konstantin@stripped +6 -0
    - implement subselect_union_engine::is_executed

  mysql-test/t/subselect_innodb.test
    1.14 05/10/11 20:43:05 konstantin@stripped +22 -0
    - a test case for Bug#12736 "Server crash during a select": test
    that ha_index_or_rnd_end and mysql_unlock_tables are called
    for all used tables in proper order.

  mysql-test/r/subselect_innodb.result
    1.11 05/10/11 20:43:05 konstantin@stripped +20 -0
    - test results fixed (Bug#12736 "Server crash during a select")

# This is a BitKeeper patch.  What follows are the unified diffs for the
# set of deltas contained in the patch.  The rest of the patch, the part
# that BitKeeper cares about, is below these diffs.
# User:	konstantin
# Host:	dragonfly.local
# Root:	/opt/local/work/mysql-5.0-12736

--- 1.199/sql/sql_lex.h	2005-09-23 00:46:50 +04:00
+++ 1.200/sql/sql_lex.h	2005-10-11 20:43:06 +04:00
@@ -386,12 +386,12 @@
   select_result *result;
   ulong found_rows_for_union;
   bool res;
+public:
   bool  prepared, // prepare phase already performed for UNION (unit)
     optimized, // optimize phase already performed for UNION (unit)
     executed, // already executed
     cleaned;
 
-public:
   // list of fields which points to temporary table for union
   List<Item> item_list;
   /*
@@ -638,6 +638,11 @@
     SELECT_LEX and all nested SELECT_LEXes and SELECT_LEX_UNITs).
   */
   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;
 

--- 1.376/sql/sql_select.cc	2005-09-30 01:34:15 +04:00
+++ 1.377/sql/sql_select.cc	2005-10-11 20:43:06 +04:00
@@ -1377,7 +1377,7 @@
       DBUG_PRINT("info",("Creating group table"));
       
       /* Free first data from old join */
-      curr_join->join_free(0);
+      curr_join->join_free();
       if (make_simple_join(curr_join, curr_tmp_table))
 	DBUG_VOID_RETURN;
       calc_group_buffer(curr_join, group_list);
@@ -1475,7 +1475,7 @@
     if (curr_tmp_table->distinct)
       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)
     {
       thd->proc_info="Removing duplicates";
@@ -5718,34 +5718,88 @@
   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 intermidiate 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.
+
+    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.
 
-void JOIN::join_free(bool full)
+    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 *sl;
-  DBUG_ENTER("JOIN::join_free");
-
   /*
     Optimization: if not EXPLAIN and we are done with the JOIN,
     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);
 
   for (unit= select_lex->first_inner_unit(); unit; unit= unit->next_unit())
     for (sl= unit->first_select(); sl; sl= sl->next_select())
     {
-      JOIN *join= sl->join;
-      if (join)
-        join->join_free(full);
+      Item_subselect *subselect= sl->master_unit()->item;
+      bool full_local= full && (!subselect || subselect->is_evaluated());
+      /*
+        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);
+      /* Can't unlock if at least one JOIN is still needed */
+      can_unlock= can_unlock && full_local;
     }
 
   /*
     We are not using tables anymore
     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 == (thd->lex->unit.fake_select_lex ?
                       thd->lex->unit.fake_select_lex : &thd->lex->select_lex)))
@@ -6059,7 +6113,7 @@
     DBUG_RETURN(0);
   }
 
-  join->join_free(0);
+  join->join_free();
 
   if (send_row)
   {
@@ -9004,7 +9058,7 @@
 	The following will unlock all cursors if the command wasn't an
 	update command
       */
-      join->join_free(0);				// Unlock all cursors
+      join->join_free();			// Unlock all cursors
       if (join->result->send_eof())
 	rc= 1;                                  // Don't send error
     }

--- 1.99/sql/sql_select.h	2005-09-22 02:10:59 +04:00
+++ 1.100/sql/sql_select.h	2005-10-11 20:43:06 +04:00
@@ -358,7 +358,7 @@
     the end of execution in order to increase concurrency and reduce
     memory consumption.
   */
-  void join_free(bool full);
+  void join_free();
   /* Cleanup this JOIN, possibly for reuse */
   void cleanup(bool full);
   void clear();

--- 1.129/sql/sql_union.cc	2005-09-22 02:10:59 +04:00
+++ 1.130/sql/sql_union.cc	2005-10-11 20:43:06 +04:00
@@ -720,3 +720,17 @@
   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);
+}
+

--- 1.10/mysql-test/r/subselect_innodb.result	2005-02-06 14:06:08 +03:00
+++ 1.11/mysql-test/r/subselect_innodb.result	2005-10-11 20:43:05 +04:00
@@ -152,3 +152,23 @@
 b	count(*)
 deallocate prepare my_stmt;
 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;

--- 1.13/mysql-test/t/subselect_innodb.test	2005-07-28 04:21:49 +04:00
+++ 1.14/mysql-test/t/subselect_innodb.test	2005-10-11 20:43:05 +04:00
@@ -161,3 +161,25 @@
 drop table t1,t2;
 
 # 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;
+

--- 1.119/sql/item_subselect.cc	2005-09-22 02:10:57 +04:00
+++ 1.120/sql/item_subselect.cc	2005-10-11 20:43:05 +04:00
@@ -1413,6 +1413,12 @@
 }
 
 
+bool subselect_union_engine::is_executed() const
+{
+  return unit->executed;
+}
+
+
 void subselect_uniquesubquery_engine::cleanup()
 {
   DBUG_ENTER("subselect_uniquesubquery_engine::cleanup");

--- 1.74/sql/item_subselect.h	2005-09-02 17:21:07 +04:00
+++ 1.75/sql/item_subselect.h	2005-10-11 20:43:06 +04:00
@@ -110,6 +110,12 @@
     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
     mechanism. Engine call this method before rexecution query.
   */
@@ -317,6 +323,7 @@
   virtual void print(String *str)= 0;
   virtual bool change_result(Item_subselect *si, select_subselect *result)= 0;
   virtual bool no_tables()= 0;
+  virtual bool is_executed() const { return FALSE; }
 };
 
 
@@ -342,6 +349,7 @@
   void print (String *str);
   bool change_result(Item_subselect *si, select_subselect *result);
   bool no_tables();
+  bool is_executed() const { return executed; }
 };
 
 
@@ -363,6 +371,7 @@
   void print (String *str);
   bool change_result(Item_subselect *si, select_subselect *result);
   bool no_tables();
+  bool is_executed() const;
 };
 
 
@@ -411,3 +420,10 @@
   int exec();
   void print (String *str);
 };
+
+
+inline bool Item_subselect::is_evaluated() const
+{
+  return engine->is_executed();
+}
+
Thread
bk commit into 5.0 tree (konstantin:1.2032) BUG#12736konstantin11 Oct
  • bk commit into 5.0 tree (konstantin:1.2032) BUG#12736Michael Widenius13 Oct