List:Commits« Previous MessageNext Message »
From:Norvald H. Ryeng Date:January 17 2012 2:45pm
Subject:bzr push into mysql-trunk branch (norvald.ryeng:3740 to 3741) Bug#11764371
View as plain text  
 3741 Norvald H. Ryeng	2012-01-17
      Bug#11764371 57196: MORE FUN WITH ASSERTION: !TABLE->FILE ||
      TABLE->FILE->INITED == HANDLER::
      
      Problem: An assertion is triggered by queries that assign the result
      of a subquery to a user variable when the outer query is doing
      DISTINCT and GROUP BY.
      
      Because of the DISTINCT and GROUP BY, the query is executed using two
      temporary tables. The first temporary table is filled by executing the
      query over the base tables. The subquery is also executed, and the
      result is stored in the user variable. After this execution,
      join_free() is called to free data from the old join and release all
      read locks since the next execution is based on the temporary table.
      
      The second round of execution is over the first temporary table, but
      because of the user variable assignment, the subquery is re-executed
      over the base tables. Since these tables are now unlocked, an
      assertion guarding handler::ha_rnd_next() against reading from
      unlocked tables is triggered.
      
      Fix: In change_to_use_tmp_field(), if the item is an
      Item_func_set_user_var that stores the value of a subquery, replace it
      with a cloned Item_func_set_user_var that stores the result of an
      Item_field that contains the result of the subquery. This makes the
      next execution round use the stored value instead of re-executing the
      subquery.
      
      This patch also fixes bug #13260504. No additional testcase required.
     @ mysql-test/r/user_var.result
        Test case for bug #11764371.
     @ mysql-test/t/user_var.test
        Test case for bug #11764371.
     @ sql/item_func.h
        Add constructors.
     @ sql/sql_executor.cc
        Change user variable assignment functions to read from fields after
        tables have been unlocked.

    modified:
      mysql-test/r/user_var.result
      mysql-test/t/user_var.test
      sql/item_func.h
      sql/sql_executor.cc
 3740 Vasil Dimov	2012-01-17
      Adjust mtr .result files after
      vasil.dimov@stripped

    modified:
      mysql-test/suite/funcs_1/r/is_columns_mysql.result
      mysql-test/suite/funcs_1/r/is_columns_mysql_embedded.result
      mysql-test/suite/funcs_1/r/is_tables_mysql.result
      mysql-test/suite/funcs_1/r/is_tables_mysql_embedded.result
=== modified file 'mysql-test/r/user_var.result'
--- a/mysql-test/r/user_var.result	2011-10-27 08:08:46 +0000
+++ b/mysql-test/r/user_var.result	2012-01-17 14:44:49 +0000
@@ -493,3 +493,26 @@ GROUP BY @b:=(SELECT COUNT(*) > t2.a);
 @a:=MIN(t1.a)
 1
 DROP TABLE t1;
+CREATE TABLE t1(a INT) ENGINE=InnoDB;
+INSERT INTO t1 VALUES (0);
+SELECT DISTINCT POW(COUNT(*), @a:=(SELECT 1 FROM t1 LEFT JOIN t1 AS t2 ON @a))
+AS b FROM t1 GROUP BY a;
+b
+1
+SELECT @a;
+@a
+1
+DROP TABLE t1;
+CREATE TABLE t1(f1 INT, f2 INT);
+INSERT INTO t1 VALUES (1,2),(2,3),(3,1);
+CREATE TABLE t2(a INT);
+INSERT INTO t2 VALUES (1);
+SET @var=NULL;
+SELECT @var:=(SELECT f2 FROM t2 WHERE @var) FROM t1 GROUP BY f1 ORDER BY f2 DESC
+LIMIT 1;
+@var:=(SELECT f2 FROM t2 WHERE @var)
+NULL
+SELECT @var;
+@var
+NULL
+DROP TABLE t1, t2;

