mirror of
https://github.com/MariaDB/server.git
synced 2025-12-24 11:21:21 +03:00
Bug #45640: optimizer bug produces wrong results
Grouping by a subquery in a query with a distinct aggregate
function lead to a wrong result (wrong and unordered
grouping values).
There are two related problems:
1) The query like this:
SELECT (SELECT t1.a) aa, COUNT(DISTINCT b) c
FROM t1 GROUP BY aa
returned wrong result, because the outer reference "t1.a"
in the subquery was substituted with the Item_ref item.
The Item_ref item obtains data from the result_field object
that refreshes once after the end of each group. This data
is not applicable to filesort since filesort() doesn't care
about groups (and doesn't update result_field objects with
copy_fields() and so on). Also that data is not applicable
to group separation algorithm: end_send_group() checks every
record with test_if_group_changed() that evaluates Item_ref
items, but it refreshes those Item_ref-s only after the end
of group, that is a vicious circle and the grouped column
values in the output are shifted.
Fix: if
a) we grouping by a subquery and
b) that subquery has outer references to FROM list
of the grouping query,
then we substitute these outer references with
Item_direct_ref like references under aggregate
functions: Item_direct_ref obtains data directly
from the current record.
2) The query with a non-trivial grouping expression like:
SELECT (SELECT t1.a) aa, COUNT(DISTINCT b) c
FROM t1 GROUP BY aa+0
also returned wrong result, since JOIN::exec() substitutes
references to top-level aliases in SELECT list with Item_copy
caching items. Item_copy items have same refreshing policy
as Item_ref items, so the whole groping expression with
Item_copy inside returns wrong result in filesort() and
end_send_group().
Fix: include aliased items into GROUP BY item tree instead
of Item_ref references to them.
mysql-test/r/group_by.result:
Test case for bug #45640
mysql-test/t/group_by.test:
Test case for bug #45640
sql/item.cc:
Bug #45640: optimizer bug produces wrong results
Item_field::fix_fields() has been modified to resolve
aliases in GROUP BY item trees into aliased items instead
of Item_ref items.
sql/item.h:
Bug #45640: optimizer bug produces wrong results
- Item::find_item_processor() has been introduced.
- Item_ref::walk() has been modified to apply processors
to itself too (not only to referenced item).
sql/mysql_priv.h:
Bug #45640: optimizer bug produces wrong results
fix_inner_refs() has been modified to accept group_list
parameter.
sql/sql_lex.cc:
Bug #45640: optimizer bug produces wrong results
Initialization of st_select_lex::group_fix_field has
been added.
sql/sql_lex.h:
Bug #45640: optimizer bug produces wrong results
The st_select_lex::group_fix_field field has been introduced
to control alias resolution in Itef_fied::fix_fields.
sql/sql_select.cc:
Bug #45640: optimizer bug produces wrong results
- The fix_inner_refs function has been modified to treat
subquery outer references like outer fields under aggregate
functions, if they are included in GROUP BY item tree.
- The find_order_in_list function has been modified to
fix Item_field alias fields included in the GROUP BY item
trees in a special manner.
This commit is contained in:
@@ -287,6 +287,7 @@ bool handle_select(THD *thd, LEX *lex, select_result *result,
|
||||
all_fields List of all fields used in select
|
||||
select Current select
|
||||
ref_pointer_array Array of references to Items used in current select
|
||||
group_list GROUP BY list (is NULL by default)
|
||||
|
||||
DESCRIPTION
|
||||
The function serves 3 purposes - adds fields referenced from inner
|
||||
@@ -305,6 +306,8 @@ bool handle_select(THD *thd, LEX *lex, select_result *result,
|
||||
function is aggregated in the select where the outer field was
|
||||
resolved or in some more inner select then the Item_direct_ref
|
||||
class should be used.
|
||||
Also it should be used if we are grouping by a subquery containing
|
||||
the outer field.
|
||||
The resolution is done here and not at the fix_fields() stage as
|
||||
it can be done only after sum functions are fixed and pulled up to
|
||||
selects where they are have to be aggregated.
|
||||
@@ -321,7 +324,7 @@ bool handle_select(THD *thd, LEX *lex, select_result *result,
|
||||
|
||||
bool
|
||||
fix_inner_refs(THD *thd, List<Item> &all_fields, SELECT_LEX *select,
|
||||
Item **ref_pointer_array)
|
||||
Item **ref_pointer_array, ORDER *group_list)
|
||||
{
|
||||
Item_outer_ref *ref;
|
||||
bool res= FALSE;
|
||||
@@ -371,6 +374,22 @@ fix_inner_refs(THD *thd, List<Item> &all_fields, SELECT_LEX *select,
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
Check if GROUP BY item trees contain the outer ref:
|
||||
in this case we have to use Item_direct_ref instead of Item_ref.
|
||||
*/
|
||||
for (ORDER *group= group_list; group; group= group->next)
|
||||
{
|
||||
if ((*group->item)->walk(&Item::find_item_processor, TRUE,
|
||||
(uchar *) ref))
|
||||
{
|
||||
direct_ref= TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
new_ref= direct_ref ?
|
||||
new Item_direct_ref(ref->context, item_ref, ref->table_name,
|
||||
ref->field_name, ref->alias_name_used) :
|
||||
@@ -577,7 +596,8 @@ JOIN::prepare(Item ***rref_pointer_array,
|
||||
}
|
||||
|
||||
if (select_lex->inner_refs_list.elements &&
|
||||
fix_inner_refs(thd, all_fields, select_lex, ref_pointer_array))
|
||||
fix_inner_refs(thd, all_fields, select_lex, ref_pointer_array,
|
||||
group_list))
|
||||
DBUG_RETURN(-1);
|
||||
|
||||
if (group_list)
|
||||
@@ -14547,11 +14567,29 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
|
||||
|
||||
We check order_item->fixed because Item_func_group_concat can put
|
||||
arguments for which fix_fields already was called.
|
||||
|
||||
group_fix_field= TRUE is to resolve aliases from the SELECT list
|
||||
without creating of Item_ref-s: JOIN::exec() wraps aliased items
|
||||
in SELECT list with Item_copy items. To re-evaluate such a tree
|
||||
that includes Item_copy items we have to refresh Item_copy caches,
|
||||
but:
|
||||
- filesort() never refresh Item_copy items,
|
||||
- end_send_group() checks every record for group boundary by the
|
||||
test_if_group_changed function that obtain data from these
|
||||
Item_copy items, but the copy_fields function that
|
||||
refreshes Item copy items is called after group boundaries only -
|
||||
that is a vicious circle.
|
||||
So we prevent inclusion of Item_copy items.
|
||||
*/
|
||||
if (!order_item->fixed &&
|
||||
bool save_group_fix_field= thd->lex->current_select->group_fix_field;
|
||||
if (is_group_field)
|
||||
thd->lex->current_select->group_fix_field= TRUE;
|
||||
bool ret= (!order_item->fixed &&
|
||||
(order_item->fix_fields(thd, order->item) ||
|
||||
(order_item= *order->item)->check_cols(1) ||
|
||||
thd->is_fatal_error))
|
||||
thd->is_fatal_error));
|
||||
thd->lex->current_select->group_fix_field= save_group_fix_field;
|
||||
if (ret)
|
||||
return TRUE; /* Wrong field. */
|
||||
|
||||
uint el= all_fields.elements;
|
||||
|
||||
Reference in New Issue
Block a user