Commit b20c3dc6 authored by Vicențiu Ciorbaru's avatar Vicențiu Ciorbaru

MDEV-14715: Assertion `!table || (!table->read_set... failed in Field_num::val_decimal

The assertion failure was caused by an incorrectly set read_set for
functions in the ORDER BY clause in part of a union, when we are using
a mergeable view and the order by clause can be skipped (removed).

An order by clause can be skipped if it's part of one part of the UNION as
the result set is not meaningful when multiple SELECT queries are UNIONed. The
server is aware of this optimization and tries to remove the order by
clause before JOIN::prepare. The problem is that we need to throw an
error when the ORDER BY clause contains invalid columns. To do this, we
attempt resolving the ORDER BY expressions, then subsequently drop them
if resolution succeeded. However, ORDER BY resolution had the side
effect of adding the expressions to the all_fields list, which is used
to construct temporary tables to store the result. We may be ignoring
the ORDER BY statement, but the tmp table still tried to compute the
values for the expressions, even if the columns are never used.

The assertion only shows itself if the order by clause contains members
which were not previously in the select list, and are part of a
function.

There is an additional question as to why this only manifests when using
VIEWS and not when using a regular table. The difference lies with the
"reset" of the read_set for the temporary table during
SELECT_LEX::update_used_tables() in JOIN::optimize(). The changes
introduced in fdf789a7 cleared the
read_set when a mergeable view is encountered in the TABLE_LIST
defintion.

Upon initial order_list resolution, the table's read_set is updated
correctly. JOIN::optimize() will only reset the read_set if it
encounters a VIEW. Since we no longer have ORDER BY clause in
JOIN::optimize() we never get to correctly update the read_set again.

Other relevant commit by Timour, which first introduced the order
resolution when we "can_skip_sort_order":
883af99e

Solution:
Don't add the resolved ORDER BY elements to all_fields. We only resolve
them to check if an error should be returned for the query. Ignore them
completely otherwise.
parent 6d826e3d
......@@ -1995,4 +1995,41 @@ avg(f) sub
31.5000 0
1.5000 1
drop table t1,t2,t3;
#
# MDEV-14715 Assertion `!table || (!table->read_set ||
# bitmap_is_set(table->read_set, field_index))'
# failed in Field_num::val_decimal
#
CREATE TABLE t1 (a INT, b INT) ENGINE=MyISAM;
CREATE VIEW v1 AS SELECT * FROM t1;
INSERT INTO t1 VALUES (1, NULL),(3, 4);
(SELECT a, sum(a) AS f FROM v1 group by a ORDER BY b + sum(a))
UNION
(SELECT 2, 2);
ERROR HY000: Invalid use of group function
(SELECT a, sum(a) AS f FROM v1 group by a ORDER BY b + 1)
UNION
(SELECT 2, 2);
a f
1 1
3 3
2 2
SELECT a, b FROM t1
UNION
(SELECT a, VAR_POP(a) AS f FROM v1 GROUP BY a ORDER BY b/a );
a b
1 NULL
3 4
1 0
3 0
DROP TABLE t1;
(SELECT a, sum(a) AS f FROM v1 group by a ORDER BY b + 1)
UNION
(SELECT 2, 2);
ERROR HY000: View 'test.v1' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them
DROP VIEW v1;
(SELECT a, sum(a) AS f FROM v1 group by a ORDER BY b + 1)
UNION
(SELECT 2, 2);
ERROR 42S02: Table 'test.v1' doesn't exist
End of 5.5 tests
......@@ -1384,4 +1384,41 @@ select e,f, (e , f) in (select e,b from t1 union select c,d from t2) as sub from
select avg(f), (e , f) in (select e,b from t1 union select c,d from t2) as sub from t3 group by sub;
drop table t1,t2,t3;
--echo #
--echo # MDEV-14715 Assertion `!table || (!table->read_set ||
--echo # bitmap_is_set(table->read_set, field_index))'
--echo # failed in Field_num::val_decimal
--echo #
CREATE TABLE t1 (a INT, b INT) ENGINE=MyISAM;
CREATE VIEW v1 AS SELECT * FROM t1;
INSERT INTO t1 VALUES (1, NULL),(3, 4);
--error ER_INVALID_GROUP_FUNC_USE
(SELECT a, sum(a) AS f FROM v1 group by a ORDER BY b + sum(a))
UNION
(SELECT 2, 2);
(SELECT a, sum(a) AS f FROM v1 group by a ORDER BY b + 1)
UNION
(SELECT 2, 2);
SELECT a, b FROM t1
UNION
(SELECT a, VAR_POP(a) AS f FROM v1 GROUP BY a ORDER BY b/a );
DROP TABLE t1;
--error ER_VIEW_INVALID
(SELECT a, sum(a) AS f FROM v1 group by a ORDER BY b + 1)
UNION
(SELECT 2, 2);
DROP VIEW v1;
--error ER_NO_SUCH_TABLE
(SELECT a, sum(a) AS f FROM v1 group by a ORDER BY b + 1)
UNION
(SELECT 2, 2);
--echo End of 5.5 tests
......@@ -279,6 +279,10 @@ enum enum_exec_or_opt {WALK_OPTIMIZATION_TABS , WALK_EXECUTION_TABS};
JOIN_TAB *first_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind);
JOIN_TAB *next_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind,
JOIN_TAB *tab);
static bool
find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
ORDER *order, List<Item> &fields, List<Item> &all_fields,
bool is_group_field, bool add_to_all_fields);
/**
This handles SELECT with and without UNION.
......@@ -735,9 +739,15 @@ JOIN::prepare(Item ***rref_pointer_array,
/* Resolve the ORDER BY that was skipped, then remove it. */
if (skip_order_by && select_lex != select_lex->master_unit()->global_parameters)
{
if (setup_order(thd, (*rref_pointer_array), tables_list, fields_list,
all_fields, select_lex->order_list.first))
DBUG_RETURN(-1);
thd->where= "order clause";
for (ORDER *order= select_lex->order_list.first; order; order= order->next)
{
/* Don't add the order items to all fields. Just resolve them to ensure
the query is valid, we'll drop them immediately after. */
if (find_order_in_list(thd, *rref_pointer_array, tables_list, order,
fields_list, all_fields, false, false))
DBUG_RETURN(-1);
}
select_lex->order_list.empty();
}
......@@ -20625,7 +20635,10 @@ cp_buffer_from_ref(THD *thd, TABLE *table, TABLE_REF *ref)
SELECT list)
@param[in,out] all_fields All select, group and order by fields
@param[in] is_group_field True if order is a GROUP field, false if
ORDER by field
ORDER by field
@param[in] add_to_all_fields If the item is to be added to all_fields and
ref_pointer_array, this flag can be set to
false to stop the automatic insertion.
@retval
FALSE if OK
......@@ -20636,7 +20649,7 @@ cp_buffer_from_ref(THD *thd, TABLE *table, TABLE_REF *ref)
static bool
find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
ORDER *order, List<Item> &fields, List<Item> &all_fields,
bool is_group_field)
bool is_group_field, bool add_to_all_fields)
{
Item *order_item= *order->item; /* The item from the GROUP/ORDER caluse. */
Item::Type order_item_type;
......@@ -20755,6 +20768,9 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
thd->is_error()))
return TRUE; /* Wrong field. */
if (!add_to_all_fields)
return FALSE;
uint el= all_fields.elements;
DBUG_ASSERT(all_fields.elements <=
thd->lex->current_select->ref_pointer_array_size);
......@@ -20784,13 +20800,13 @@ find_order_in_list(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
*/
int setup_order(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
List<Item> &fields, List<Item> &all_fields, ORDER *order)
List<Item> &fields, List<Item> &all_fields, ORDER *order)
{
thd->where="order clause";
for (; order; order=order->next)
{
if (find_order_in_list(thd, ref_pointer_array, tables, order, fields,
all_fields, FALSE))
all_fields, FALSE, true))
return 1;
}
return 0;
......@@ -20842,7 +20858,7 @@ setup_group(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
for (ord= order; ord; ord= ord->next)
{
if (find_order_in_list(thd, ref_pointer_array, tables, ord, fields,
all_fields, TRUE))
all_fields, TRUE, true))
return 1;
(*ord->item)->marker= UNDEF_POS; /* Mark found */
if ((*ord->item)->with_sum_func)
......
......@@ -1757,7 +1757,7 @@ int get_quick_record(SQL_SELECT *select);
SORT_FIELD * make_unireg_sortorder(ORDER *order, uint *length,
SORT_FIELD *sortorder);
int setup_order(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
List<Item> &fields, List <Item> &all_fields, ORDER *order);
List<Item> &fields, List <Item> &all_fields, ORDER *order);
int setup_group(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables,
List<Item> &fields, List<Item> &all_fields, ORDER *order,
bool *hidden_group_fields);
......
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