Skip to content

Commit 35d7748

Browse files
author
Dag Wanvik
committed
Bug#21575790 ROLLUP WITH IN SUBQUERY CAUSES CRASH
There is an invariant in Item_in_optimizer which is violated by the tree rewriting done for ROLLUP. Quote from Item_in_optimizer's class Doxygen: args[0] is a copy of Item_in_subselect's left expression and should be kept equal also after resolving. ROLLUP replaces arg[0] with an Item_ref in SELECT_LEX::change_group_ref: "Replace occurrences of group by fields in an expression by ref items. The function replaces occurrences of group by fields in expr by ref objects for these fields unless they are under aggregate functions." This replacement fails to update Item_in_subselect's left expression. This lack of synchronization leads to an error by going down the wrong code path based on the incorrect state in left. The fix is to make sure Item_in_subselect's arg[0] stays in synch with the Item_in_subselect's left expression. It also makes sure the cache of the old item now uses the new. Regression test added.
1 parent 2e4df05 commit 35d7748

File tree

7 files changed

+66
-14
lines changed

7 files changed

+66
-14
lines changed

sql/item_cmpfunc.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2481,6 +2481,22 @@ Item *Item_in_optimizer::transform(Item_transformer transformer, uchar *argument
24812481
}
24822482

24832483

2484+
void Item_in_optimizer::replace_argument(THD *thd, Item **oldpp, Item *newp)
2485+
{
2486+
// Maintain the invariant described in this class's comment
2487+
Item_in_subselect *ss= down_cast<Item_in_subselect *>(args[1]);
2488+
thd->change_item_tree(&ss->left_expr, newp);
2489+
/*
2490+
fix_left() does cache setup. This setup() does (mainly) cache->example=arg[0];
2491+
we could wonder why change_item_tree isn't used instead of this simple
2492+
assignment. The reason is that cache->setup() is called at every
2493+
fix_fields(), so every execution, so it's not important if the previous
2494+
execution left a non-rolled-back now-pointing-to-garbage cache->example -
2495+
it will be overwritten.
2496+
*/
2497+
fix_left(thd, NULL);
2498+
}
2499+
24842500
longlong Item_func_eq::val_int()
24852501
{
24862502
DBUG_ASSERT(fixed == 1);

sql/item_cmpfunc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ class Item_in_optimizer: public Item_bool_func
319319
Item_cache **get_cache() { return &cache; }
320320
void keep_top_level_cache();
321321
Item *transform(Item_transformer transformer, uchar *arg);
322+
void replace_argument(THD *thd, Item **oldpp, Item *newp);
322323
};
323324

324325
/// Abstract factory interface for creating comparison predicates.

sql/item_func.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,6 +1101,12 @@ Item *Item_func::gc_subst_transformer(uchar *arg)
11011101
}
11021102

11031103

1104+
void Item_func::replace_argument(THD *thd, Item **oldpp, Item *newp)
1105+
{
1106+
thd->change_item_tree(oldpp, newp);
1107+
}
1108+
1109+
11041110
double Item_int_func::val_real()
11051111
{
11061112
DBUG_ASSERT(fixed == 1);

sql/item_func.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,12 @@ class Item_func :public Item_result_field
453453
}
454454
virtual Item *gc_subst_transformer(uchar *arg);
455455