=== modified file 'mysql-test/t/user_var.test'
--- a/mysql-test/t/user_var.test	2011-10-27 08:08:46 +0000
+++ b/mysql-test/t/user_var.test	2012-01-17 14:44:49 +0000
@@ -415,3 +415,24 @@ INSERT INTO t1 VALUES (1), (2);
 SELECT DISTINCT @a:=MIN(t1.a) FROM t1, t1 AS t2
 GROUP BY @b:=(SELECT COUNT(*) > t2.a);
 DROP TABLE t1;
+
+#
+# Bug #11764371 57196: MORE FUN WITH ASSERTION: !TABLE->FILE ||
+# TABLE->FILE->INITED == HANDLER::
+#
+
+CREATE TABLE t1(a INT) ENGINE=InnoDB;
+INSERT INTO t1 VALUES (0);
+SELECT DISTINCT POW(COUNT(*), @a:=(SELECT 1 FROM t1 LEFT JOIN t1 AS t2 ON @a))
+AS b FROM t1 GROUP BY a;
+SELECT @a;
+DROP TABLE t1;
+CREATE TABLE t1(f1 INT, f2 INT);
+INSERT INTO t1 VALUES (1,2),(2,3),(3,1);
+CREATE TABLE t2(a INT);
+INSERT INTO t2 VALUES (1);
+SET @var=NULL;
+SELECT @var:=(SELECT f2 FROM t2 WHERE @var) FROM t1 GROUP BY f1 ORDER BY f2 DESC
+LIMIT 1;
+SELECT @var;
+DROP TABLE t1, t2;

