Commit 9fa1bce4 authored by Sergey Petrunya's avatar Sergey Petrunya

MWL#17: Table elimination

mysql-test/r/table_elim.result:
  MWL#17: Table elimination
  - More tests
mysql-test/t/table_elim.test:
  MWL#17: Table elimination
  - More tests
sql/opt_table_elimination.cc:
  MWL#17: Table elimination
  - Code cleanup
sql/sql_select.cc:
  MWL#17: Table elimination
  - Code cleanup
sql/sql_select.h:
  MWL#17: Table elimination
  - Code cleanup
sql/table.h:
  MWL#17: Table elimination
  - Code cleanup
parent d764108a
...@@ -172,3 +172,33 @@ Note 1276 Field or reference 'test.F.id' of SELECT #3 was resolved in SELECT #1 ...@@ -172,3 +172,33 @@ Note 1276 Field or reference 'test.F.id' of SELECT #3 was resolved in SELECT #1
Note 1003 select `F`.`id` AS `id` from `test`.`t0` `F` join `test`.`t2` `A2` where ((`F`.`id` = `A2`.`id`) and (`A2`.`attr2` between 12 and 14) and (`A2`.`fromdate` = (select max(`test`.`t2`.`fromdate`) AS `MAX(fromdate)` from `test`.`t2` where (`test`.`t2`.`id` = `F`.`id`)))) Note 1003 select `F`.`id` AS `id` from `test`.`t0` `F` join `test`.`t2` `A2` where ((`F`.`id` = `A2`.`id`) and (`A2`.`attr2` between 12 and 14) and (`A2`.`fromdate` = (select max(`test`.`t2`.`fromdate`) AS `MAX(fromdate)` from `test`.`t2` where (`test`.`t2`.`id` = `F`.`id`))))
drop view v1, v2; drop view v1, v2;
drop table t0, t1, t2; drop table t0, t1, t2;
create table t1 (a int);
insert into t1 values (0),(1),(2),(3);
create table t2 (pk1 int, pk2 int, pk3 int, col int, primary key(pk1, pk2, pk3));
insert into t2 select a,a,a,a from t1;
This must use only t1:
explain select t1.* from t1 left join t2 on t2.pk1=t1.a and
t2.pk2=t2.pk1+1 and
t2.pk3=t2.pk2+1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 4
This must use only t1:
explain select t1.* from t1 left join t2 on t2.pk1=t1.a and
t2.pk3=t2.pk1+1 and
t2.pk2=t2.pk3+1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 4
This must use both:
explain select t1.* from t1 left join t2 on t2.pk1=t1.a and
t2.pk3=t2.pk1+1 and
t2.pk2=t2.pk3+t2.col;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 4
1 SIMPLE t2 ref PRIMARY PRIMARY 4 test.t1.a 1
This must use only t1:
explain select t1.* from t1 left join t2 on t2.pk2=t1.a and
t2.pk1=t2.pk2+1 and
t2.pk3=t2.pk1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 4
drop table t1, t2;
...@@ -125,3 +125,36 @@ explain extended select id from v2 where attr2 between 12 and 14; ...@@ -125,3 +125,36 @@ explain extended select id from v2 where attr2 between 12 and 14;
drop view v1, v2; drop view v1, v2;
drop table t0, t1, t2; drop table t0, t1, t2;
#
# Tests for the code that uses t.keypartX=func(t.keypartY) equalities to
# make table elimination inferences
#
create table t1 (a int);
insert into t1 values (0),(1),(2),(3);
create table t2 (pk1 int, pk2 int, pk3 int, col int, primary key(pk1, pk2, pk3));
insert into t2 select a,a,a,a from t1;
--echo This must use only t1:
explain select t1.* from t1 left join t2 on t2.pk1=t1.a and
t2.pk2=t2.pk1+1 and
t2.pk3=t2.pk2+1;
--echo This must use only t1:
explain select t1.* from t1 left join t2 on t2.pk1=t1.a and
t2.pk3=t2.pk1+1 and
t2.pk2=t2.pk3+1;
--echo This must use both:
explain select t1.* from t1 left join t2 on t2.pk1=t1.a and
t2.pk3=t2.pk1+1 and
t2.pk2=t2.pk3+t2.col;
--echo This must use only t1:
explain select t1.* from t1 left join t2 on t2.pk2=t1.a and
t2.pk1=t2.pk2+1 and
t2.pk3=t2.pk1;
drop table t1, t2;
...@@ -42,14 +42,16 @@ ...@@ -42,14 +42,16 @@
Table elimination is redone on every PS re-execution. Table elimination is redone on every PS re-execution.
*/ */
static int static void mark_as_eliminated(JOIN *join, TABLE_LIST *tbl);
eliminate_tables_for_join_list(JOIN *join, List<TABLE_LIST> *join_list, static bool table_has_one_match(TABLE *table, table_map bound_tables,
bool *multiple_matches);
static uint
eliminate_tables_for_list(JOIN *join, TABLE **leaves_arr,
List<TABLE_LIST> *join_list,
bool its_outer_join,
table_map tables_in_list,
table_map tables_used_elsewhere, table_map tables_used_elsewhere,
uint *const_tbl_count, table_map *const_tables); bool *multiple_matches);
static bool table_has_one_match(TABLE *table, table_map bound_tables);
static void
mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count,
table_map *const_tables);
static bool static bool
extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table,
KEYUSE *key_start, KEYUSE *key_end, KEYUSE *key_start, KEYUSE *key_end,
...@@ -95,8 +97,7 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, ...@@ -95,8 +97,7 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table,
*/ */
void eliminate_tables(JOIN *join, uint *const_tbl_count, void eliminate_tables(JOIN *join)
table_map *const_tables)
{ {
Item *item; Item *item;
table_map used_tables; table_map used_tables;
...@@ -140,220 +141,140 @@ void eliminate_tables(JOIN *join, uint *const_tbl_count, ...@@ -140,220 +141,140 @@ void eliminate_tables(JOIN *join, uint *const_tbl_count,
} }
} }
if (((1 << join->tables) - 1) & ~used_tables) table_map all_tables= join->all_tables_map();
if (all_tables & ~used_tables)
{ {
/* There are some tables that we probably could eliminate. Try it. */ /* There are some tables that we probably could eliminate. Try it. */
eliminate_tables_for_join_list(join, join->join_list, used_tables, TABLE *leaves_array[MAX_TABLES];
const_tbl_count, const_tables); bool multiple_matches= FALSE;
eliminate_tables_for_list(join, leaves_array, join->join_list, FALSE,
all_tables, used_tables, &multiple_matches);
} }
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
/* /*
Perform table elimination in a given join list Perform table elimination in a given join list
SYNOPSIS SYNOPSIS
eliminate_tables_for_join_list() eliminate_tables_for_list()
join The join join The join
join_list Join list to work on join_list Join list to work on
tables_used_elsewhere Bitmap of tables that are referred to from tables_used_elsewhere Bitmap of tables that are referred to from
somewhere outside of the join list (e.g. somewhere outside of the join list (e.g.
select list, HAVING, etc). select list, HAVING, etc).
const_tbl_count INOUT Number of constant tables (eliminated tables
are considered constant)
const_tables INOUT Bitmap of constant tables.
DESCRIPTION DESCRIPTION
Try eliminating members of the given join list (and its children,
recursively).
Search for tables to be eliminated is performed on recursive descent,
while the elimination is done on ascent.
DESCENT AND NO-REFERENCES CHECK
The descent part is needed because of the following: consider a join list
t0 LEFT JOIN
(t1
LEFT JOIN t2 ON cond1(t1,t2)
LEFT JOIN t3 ON cond2(..., possibly-t2) (*)
LEFT JOIN t4 ON cond3(..., possibly-t2, possibly-t3)
) ON cond4
Suppose we're looking at whether we can eliminate outer join marked with
(*), in other words, table t3. Before we can do that, we need to
1. Check that there are no references to table t3 in cond4 (in general:
all ON expressions of embedding outer joins, this explains the need for
descent)
2. Check that there are no references to table t3 in its following-siblings,
in this example, in cond3.
3. Although SQL language doesn't allow referring to table t3 from cond1,
simplify_joins() may create such back-references, so we'll also need to
check if t3's preceding-siblings have ON expressions with references
from t3.
ASCENT AND THE ELIMINATION
The removal is done in a bottom-up way because we can consider an outer
join nest for elimination only after we have successfully eliminated all
of its children outer joins.
RETURN RETURN
Number of tables that have been eliminated Number of base tables left after elimination. 0 means everything was
eliminated. Tables that belong to the
children of this join nest are also counted.
// TRUE The entire join list can be eliminated (caller should remove)
// FALSE Otherwise
number of tables that were eliminated (compare this with total number of
tables in the join_list to tell if the entire join was eliminated)
*/ */
static uint
static int eliminate_tables_for_list(JOIN *join, TABLE **leaves_arr,
eliminate_tables_for_join_list(JOIN *join, List<TABLE_LIST> *join_list, List<TABLE_LIST> *join_list,
bool its_outer_join,
table_map tables_in_list,
table_map tables_used_elsewhere, table_map tables_used_elsewhere,
uint *const_tbl_count, table_map *const_tables) bool *multiple_matches)
{ {
List_iterator<TABLE_LIST> it(*join_list);
table_map used_tables_on_right[MAX_TABLES];
table_map tables_used_on_left;
TABLE_LIST *tbl; TABLE_LIST *tbl;
int i, n_tables; List_iterator<TABLE_LIST> it(*join_list);
int eliminated=0; table_map tables_used_on_left= 0;
TABLE **cur_table= leaves_arr;
bool children_have_multiple_matches= FALSE;
uint base_tables= 0;
/* Collect used_tables_on_right array */ while ((tbl= it++))
for (i=0; (tbl= it++); i++)
{ {
used_tables_on_right[i]= 0;
if (tbl->on_expr) if (tbl->on_expr)
used_tables_on_right[i]= tbl->on_expr->used_tables();
if (tbl->nested_join)
used_tables_on_right[i]= tbl->nested_join->used_tables;
}
n_tables= i;
for (i= n_tables - 2; i > 0; i--)
used_tables_on_right[i] |= used_tables_on_right[i+1];
i= 1;
it.rewind();
tables_used_on_left= 0;
/* For each member of the join list, check if we can eliminate it */
while ((tbl= it++))
{ {
table_map tables_used_outside= tables_used_on_left | table_map outside_used_tables= tables_used_elsewhere |
used_tables_on_right[i] | tables_used_on_left;
tables_used_elsewhere; bool multiple_matches= FALSE;
table_map cur_tables= 0;
if (tbl->nested_join) if (tbl->nested_join)
{ {
DBUG_ASSERT(tbl->on_expr); /* This is "... LEFT JOIN (join_nest) ON cond" */
/* uint n;
There can be cases where table removal is applicable for tables if (!(n= eliminate_tables_for_list(join, cur_table,
within the outer join but not for the outer join itself. Ask to &tbl->nested_join->join_list, TRUE,
remove the children first. tbl->nested_join->used_tables,
outside_used_tables,
TODO: NoHopelessEliminationAttempts: the below call can return &multiple_matches)))
information about whether it would make any sense to try removing
this entire outer join nest.
*/
int eliminated_in_children=
eliminate_tables_for_join_list(join, &tbl->nested_join->join_list,
tables_used_outside,
const_tbl_count, const_tables);
tbl->nested_join->n_tables -=eliminated_in_children;
cur_tables= tbl->nested_join->used_tables;
if (!(cur_tables & tables_used_outside))
{
/*
Check if all embedded tables together can produce at most one
record combination. This is true when
- each of them has one_match(outer-tables) property
(this is a stronger condition than all of them together having
this property but that's irrelevant here)
- there are no outer joins among them
(except for the case of outer join which has all inner tables
to be constant and is guaranteed to produce only one record.
that record will be null-complemented)
*/
bool one_match= TRUE;
List_iterator<TABLE_LIST> it2(tbl->nested_join->join_list);
TABLE_LIST *inner;
while ((inner= it2++))
{
/*
Bail out if we see an outer join (TODO: handle the above
null-complemntated-rows-only case)
*/
if (inner->on_expr)
{
one_match= FALSE;
break;
}
if (inner->table && // <-- to be removed after NoHopelessEliminationAttempts
!table_has_one_match(inner->table,
~tbl->nested_join->used_tables))
{ {
one_match= FALSE; mark_as_eliminated(join, tbl);
break;
} }
tbl->nested_join->n_tables= n;
base_tables += n;
} }
if (one_match) else
{ {
it2.rewind(); /* This is "... LEFT JOIN tbl ON cond" */
while ((inner= it2++)) if (!(tbl->table->map & outside_used_tables) &&
table_has_one_match(tbl->table, join->all_tables_map(),
&multiple_matches))
{ {
mark_table_as_eliminated(join, inner->table, const_tbl_count, mark_as_eliminated(join, tbl);
const_tables);
}
eliminated += tbl->nested_join->join_list.elements;
//psergey-todo: do we need to do anything about removing the join
//nest?
tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL);
} }
else else
{ base_tables++;
eliminated += eliminated_in_children;
}
} }
tables_used_on_left |= tbl->on_expr->used_tables();
children_have_multiple_matches= children_have_multiple_matches ||
multiple_matches;
} }
else if (tbl->on_expr) else
{
cur_tables= tbl->on_expr->used_tables();
if (!(tbl->table->map & tables_used_outside) &&
table_has_one_match(tbl->table, (table_map)-1))
{ {
mark_table_as_eliminated(join, tbl->table, const_tbl_count, DBUG_ASSERT(!tbl->nested_join);
const_tables); base_tables++;
tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL);
eliminated += 1;
}
} }
i++; if (tbl->table)
tables_used_on_left |= cur_tables; *(cur_table++)= tbl->table;
} }
return eliminated;
}
*multiple_matches |= children_have_multiple_matches;
/* /* Try eliminating the nest we're called for */
Mark table as eliminated: if (its_outer_join && !children_have_multiple_matches &&
- Mark it as constant table !(tables_in_list & tables_used_elsewhere))
- Move it to the front of join order {
- Record it in join->eliminated_tables table_map bound_tables= join->const_table_map | (join->all_tables_map() &
*/ ~tables_in_list);
table_map old_bound_tables;
TABLE **leaves_end= cur_table;
/*
Do the same as const table search table: try to expand the set of bound
tables until it covers all tables in the join_list
*/
do
{
old_bound_tables= bound_tables;
for (cur_table= leaves_arr; cur_table != leaves_end; cur_table++)
{
if (!((*cur_table)->map & join->eliminated_tables) &&
table_has_one_match(*cur_table, bound_tables, multiple_matches))
{
bound_tables |= (*cur_table)->map;
}
}
} while (old_bound_tables != bound_tables);
static if (!(tables_in_list & ~bound_tables))
void mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count,
table_map *const_tables)
{
JOIN_TAB *tab= table->reginfo.join_tab;
if (!(*const_tables & tab->table->map))
{ {
DBUG_PRINT("info", ("Eliminated table %s", table->alias)); /*
tab->type= JT_CONST; This join_list can be eliminated. Signal about this to the caller by
join->eliminated_tables |= table->map; returning number of tables.
*const_tables |= table->map; */
join->const_table_map|= table->map; base_tables= 0;
set_position(join, (*const_tbl_count)++, tab, (KEYUSE*)0); }
} }
return base_tables;
} }
...@@ -364,6 +285,9 @@ void mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count, ...@@ -364,6 +285,9 @@ void mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count,
table_has_one_match() table_has_one_match()
table The [base] table being checked table The [base] table being checked
bound_tables Tables that should be considered bound. bound_tables Tables that should be considered bound.
multiple_matches OUT Set to TRUE when there is no way we could
find find a limitation that would give us one-match
property.
DESCRIPTION DESCRIPTION
Check if table will produce at most one matching record for each record Check if table will produce at most one matching record for each record
...@@ -389,7 +313,8 @@ void mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count, ...@@ -389,7 +313,8 @@ void mark_table_as_eliminated(JOIN *join, TABLE *table, uint *const_tbl_count,
FALSE No FALSE No
*/ */
static bool table_has_one_match(TABLE *table, table_map bound_tables) static bool table_has_one_match(TABLE *table, table_map bound_tables,
bool *multiple_matches)
{ {
KEYUSE *keyuse= table->reginfo.join_tab->keyuse; KEYUSE *keyuse= table->reginfo.join_tab->keyuse;
if (keyuse) if (keyuse)
...@@ -405,7 +330,7 @@ static bool table_has_one_match(TABLE *table, table_map bound_tables) ...@@ -405,7 +330,7 @@ static bool table_has_one_match(TABLE *table, table_map bound_tables)
do /* For each keypart and each way to read it */ do /* For each keypart and each way to read it */
{ {
if (keyuse->usable) if (keyuse->usable == 1)
{ {
if(!(keyuse->used_tables & ~bound_tables) && if(!(keyuse->used_tables & ~bound_tables) &&
!(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL)) !(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL))
...@@ -516,7 +441,9 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, ...@@ -516,7 +441,9 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table,
{ {
if (!(uses[i].dependency_parts & ~bound_parts)) if (!(uses[i].dependency_parts & ~bound_parts))
{ {
table_map old= bound_parts;
bound_parts|= key_part_map(1) << uses[i].keyuse->keypart; bound_parts|= key_part_map(1) << uses[i].keyuse->keypart;
if (old != bound_parts)
n_bounded++; n_bounded++;
} }
if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts)) if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts))
...@@ -527,6 +454,42 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table, ...@@ -527,6 +454,42 @@ extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table,
return FALSE; return FALSE;
} }
/*
Mark one table or the whole join nest as eliminated.
*/
static void mark_as_eliminated(JOIN *join, TABLE_LIST *tbl)
{
TABLE *table;
/*
NOTE: there are TABLE_LIST object that have
tbl->table!= NULL && tbl->nested_join!=NULL and
tbl->table == tbl->nested_join->join_list->element(..)->table
*/
if (tbl->nested_join)
{
TABLE_LIST *child;
List_iterator<TABLE_LIST> it(tbl->nested_join->join_list);
while ((child= it++))
mark_as_eliminated(join, child);
}
else if ((table= tbl->table))
{
JOIN_TAB *tab= tbl->table->reginfo.join_tab;
if (!(join->const_table_map & tab->table->map))
{
DBUG_PRINT("info", ("Eliminated table %s", table->alias));
tab->type= JT_CONST;
join->eliminated_tables |= table->map;
join->const_table_map|= table->map;
set_position(join, join->const_tables++, tab, (KEYUSE*)0);
}
}
if (tbl->on_expr)
tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL);
}
/** /**
@} (end of group Table_Elimination) @} (end of group Table_Elimination)
*/ */
......
...@@ -2653,12 +2653,13 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, ...@@ -2653,12 +2653,13 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds,
~outer_join, join->select_lex, &sargables)) ~outer_join, join->select_lex, &sargables))
goto error; goto error;
/* Read tables with 0 or 1 rows (system tables) */
join->const_table_map= 0; join->const_table_map= 0;
join->const_tables= const_count;
eliminate_tables(join);
const_count= join->const_tables;
found_const_table_map= join->const_table_map;
eliminate_tables(join, &const_count, &found_const_table_map); /* Read tables with 0 or 1 rows (system tables) */
join->const_table_map= found_const_table_map;
for (POSITION *p_pos=join->positions, *p_end=p_pos+const_count; for (POSITION *p_pos=join->positions, *p_end=p_pos+const_count;
p_pos < p_end ; p_pos < p_end ;
p_pos++) p_pos++)
...@@ -2761,7 +2762,7 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, ...@@ -2761,7 +2762,7 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds,
{ {
start_keyuse=keyuse; start_keyuse=keyuse;
key=keyuse->key; key=keyuse->key;
if (keyuse->usable) if (keyuse->usable == 1)
s->keys.set_bit(key); // QQ: remove this ? s->keys.set_bit(key); // QQ: remove this ?
refs=0; refs=0;
...@@ -2769,7 +2770,7 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, ...@@ -2769,7 +2770,7 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds,
eq_part.clear_all(); eq_part.clear_all();
do do
{ {
if (keyuse->usable && keyuse->val->type() != Item::NULL_ITEM && if (keyuse->usable==1 && keyuse->val->type() != Item::NULL_ITEM &&
!keyuse->optimize) !keyuse->optimize)
{ {
if (!((~found_const_table_map) & keyuse->used_tables)) if (!((~found_const_table_map) & keyuse->used_tables))
...@@ -3601,7 +3602,12 @@ add_key_part(DYNAMIC_ARRAY *keyuse_array,KEY_FIELD *key_field) ...@@ -3601,7 +3602,12 @@ add_key_part(DYNAMIC_ARRAY *keyuse_array,KEY_FIELD *key_field)
keyuse.optimize= key_field->optimize & KEY_OPTIMIZE_REF_OR_NULL; keyuse.optimize= key_field->optimize & KEY_OPTIMIZE_REF_OR_NULL;
keyuse.null_rejecting= key_field->null_rejecting; keyuse.null_rejecting= key_field->null_rejecting;
keyuse.cond_guard= key_field->cond_guard; keyuse.cond_guard= key_field->cond_guard;
keyuse.usable= key_field->usable; if (!(keyuse.usable= key_field->usable))
{
/* The following will have special meanings: */
keyuse.keypart_map= 0;
keyuse.used_tables= 0;
}
VOID(insert_dynamic(keyuse_array,(uchar*) &keyuse)); VOID(insert_dynamic(keyuse_array,(uchar*) &keyuse));
} }
} }
...@@ -3668,7 +3674,7 @@ add_ft_keys(DYNAMIC_ARRAY *keyuse_array, ...@@ -3668,7 +3674,7 @@ add_ft_keys(DYNAMIC_ARRAY *keyuse_array,
keyuse.used_tables=cond_func->key_item()->used_tables(); keyuse.used_tables=cond_func->key_item()->used_tables();
keyuse.optimize= 0; keyuse.optimize= 0;
keyuse.keypart_map= 0; keyuse.keypart_map= 0;
keyuse.usable= TRUE; keyuse.usable= 1;
VOID(insert_dynamic(keyuse_array,(uchar*) &keyuse)); VOID(insert_dynamic(keyuse_array,(uchar*) &keyuse));
} }
...@@ -3686,7 +3692,7 @@ sort_keyuse(KEYUSE *a,KEYUSE *b) ...@@ -3686,7 +3692,7 @@ sort_keyuse(KEYUSE *a,KEYUSE *b)
// Usable ones go before the unusable // Usable ones go before the unusable
if (a->usable != b->usable) if (a->usable != b->usable)
return (int)a->usable - (int)b->usable; return (int)b->usable - (int)a->usable;
// Place const values before other ones // Place const values before other ones
if ((res= test((a->used_tables & ~OUTER_REF_TABLE_BIT)) - if ((res= test((a->used_tables & ~OUTER_REF_TABLE_BIT)) -
...@@ -3898,7 +3904,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, ...@@ -3898,7 +3904,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab,
found_eq_constant=0; found_eq_constant=0;
for (i=0 ; i < keyuse->elements-1 ; i++,use++) for (i=0 ; i < keyuse->elements-1 ; i++,use++)
{ {
if (use->usable && !use->used_tables && if (use->usable == 1 && !use->used_tables &&
use->optimize != KEY_OPTIMIZE_REF_OR_NULL) use->optimize != KEY_OPTIMIZE_REF_OR_NULL)
use->table->const_key_parts[use->key]|= use->keypart_map; use->table->const_key_parts[use->key]|= use->keypart_map;
if (use->keypart != FT_KEYPART) if (use->keypart != FT_KEYPART)
...@@ -3923,7 +3929,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, ...@@ -3923,7 +3929,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab,
/* Save ptr to first use */ /* Save ptr to first use */
if (!use->table->reginfo.join_tab->keyuse) if (!use->table->reginfo.join_tab->keyuse)
use->table->reginfo.join_tab->keyuse=save_pos; use->table->reginfo.join_tab->keyuse=save_pos;
if (use->usable) if (use->usable == 1)
use->table->reginfo.join_tab->checked_keys.set_bit(use->key); use->table->reginfo.join_tab->checked_keys.set_bit(use->key);
save_pos++; save_pos++;
} }
...@@ -3954,7 +3960,7 @@ static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array) ...@@ -3954,7 +3960,7 @@ static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array)
To avoid bad matches, we don't make ref_table_rows less than 100. To avoid bad matches, we don't make ref_table_rows less than 100.
*/ */
keyuse->ref_table_rows= ~(ha_rows) 0; // If no ref keyuse->ref_table_rows= ~(ha_rows) 0; // If no ref
if (keyuse->usable && keyuse->used_tables & if (keyuse->usable == 1 && keyuse->used_tables &
(map= (keyuse->used_tables & ~join->const_table_map & (map= (keyuse->used_tables & ~join->const_table_map &
~OUTER_REF_TABLE_BIT))) ~OUTER_REF_TABLE_BIT)))
{ {
...@@ -4146,7 +4152,7 @@ best_access_path(JOIN *join, ...@@ -4146,7 +4152,7 @@ best_access_path(JOIN *join,
if 1. expression doesn't refer to forward tables if 1. expression doesn't refer to forward tables
2. we won't get two ref-or-null's 2. we won't get two ref-or-null's
*/ */
if (keyuse->usable && if (keyuse->usable == 1&&
!(remaining_tables & keyuse->used_tables) && !(remaining_tables & keyuse->used_tables) &&
!(ref_or_null_part && (keyuse->optimize & !(ref_or_null_part && (keyuse->optimize &
KEY_OPTIMIZE_REF_OR_NULL))) KEY_OPTIMIZE_REF_OR_NULL)))
...@@ -5601,7 +5607,7 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse, ...@@ -5601,7 +5607,7 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse,
*/ */
do do
{ {
if (!(~used_tables & keyuse->used_tables)) if (!(~used_tables & keyuse->used_tables) && keyuse->usable == 1)
{ {
if (keyparts == keyuse->keypart && if (keyparts == keyuse->keypart &&
!(found_part_ref_or_null & keyuse->optimize)) !(found_part_ref_or_null & keyuse->optimize))
...@@ -5652,7 +5658,7 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse, ...@@ -5652,7 +5658,7 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse,
for (i=0 ; i < keyparts ; keyuse++,i++) for (i=0 ; i < keyparts ; keyuse++,i++)
{ {
while (keyuse->keypart != i || ((~used_tables) & keyuse->used_tables) || while (keyuse->keypart != i || ((~used_tables) & keyuse->used_tables) ||
!keyuse->usable) !(keyuse->usable == 1))
{ {
keyuse++; /* Skip other parts */ keyuse++; /* Skip other parts */
} }
...@@ -8985,6 +8991,20 @@ static void restore_prev_nj_state(JOIN_TAB *last) ...@@ -8985,6 +8991,20 @@ static void restore_prev_nj_state(JOIN_TAB *last)
JOIN *join= last->join; JOIN *join= last->join;
while (last_emb) while (last_emb)
{ {
/*
psergey-elim: (nevermind)
new_prefix= cur_prefix & ~last;
if (!(new_prefix & cur_table_map)) // removed last inner table
{
join->cur_embedding_map&= ~last_emb->nested_join->nj_map;
}
else (current)
{
// Won't hurt doing it all the time:
join->cur_embedding_map |= ...;
}
else
*/
if (!(--last_emb->nested_join->counter)) if (!(--last_emb->nested_join->counter))
join->cur_embedding_map&= ~last_emb->nested_join->nj_map; join->cur_embedding_map&= ~last_emb->nested_join->nj_map;
else if (last_emb->nested_join->n_tables-1 == else if (last_emb->nested_join->n_tables-1 ==
...@@ -16685,8 +16705,8 @@ static void print_join(THD *thd, ...@@ -16685,8 +16705,8 @@ static void print_join(THD *thd,
DBUG_ASSERT(tables->elements >= 1); DBUG_ASSERT(tables->elements >= 1);
/* /*
Assert that the first table in the list isn't eliminated (if it was we Assert that the first table in the list isn't eliminated. This comes from
would have skipped the entire join nest) the fact that the first table can't be inner table of an outer join.
*/ */
DBUG_ASSERT(!eliminated_tables || DBUG_ASSERT(!eliminated_tables ||
!((*table)->table && ((*table)->table->map & eliminated_tables) || !((*table)->table && ((*table)->table->map & eliminated_tables) ||
......
...@@ -57,14 +57,14 @@ typedef struct keyuse_t { ...@@ -57,14 +57,14 @@ typedef struct keyuse_t {
*/ */
bool *cond_guard; bool *cond_guard;
/* /*
TRUE <=> This keyuse can be used to construct key access. 1 <=> This keyuse can be used to construct key access.
FALSE <=> Otherwise. Currently unusable KEYUSEs represent equalities 0 <=> Otherwise. Currently unusable KEYUSEs represent equalities
where one table column refers to another one, like this: where one table column refers to another one, like this:
t.keyXpartA=func(t.keyXpartB) t.keyXpartA=func(t.keyXpartB)
This equality cannot be used for index access but is useful This equality cannot be used for index access but is useful
for table elimination. for table elimination.
*/ */
bool usable; int usable;
} KEYUSE; } KEYUSE;
class store_key; class store_key;
...@@ -299,7 +299,12 @@ public: ...@@ -299,7 +299,12 @@ public:
fetching data from a cursor fetching data from a cursor
*/ */
bool resume_nested_loop; bool resume_nested_loop;
table_map const_table_map,found_const_table_map; table_map const_table_map;
/*
Constant tables for which we have found a row (as opposed to those for
which we didn't).
*/
table_map found_const_table_map;
/* Tables removed by table elimination. Set to 0 before the elimination. */ /* Tables removed by table elimination. Set to 0 before the elimination. */
table_map eliminated_tables; table_map eliminated_tables;
...@@ -548,6 +553,10 @@ public: ...@@ -548,6 +553,10 @@ public:
return (unit == &thd->lex->unit && (unit->fake_select_lex == 0 || return (unit == &thd->lex->unit && (unit->fake_select_lex == 0 ||
select_lex == unit->fake_select_lex)); select_lex == unit->fake_select_lex));
} }
inline table_map all_tables_map()
{
return (table_map(1) << tables) - 1;
}
private: private:
bool make_simple_join(JOIN *join, TABLE *tmp_table); bool make_simple_join(JOIN *join, TABLE *tmp_table);
}; };
...@@ -755,6 +764,5 @@ inline bool optimizer_flag(THD *thd, uint flag) ...@@ -755,6 +764,5 @@ inline bool optimizer_flag(THD *thd, uint flag)
return (thd->variables.optimizer_switch & flag); return (thd->variables.optimizer_switch & flag);
} }
void eliminate_tables(JOIN *join, uint *const_tbl_count, void eliminate_tables(JOIN *join);
table_map *const_tables);
...@@ -1616,7 +1616,10 @@ public: ...@@ -1616,7 +1616,10 @@ public:
typedef struct st_nested_join typedef struct st_nested_join
{ {
List<TABLE_LIST> join_list; /* list of elements in the nested join */ List<TABLE_LIST> join_list; /* list of elements in the nested join */
table_map used_tables; /* bitmap of tables in the nested join */ /*
Bitmap of tables within this nested join (including those embedded within
its children). Eliminated tables are still in the bitmap */
table_map used_tables;
table_map not_null_tables; /* tables that rejects nulls */ table_map not_null_tables; /* tables that rejects nulls */
struct st_join_table *first_nested;/* the first nested table in the plan */ struct st_join_table *first_nested;/* the first nested table in the plan */
/* /*
...@@ -1625,6 +1628,8 @@ typedef struct st_nested_join ...@@ -1625,6 +1628,8 @@ typedef struct st_nested_join
2. check_interleaving_with_nj/restore_prev_nj_state (these are called 2. check_interleaving_with_nj/restore_prev_nj_state (these are called
by the join optimizer. by the join optimizer.
Before each use the counters are zeroed by reset_nj_counters. Before each use the counters are zeroed by reset_nj_counters.
Meaning, in both cases: number of base tables within this nested join and
its children. Eliminated tables are not counted.
*/ */
uint counter; uint counter;
/* Tables left after elimination */ /* Tables left after elimination */
......
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