mirror of
https://github.com/MariaDB/server.git
synced 2025-07-30 16:24:05 +03:00
BUG#8804: wrong results for NULL IN (SELECT ...)
Evaluate "NULL IN (SELECT ...)" in a special way: Disable pushed-down conditions and their "consequences": = Do full table scans instead of unique_[index_subquery] lookups. = Change appropriate "ref_or_null" accesses to full table scans in subquery's joins. Also cache value of NULL IN (SELECT ...) if the SELECT is not correlated wrt any upper select. mysql-test/r/subselect.result: BUG#8804: wrong results for NULL IN (SELECT ...): - Updated test results sql/item.h: BUG#8804: wrong results for NULL IN (SELECT ...): - Added comments sql/item_cmpfunc.cc: BUG#8804: wrong results for NULL IN (SELECT ...): Made Item_in_optimizer to: - cache the value of "NULL IN (uncorrelated select)" - Turn off pushed-down predicates when evaluating "NULL IN (SELECT ...)" sql/item_cmpfunc.h: BUG#8804: wrong results for NULL IN (SELECT ...): - Made Item_in_optimizer cache the value of "NULL IN (uncorrelated select)" - Added comments sql/item_subselect.cc: BUG#8804: wrong results for NULL IN (SELECT ...): - When needed, wrap the predicates we push into subquery into an Item_func_trig_cond so we're able to turn them off when evaluating NULL IN (SELECT ...). - Added code to evaluate NULL IN (SELECT ...) in a special way: = In [unique_]index_subquery, do full table scan to see if there are any rows. = For other subqueries, change ref[_or_null] to ALL if the ref[_or_null] was created from pushed-down predicate. sql/item_subselect.h: BUG#8804: wrong results for NULL IN (SELECT ...): - Added Item_subselect::is_correlated - Added comments sql/records.cc: BUG#8804: wrong results for NULL IN (SELECT ...): - Make rr_sequential() non-static sql/sql_lex.cc: BUG#8804: wrong results for NULL IN (SELECT ...): - Added st_select_lex::is_correlated and Item_subselect::is_correlated. sql/sql_lex.h: BUG#8804: wrong results for NULL IN (SELECT ...): - Added st_select_lex::is_correlated sql/sql_select.cc: BUG#8804: wrong results for NULL IN (SELECT ...): - Added KEY_FIELD::outer_ref to keep track of which ref accesses are created from predicates that were pushed down into the subquery. sql/sql_select.h: BUG#8804: wrong results for NULL IN (SELECT ...): - Added KEYUSE::outer_ref mysql-test/r/subselect3.result: New BitKeeper file ``mysql-test/r/subselect3.result'' mysql-test/t/subselect3.test: New BitKeeper file ``mysql-test/t/subselect3.test''
This commit is contained in:
@ -158,8 +158,8 @@ static int join_read_prev_same(READ_RECORD *info);
|
||||
static int join_read_prev(READ_RECORD *info);
|
||||
static int join_ft_read_first(JOIN_TAB *tab);
|
||||
static int join_ft_read_next(READ_RECORD *info);
|
||||
static int join_read_always_key_or_null(JOIN_TAB *tab);
|
||||
static int join_read_next_same_or_null(READ_RECORD *info);
|
||||
int join_read_always_key_or_null(JOIN_TAB *tab);
|
||||
int join_read_next_same_or_null(READ_RECORD *info);
|
||||
static COND *make_cond_for_table(COND *cond,table_map table,
|
||||
table_map used_table);
|
||||
static Item* part_of_refkey(TABLE *form,Field *field);
|
||||
@ -512,11 +512,12 @@ err:
|
||||
DBUG_RETURN(-1); /* purecov: inspected */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
test if it is known for optimisation IN subquery
|
||||
|
||||
SYNOPSYS
|
||||
JOIN::test_in_subselect
|
||||
SYNOPSIS
|
||||
JOIN::test_in_subselect()
|
||||
where - pointer for variable in which conditions should be
|
||||
stored if subquery is known
|
||||
|
||||
@ -550,6 +551,35 @@ bool JOIN::test_in_subselect(Item **where)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Check if the passed HAVING clause is a clause added by subquery optimizer
|
||||
|
||||
SYNOPSIS
|
||||
is_having_subq_predicates()
|
||||
having Having clause
|
||||
|
||||
RETURN
|
||||
TRUE The passed HAVING clause was added by the subquery optimizer
|
||||
FALSE Otherwise
|
||||
*/
|
||||
|
||||
bool is_having_subq_predicates(Item *having)
|
||||
{
|
||||
if (having->type() == Item::FUNC_ITEM)
|
||||
{
|
||||
if (((Item_func *) having)->functype() == Item_func::ISNOTNULLTEST_FUNC)
|
||||
return TRUE;
|
||||
if (((Item_func *) having)->functype() == Item_func::TRIG_COND_FUNC)
|
||||
{
|
||||
having= ((Item_func*)having)->arguments()[0];
|
||||
if (((Item_func *) having)->functype() == Item_func::ISNOTNULLTEST_FUNC)
|
||||
return TRUE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
global select optimisation.
|
||||
return 0 - success
|
||||
@ -1016,9 +1046,7 @@ JOIN::optimize()
|
||||
}
|
||||
} else if (join_tab[0].type == JT_REF_OR_NULL &&
|
||||
join_tab[0].ref.items[0]->name == in_left_expr_name &&
|
||||
having->type() == Item::FUNC_ITEM &&
|
||||
((Item_func *) having)->functype() ==
|
||||
Item_func::ISNOTNULLTEST_FUNC)
|
||||
is_having_subq_predicates(having))
|
||||
{
|
||||
join_tab[0].type= JT_INDEX_SUBQUERY;
|
||||
error= 0;
|
||||
@ -2512,6 +2540,9 @@ typedef struct key_field_t { // Used when finding key fields
|
||||
when val IS NULL.
|
||||
*/
|
||||
bool null_rejecting;
|
||||
|
||||
/* TRUE<=> This ref access is an outer subquery reference access */
|
||||
bool outer_ref;
|
||||
} KEY_FIELD;
|
||||
|
||||
/* Values in optimize */
|
||||
@ -2810,6 +2841,7 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, Item_func *cond,
|
||||
cond->functype() == Item_func::MULT_EQUAL_FUNC) &&
|
||||
((*value)->type() == Item::FIELD_ITEM) &&
|
||||
((Item_field*)*value)->field->maybe_null());
|
||||
(*key_fields)->outer_ref= FALSE;
|
||||
(*key_fields)++;
|
||||
}
|
||||
|
||||
@ -2868,7 +2900,7 @@ add_key_equal_fields(KEY_FIELD **key_fields, uint and_level,
|
||||
}
|
||||
|
||||
static void
|
||||
add_key_fields(KEY_FIELD **key_fields,uint *and_level,
|
||||
add_key_fields(JOIN *join, KEY_FIELD **key_fields, uint *and_level,
|
||||
COND *cond, table_map usable_tables,
|
||||
SARGABLE_PARAM **sargables)
|
||||
{
|
||||
@ -2881,28 +2913,54 @@ add_key_fields(KEY_FIELD **key_fields,uint *and_level,
|
||||
{
|
||||
Item *item;
|
||||
while ((item=li++))
|
||||
add_key_fields(key_fields,and_level,item,usable_tables,sargables);
|
||||
add_key_fields(join, key_fields, and_level, item, usable_tables,
|
||||
sargables);
|
||||
for (; org_key_fields != *key_fields ; org_key_fields++)
|
||||
org_key_fields->level= *and_level;
|
||||
}
|
||||
else
|
||||
{
|
||||
(*and_level)++;
|
||||
add_key_fields(key_fields,and_level,li++,usable_tables,sargables);
|
||||
add_key_fields(join, key_fields, and_level, li++, usable_tables,
|
||||
sargables);
|
||||
Item *item;
|
||||
while ((item=li++))
|
||||
{
|
||||
KEY_FIELD *start_key_fields= *key_fields;
|
||||
(*and_level)++;
|
||||
add_key_fields(key_fields,and_level,item,usable_tables,sargables);
|
||||
add_key_fields(join, key_fields, and_level, item, usable_tables,
|
||||
sargables);
|
||||
*key_fields=merge_key_fields(org_key_fields,start_key_fields,
|
||||
*key_fields,++(*and_level));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
/* If item is of type 'field op field/constant' add it to key_fields */
|
||||
|
||||
/*
|
||||
Subquery optimization: check if the encountered condition is one
|
||||
added by condition push down into subquery.
|
||||
*/
|
||||
{
|
||||
if (cond->type() == Item::FUNC_ITEM &&
|
||||
((Item_func*)cond)->functype() == Item_func::TRIG_COND_FUNC)
|
||||
{
|
||||
cond= ((Item_func*)cond)->arguments()[0];
|
||||
if (!join->group_list && !join->order &&
|
||||
join->unit->item &&
|
||||
join->unit->item->substype() == Item_subselect::IN_SUBS &&
|
||||
!join->unit->first_select()->next_select())
|
||||
{
|
||||
add_key_fields(join, key_fields, and_level, cond, usable_tables,
|
||||
sargables);
|
||||
// Indicate that this ref access candidate is for subquery lookup:
|
||||
(*key_fields)[-1].outer_ref= TRUE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* If item is of type 'field op field/constant' add it to key_fields */
|
||||
if (cond->type() != Item::FUNC_ITEM)
|
||||
return;
|
||||
Item_func *cond_func= (Item_func*) cond;
|
||||
@ -3076,6 +3134,7 @@ add_key_part(DYNAMIC_ARRAY *keyuse_array,KEY_FIELD *key_field)
|
||||
keyuse.used_tables=key_field->val->used_tables();
|
||||
keyuse.optimize= key_field->optimize & KEY_OPTIMIZE_REF_OR_NULL;
|
||||
keyuse.null_rejecting= key_field->null_rejecting;
|
||||
keyuse.outer_ref= key_field->outer_ref;
|
||||
VOID(insert_dynamic(keyuse_array,(gptr) &keyuse));
|
||||
}
|
||||
}
|
||||
@ -3198,7 +3257,7 @@ sort_keyuse(KEYUSE *a,KEYUSE *b)
|
||||
Here we can add 'ref' access candidates for t1 and t2, but not for t3.
|
||||
*/
|
||||
|
||||
static void add_key_fields_for_nj(TABLE_LIST *nested_join_table,
|
||||
static void add_key_fields_for_nj(JOIN *join, TABLE_LIST *nested_join_table,
|
||||
KEY_FIELD **end, uint *and_level,
|
||||
SARGABLE_PARAM **sargables)
|
||||
{
|
||||
@ -3210,12 +3269,13 @@ static void add_key_fields_for_nj(TABLE_LIST *nested_join_table,
|
||||
while ((table= li++))
|
||||
{
|
||||
if (table->nested_join)
|
||||
add_key_fields_for_nj(table, end, and_level, sargables);
|
||||
add_key_fields_for_nj(join, table, end, and_level, sargables);
|
||||
else
|
||||
if (!table->on_expr)
|
||||
tables |= table->table->map;
|
||||
}
|
||||
add_key_fields(end, and_level, nested_join_table->on_expr, tables, sargables);
|
||||
add_key_fields(join, end, and_level, nested_join_table->on_expr, tables,
|
||||
sargables);
|
||||
}
|
||||
|
||||
|
||||
@ -3290,7 +3350,8 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab,
|
||||
return TRUE;
|
||||
if (cond)
|
||||
{
|
||||
add_key_fields(&end,&and_level,cond,normal_tables,sargables);
|
||||
add_key_fields(join_tab->join, &end, &and_level, cond, normal_tables,
|
||||
sargables);
|
||||
for (; field != end ; field++)
|
||||
{
|
||||
add_key_part(keyuse,field);
|
||||
@ -3312,8 +3373,9 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab,
|
||||
into account as well.
|
||||
*/
|
||||
if (*join_tab[i].on_expr_ref)
|
||||
add_key_fields(&end,&and_level,*join_tab[i].on_expr_ref,
|
||||
join_tab[i].table->map,sargables);
|
||||
add_key_fields(join_tab->join, &end, &and_level,
|
||||
*join_tab[i].on_expr_ref,
|
||||
join_tab[i].table->map, sargables);
|
||||
}
|
||||
|
||||
/* Process ON conditions for the nested joins */
|
||||
@ -3323,7 +3385,8 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab,
|
||||
while ((table= li++))
|
||||
{
|
||||
if (table->nested_join)
|
||||
add_key_fields_for_nj(table, &end, &and_level, sargables);
|
||||
add_key_fields_for_nj(join_tab->join, table, &end, &and_level,
|
||||
sargables);
|
||||
}
|
||||
}
|
||||
|
||||
@ -10784,6 +10847,13 @@ join_init_quick_read_record(JOIN_TAB *tab)
|
||||
}
|
||||
|
||||
|
||||
int rr_sequential(READ_RECORD *info);
|
||||
int init_read_record_seq(JOIN_TAB *tab)
|
||||
{
|
||||
tab->read_record.read_record= rr_sequential;
|
||||
return tab->read_record.file->ha_rnd_init(1);
|
||||
}
|
||||
|
||||
static int
|
||||
test_if_quick_select(JOIN_TAB *tab)
|
||||
{
|
||||
@ -10912,7 +10982,7 @@ join_ft_read_next(READ_RECORD *info)
|
||||
Reading of key with key reference and one part that may be NULL
|
||||
*/
|
||||
|
||||
static int
|
||||
int
|
||||
join_read_always_key_or_null(JOIN_TAB *tab)
|
||||
{
|
||||
int res;
|
||||
@ -10928,7 +10998,7 @@ join_read_always_key_or_null(JOIN_TAB *tab)
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
int
|
||||
join_read_next_same_or_null(READ_RECORD *info)
|
||||
{
|
||||
int error;
|
||||
|
Reference in New Issue
Block a user