=== modified file 'sql/item_func.h'
--- a/sql/item_func.h	2011-11-17 13:41:28 +0000
+++ b/sql/item_func.h	2012-01-17 14:44:49 +0000
@@ -1555,6 +1555,7 @@ class Item_var_func :public Item_func
 {
 public:
   Item_var_func() :Item_func() { }
+  Item_var_func(THD *thd, Item_var_func *item) :Item_func(thd, item) { }
   Item_var_func(Item *a) :Item_func(a) { }
   bool get_date(MYSQL_TIME *ltime, uint fuzzydate)
   {
@@ -1604,6 +1605,12 @@ public:
     :Item_var_func(b), cached_result_type(INT_RESULT),
      entry(NULL), entry_thread_id(0), name(a)
   {}
+  Item_func_set_user_var(THD *thd, Item_func_set_user_var *item)
+    :Item_var_func(thd, item), cached_result_type(item->cached_result_type),
+     entry(item->entry), entry_thread_id(item->entry_thread_id),
+     value(item->value), decimal_buff(item->decimal_buff),
+     null_item(item->null_item), save_result(item->save_result), name(item->name)
+  {}
   enum Functype functype() const { return SUSERVAR_FUNC; }
   double val_real();
   longlong val_int();

=== modified file 'sql/sql_executor.cc'
--- a/sql/sql_executor.cc	2012-01-13 09:33:13 +0000
+++ b/sql/sql_executor.cc	2012-01-17 14:44:49 +0000
@@ -4670,64 +4670,88 @@ change_to_use_tmp_fields(THD *thd, Ref_p
   res_selected_fields.empty();
   res_all_fields.empty();
 
-  uint i, border= all_fields.elements - elements;
-  for (i= 0; (item= it++); i++)
+  uint border= all_fields.elements - elements;
+  for (uint i= 0; (item= it++); i++)
   {
     Field *field;
-
-    if ((item->with_sum_func && item->type() != Item::SUM_FUNC_ITEM) ||
-        (item->type() == Item::FUNC_ITEM &&
-         ((Item_func*)item)->functype() == Item_func::SUSERVAR_FUNC))
+    if (item->with_sum_func && item->type() != Item::SUM_FUNC_ITEM)
       item_field= item;
-    else
+    else if (item->type() == Item::FIELD_ITEM)
+      item_field= item->get_tmp_table_item(thd);
+    else if (item->type() == Item::FUNC_ITEM &&
+             ((Item_func*)item)->functype() == Item_func::SUSERVAR_FUNC)
     {
-      if (item->type() == Item::FIELD_ITEM)
+      field= item->get_tmp_table_field();
+      if (field != NULL)
       {
-	item_field= item->get_tmp_table_item(thd);
+        /*
+          Replace "@:=<expression>" with "@:=<tmp table column>". Otherwise, we
+          would re-evaluate <expression>, and if expression were a subquery, this
+          would access already-unlocked tables.
+        */
+        Item_func_set_user_var* suv=
+          new Item_func_set_user_var(thd, (Item_func_set_user_var*) item);
+        Item_field *new_field= new Item_field(field);
+        if (!suv || !new_field)
+          DBUG_RETURN(true);                  // Fatal error
+        /*
+          We are replacing the argument of Item_func_set_user_var after its value
+          has been read. The argument's null_value should be set by now, so we
+          must set it explicitly for the replacement argument since the
+          null_value may be read without any preceeding call to val_*().
+        */
+        new_field->update_null_value();
+        List<Item> list;
+        list.push_back(new_field);
+        suv->set_arguments(list);
+        item_field= suv;
       }
-      else if ((field= item->get_tmp_table_field()))
+      else
+        item_field= item;
+    }
+    else if ((field= item->get_tmp_table_field()))
+    {
+      if (item->type() == Item::SUM_FUNC_ITEM && field->table->group)
+        item_field= ((Item_sum*) item)->result_item(field);
+      else
+        item_field= (Item*) new Item_field(field);
+      if (!item_field)
+        DBUG_RETURN(true);                    // Fatal error
+
+      if (item->real_item()->type() != Item::FIELD_ITEM)
+        field->orig_table= 0;
+      item_field->name= item->name;
+      if (item->type() == Item::REF_ITEM)
       {
-	if (item->type() == Item::SUM_FUNC_ITEM && field->table->group)
-	  item_field= ((Item_sum*) item)->result_item(field);
-	else
-	  item_field= (Item*) new Item_field(field);
-	if (!item_field)
-	  DBUG_RETURN(TRUE);                    // Fatal error
-
-        if (item->real_item()->type() != Item::FIELD_ITEM)
-          field->orig_table= 0;
-	item_field->name= item->name;
-        if (item->type() == Item::REF_ITEM)
-        {
-          Item_field *ifield= (Item_field *) item_field;
-          Item_ref *iref= (Item_ref *) item;
-          ifield->table_name= iref->table_name;
-          ifield->db_name= iref->db_name;
-        }
+        Item_field *ifield= (Item_field *) item_field;
+        Item_ref *iref= (Item_ref *) item;
+        ifield->table_name= iref->table_name;
+        ifield->db_name= iref->db_name;
+      }
 #ifndef DBUG_OFF
-	if (!item_field->name)
-	{
-	  char buff[256];
-	  String str(buff,sizeof(buff),&my_charset_bin);
-	  str.length(0);
-	  item->print(&str, QT_ORDINARY);
-	  item_field->name= sql_strmake(str.ptr(),str.length());
-	}
-#endif
+      if (!item_field->name)
+      {
+        char buff[256];
+        String str(buff,sizeof(buff),&my_charset_bin);
+        str.length(0);
+        item->print(&str, QT_ORDINARY);
+        item_field->name= sql_strmake(str.ptr(),str.length());
       }
-      else
-	item_field= item;
+#endif
     }
+    else
+      item_field= item;
+
     res_all_fields.push_back(item_field);
     ref_pointer_array[((i < border)? all_fields.elements-i-1 : i-border)]=
       item_field;
   }
 
   List_iterator_fast<Item> itr(res_all_fields);
-  for (i= 0; i < border; i++)
+  for (uint i= 0; i < border; i++)
     itr++;
   itr.sublist(res_selected_fields, elements);
-  DBUG_RETURN(FALSE);
+  DBUG_RETURN(false);
 }
 
 

No bundle (reason: useless for push emails).
Thread
bzr push into mysql-trunk branch (norvald.ryeng:3740 to 3741) Bug#11764371Norvald H. Ryeng17 Jan