456+
/**
457+
Does essentially the same as THD::change_item_tree, plus
458+
maintains any necessary any invariants.
459+
*/
460+
virtual void replace_argument(THD *thd, Item **oldpp, Item *newp);
461+
456462
protected:
457463
/**
458464
Whether or not an item should contribute to the filtering effect

sql/item_subselect.cc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,7 +1397,7 @@ bool Item_in_subselect::test_limit()
13971397
Item_in_subselect::Item_in_subselect(Item * left_exp,
13981398
st_select_lex *select):
13991399
Item_exists_subselect(), left_expr(left_exp), left_expr_cache(NULL),
1400-
left_expr_cache_filled(false), need_expr_cache(TRUE), expr(NULL),
1400+
left_expr_cache_filled(false), need_expr_cache(TRUE), m_injected_left_expr(NULL),
14011401
optimizer(NULL), was_null(FALSE), abort_on_null(FALSE),
14021402
in2exists_info(NULL), pushed_cond_guards(NULL), upper_item(NULL)
14031403
{
@@ -1415,7 +1415,7 @@ Item_in_subselect::Item_in_subselect(Item * left_exp,
14151415
Item_in_subselect::Item_in_subselect(const POS &pos, Item * left_exp,
14161416
PT_subselect *pt_subselect_arg)
14171417
: super(pos), left_expr(left_exp), left_expr_cache(NULL),
1418-
left_expr_cache_filled(false), need_expr_cache(TRUE), expr(NULL),
1418+
left_expr_cache_filled(false), need_expr_cache(TRUE), m_injected_left_expr(NULL),
14191419
optimizer(NULL), was_null(FALSE), abort_on_null(FALSE),
14201420
in2exists_info(NULL), pushed_cond_guards(NULL), upper_item(NULL),
14211421
pt_subselect(pt_subselect_arg)
@@ -1873,7 +1873,7 @@ Item_in_subselect::single_value_transformer(SELECT_LEX *select,
18731873
As far as Item_ref_in_optimizer do not substitute itself on fix_fields
18741874
we can use same item for all selects.
18751875
*/
1876-
Item_ref *const left=
1876+
Item_direct_ref *const left=
18771877
new Item_direct_ref(&select->context, (Item**)optimizer->get_cache(),
18781878
(char *)"<no matter>", (char *)in_left_expr_name);
18791879
if (left == NULL)
@@ -1883,7 +1883,7 @@ Item_in_subselect::single_value_transformer(SELECT_LEX *select,
18831883
if (!left_expr->const_item())
18841884
left->depended_from= select->outer_select();
18851885

1886-
expr= left;
1886+
m_injected_left_expr= left;
18871887

18881888
DBUG_ASSERT(in2exists_info == NULL);
18891889
in2exists_info= new In2exists_info;
@@ -1965,7 +1965,7 @@ Item_in_subselect::single_value_in_to_exists_transformer(SELECT_LEX *select,
19651965
select->group_list.elements)
19661966
{
19671967
bool tmp;
1968-
Item_bool_func *item= func->create(expr,
1968+
Item_bool_func *item= func->create(m_injected_left_expr,
19691969
new Item_ref_null_helper(&select->context,
19701970
this,
19711971
&select->ref_ptrs[0],
@@ -2012,7 +2012,7 @@ Item_in_subselect::single_value_in_to_exists_transformer(SELECT_LEX *select,
20122012
if (select->table_list.elements || select->where_cond())
20132013
{
20142014
bool tmp;
2015-
Item_bool_func *item= func->create(expr, orig_item);
2015+
Item_bool_func *item= func->create(m_injected_left_expr, orig_item);
20162016
/*
20172017
We may soon add a 'OR inner IS NULL' to 'item', but that may later be
20182018
removed if 'inner' is not nullable, so the in2exists mark must be on
@@ -2104,7 +2104,7 @@ Item_in_subselect::single_value_in_to_exists_transformer(SELECT_LEX *select,
21042104
argument (reference) to fix_fields()
21052105
*/
21062106
Item_bool_func *new_having=
2107-
func->create(expr,
2107+
func->create(m_injected_left_expr,
21082108
new Item_ref_null_helper(&select->context, this,
21092109
&select->ref_ptrs[0],
21102110
(char *)"<no matter>",

sql/item_subselect.h

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ class Item_in_subselect :public Item_exists_subselect
386386
public:
387387
Item *left_expr;
388388
protected:
389-
/*
389+
/**
390390
Cache of the left operand of the subquery predicate. Allocated in the
391391
runtime memory root, for each execution, thus need not be freed.
392392
*/
@@ -395,13 +395,35 @@ class Item_in_subselect :public Item_exists_subselect
395395
/** The need for expr cache may be optimized away, @sa init_left_expr_cache. */
396396
bool need_expr_cache;
397397

398-
/*
399-
expr & optimizer used in subselect rewriting to store Item for
400-
all JOIN in UNION
398+
private:
399+
/**
400+
In the case of
401+
402+
x COMP_OP (SELECT1 UNION SELECT2 ...)
403+
404+
- the subquery transformation is done on SELECT1; this requires wrapping
405+
'x' with more Item layers, and injecting that in a condition in SELECT1.
406+
407+
- the same transformation is done on SELECT2; but the wrapped 'x' doesn't
408+
need to be created again, the one created for SELECT1 could be reused
409+
410+
- to achieve this, the wrapped 'x' is stored in member 'm_injected_left_expr'
411+
when it is created for SELECT1, and is later reused for SELECT2.
412+
*/
413+
414+
/**
415+
This will refer to a cached value which is reevaluated once for each candidate
416+
row, cf. setup in ::single_value_transformer.
417+
*/
418+
Item_direct_ref *m_injected_left_expr;
419+
420+
/**
421+
Pointer to the created Item_in_optimizer; it is stored for the same
422+
reasons as 'm_injected_left_expr'.
401423
*/
402-
Item *expr;
403424
Item_in_optimizer *optimizer;
404425
bool was_null;
426+
protected:
405427
bool abort_on_null;
406428
private:
407429
/**
@@ -471,7 +493,7 @@ class Item_in_subselect :public Item_exists_subselect
471493

472494
Item_in_subselect()
473495
:Item_exists_subselect(), left_expr(NULL), left_expr_cache(NULL),
474-
left_expr_cache_filled(false), need_expr_cache(TRUE), expr(NULL),
496+
left_expr_cache_filled(false), need_expr_cache(TRUE), m_injected_left_expr(NULL),
475497
optimizer(NULL), was_null(FALSE), abort_on_null(FALSE),
476498
in2exists_info(NULL), pushed_cond_guards(NULL), upper_item(NULL)
477499
{}

sql/sql_resolver.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3486,7 +3486,8 @@ bool SELECT_LEX::change_group_ref(THD *thd, Item_func *expr, bool *changed)
34863486
if (!(new_item= new Item_ref(&context, group->item, 0,
34873487
item->item_name.ptr())))
34883488
return true; /* purecov: inspected */
3489-
thd->change_item_tree(arg, new_item);
3489+
3490+
expr->replace_argument(thd, arg, new_item);
34903491
arg_changed= true;
34913492
}
34923493
}

0 commit comments

Comments
 (0)