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#11764371 | Norvald H. Ryeng | 17 Jan |