Commit 4102605f authored by Sergey Petrunia's avatar Sergey Petrunia

MWL#17: Table elimination

- Moved table elimination code to sql/opt_table_elimination.cc
- Added comments 

.bzrignore:
  MWL#17: Table elimination
  - Moved table elimination code to sql/opt_table_elimination.cc
libmysqld/Makefile.am:
  MWL#17: Table elimination
  - Moved table elimination code to sql/opt_table_elimination.cc
sql/CMakeLists.txt:
  MWL#17: Table elimination
  - Moved table elimination code to sql/opt_table_elimination.cc
sql/Makefile.am:
  MWL#17: Table elimination
  - Moved table elimination code to sql/opt_table_elimination.cc
parent defbdce7
...@@ -1905,3 +1905,4 @@ sql/share/swedish ...@@ -1905,3 +1905,4 @@ sql/share/swedish
sql/share/ukrainian sql/share/ukrainian
libmysqld/examples/mysqltest.cc libmysqld/examples/mysqltest.cc
extra/libevent/event-config.h extra/libevent/event-config.h
libmysqld/opt_table_elimination.cc
...@@ -76,7 +76,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \ ...@@ -76,7 +76,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \
rpl_filter.cc sql_partition.cc sql_builtin.cc sql_plugin.cc \ rpl_filter.cc sql_partition.cc sql_builtin.cc sql_plugin.cc \
sql_tablespace.cc \ sql_tablespace.cc \
rpl_injector.cc my_user.c partition_info.cc \ rpl_injector.cc my_user.c partition_info.cc \
sql_servers.cc event_parse_data.cc sql_servers.cc event_parse_data.cc opt_table_elimination.cc
libmysqld_int_a_SOURCES= $(libmysqld_sources) libmysqld_int_a_SOURCES= $(libmysqld_sources)
nodist_libmysqld_int_a_SOURCES= $(libmysqlsources) $(sqlsources) nodist_libmysqld_int_a_SOURCES= $(libmysqlsources) $(sqlsources)
......
...@@ -73,7 +73,7 @@ ADD_EXECUTABLE(mysqld ...@@ -73,7 +73,7 @@ ADD_EXECUTABLE(mysqld
partition_info.cc rpl_utility.cc rpl_injector.cc sql_locale.cc partition_info.cc rpl_utility.cc rpl_injector.cc sql_locale.cc
rpl_rli.cc rpl_mi.cc sql_servers.cc rpl_rli.cc rpl_mi.cc sql_servers.cc
sql_connect.cc scheduler.cc sql_connect.cc scheduler.cc
sql_profile.cc event_parse_data.cc sql_profile.cc event_parse_data.cc opt_table_elimination.cc
${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc ${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc
${PROJECT_SOURCE_DIR}/sql/sql_yacc.h ${PROJECT_SOURCE_DIR}/sql/sql_yacc.h
${PROJECT_SOURCE_DIR}/include/mysqld_error.h ${PROJECT_SOURCE_DIR}/include/mysqld_error.h
......
...@@ -121,7 +121,8 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ ...@@ -121,7 +121,8 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
event_queue.cc event_db_repository.cc events.cc \ event_queue.cc event_db_repository.cc events.cc \
sql_plugin.cc sql_binlog.cc \ sql_plugin.cc sql_binlog.cc \
sql_builtin.cc sql_tablespace.cc partition_info.cc \ sql_builtin.cc sql_tablespace.cc partition_info.cc \
sql_servers.cc event_parse_data.cc sql_servers.cc event_parse_data.cc \
opt_table_elimination.cc
nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c
......
...@@ -1915,17 +1915,22 @@ void Item_field::reset_field(Field *f) ...@@ -1915,17 +1915,22 @@ void Item_field::reset_field(Field *f)
name= (char*) f->field_name; name= (char*) f->field_name;
} }
bool Item_field::check_column_usage_processor(uchar *arg) bool Item_field::check_column_usage_processor(uchar *arg)
{ {
Field_processor_info* info=(Field_processor_info*)arg; Field_processor_info* info=(Field_processor_info*)arg;
/* It is ok if this is a column of an allowed table: */
if (used_tables() & ~info->allowed_tables) if (used_tables() & ~info->allowed_tables)
return FALSE; return FALSE;
if (field->table == info->table) if (field->table == info->table)
{ {
/* It is not ok to use columns that are not part of the key of interest: */
if (!(field->part_of_key.is_set(info->keyno))) if (!(field->part_of_key.is_set(info->keyno)))
return TRUE; return TRUE;
/* Find which key part we're using and mark it in needed_key_parts */
KEY *key= &field->table->key_info[info->keyno]; KEY *key= &field->table->key_info[info->keyno];
for (uint part= 0; part < key->key_parts; part++) for (uint part= 0; part < key->key_parts; part++)
{ {
...@@ -1935,10 +1940,17 @@ bool Item_field::check_column_usage_processor(uchar *arg) ...@@ -1935,10 +1940,17 @@ bool Item_field::check_column_usage_processor(uchar *arg)
break; break;
} }
} }
return FALSE;
} }
return FALSE;
/*
We get here when this refers to a table that's neither the table of
interest, nor one of the allowed tables.
*/
return TRUE;
} }
const char *Item_ident::full_name() const const char *Item_ident::full_name() const
{ {
char *tmp; char *tmp;
......
...@@ -731,7 +731,11 @@ public: ...@@ -731,7 +731,11 @@ public:
virtual bool val_bool_result() { return val_bool(); } virtual bool val_bool_result() { return val_bool(); }
virtual bool is_null_result() { return is_null(); } virtual bool is_null_result() { return is_null(); }
/* bit map of tables used by item */ /*
Bitmap of tables used by item
(note: if you need to check dependencies on individual columns, check out
check_column_usage_processor)
*/
virtual table_map used_tables() const { return (table_map) 0L; } virtual table_map used_tables() const { return (table_map) 0L; }
/* /*
Return table map of tables that can't be NULL tables (tables that are Return table map of tables that can't be NULL tables (tables that are
...@@ -1013,7 +1017,7 @@ public: ...@@ -1013,7 +1017,7 @@ public:
bool eq_by_collation(Item *item, bool binary_cmp, CHARSET_INFO *cs); bool eq_by_collation(Item *item, bool binary_cmp, CHARSET_INFO *cs);
}; };
/* Data for Item::check_column_usage_processor */
typedef struct typedef struct
{ {
table_map allowed_tables; table_map allowed_tables;
...@@ -1022,6 +1026,7 @@ typedef struct ...@@ -1022,6 +1026,7 @@ typedef struct
uint needed_key_parts; uint needed_key_parts;
} Field_processor_info; } Field_processor_info;
class sp_head; class sp_head;
......
...@@ -250,7 +250,7 @@ bool Item_subselect::walk(Item_processor processor, bool walk_subquery, ...@@ -250,7 +250,7 @@ bool Item_subselect::walk(Item_processor processor, bool walk_subquery,
if (lex->having && (lex->having)->walk(processor, walk_subquery, if (lex->having && (lex->having)->walk(processor, walk_subquery,
argument)) argument))
return 1; return 1;
/* TODO: why doesn't this walk the OUTER JOINs' ON expressions */ /* TODO: why does this walk WHERE/HAVING but not ON expressions of outer joins? */
while ((item=li++)) while ((item=li++))
{ {
......
...@@ -351,9 +351,10 @@ public: ...@@ -351,9 +351,10 @@ public:
Return bitmap of tables that are needed to evaluate the item. Return bitmap of tables that are needed to evaluate the item.
The implementation takes into account the used strategy: items resolved The implementation takes into account the used strategy: items resolved
at optimization phase report 0. at optimization phase will report 0.
Items that depend on the number of rows only, e.g. COUNT(*) will report Items that depend on the number of join output records, but not columns
zero, but will still false from const_item(). of any particular table (like COUNT(*)) will report 0 from used_tables(),
but will still return false from const_item().
*/ */
table_map used_tables() const { return used_tables_cache; } table_map used_tables() const { return used_tables_cache; }
void update_used_tables (); void update_used_tables ();
......
/**
@file
@brief
Table Elimination Module
@defgroup Table_Elimination Table Elimination Module
@{
*/
#ifdef USE_PRAGMA_IMPLEMENTATION
#pragma implementation // gcc: Class implementation
#endif
#include "mysql_priv.h"
#include "sql_select.h"
/*
OVERVIEW
The module has one entry point - eliminate_tables() function, which one
needs to call (once) sometime after update_ref_and_keys() but before the
join optimization.
eliminate_tables() operates over the JOIN structures. Logically, it
removes the right sides of outer join nests. Physically, it changes the
following members:
* Eliminated tables are marked as constant and moved to the front of the
join order.
* In addition to this, they are recorded in JOIN::eliminated_tables bitmap.
* All join nests have their NESTED_JOIN::n_tables updated to discount
the eliminated tables
* Items that became disused because they were in the ON expression of an
eliminated outer join are notified by means of the Item tree walk which
calls Item::mark_as_eliminated_processor for every item
- At the moment the only Item that cares is Item_subselect with its
Item_subselect::eliminated flag which is used by EXPLAIN code to
check if the subquery should be shown in EXPLAIN.
Table elimination is intended to be done on every PS re-execution.
*/
static int
eliminate_tables_for_join_list(JOIN *join, List<TABLE_LIST> *join_list,
table_map used_tables_elsewhere,
uint *const_tbl_count, table_map *const_tables);
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
extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table,
KEYUSE *key_start, KEYUSE *key_end,
uint n_keyuses, table_map bound_parts);
/*
Perform table elimination
SYNOPSIS
eliminate_tables()
join Join to work on
const_tbl_count INOUT Number of constant tables (this includes
eliminated tables)
const_tables INOUT Bitmap of constant tables
DESCRIPTION
TODO fix comment
SELECT * FROM t1 LEFT JOIN
(t2 JOIN t3) ON t3.primary_key=t1.col AND
t4.primary_key= t2.col
CRITERIA FOR REMOVING ONE OJ NEST
we can't rely on sole presense of eq_refs. Because if we do, we'll miss
things like this:
SELECT * FROM flights LEFT JOIN
(pax as S1 JOIN pax as S2 ON S2.id=S1.spouse AND s1.id=s2.spouse)
(no-polygamy schema/query but there can be many couples on the flight)
..
REMOVAL PROCESS
We can remove an inner side of an outer join if it there is a warranty
that it will produce not more than one record:
... t1 LEFT JOIN t2 ON (t2.unique_key = expr) ...
For nested outer joins:
- The process naturally occurs bottom-up (in order to remove an
outer-join we need to analyze its contents)
- If we failed to remove an outer join nest, it makes no sense to
try removing its ancestors, as the
ot LEFT JOIN it ON cond
pair may possibly produce two records (one record via match and
another one as access-method record).
Q: If we haven't removed an OUTER JOIN, does it make sense to attempt
removing its ancestors?
A: No as the innermost outer join will produce two records => no ancestor
outer join nest will be able to provide the max_fanout==1 guarantee.
*/
void eliminate_tables(JOIN *join, uint *const_tbl_count,
table_map *const_tables)
{
Item *item;
table_map used_tables;
DBUG_ENTER("eliminate_tables");
DBUG_ASSERT(join->eliminated_tables == 0);
/* MWL#17 is only about outer join elimination, so: */
if (!join->outer_join)
DBUG_VOID_RETURN;
/* Find the tables that are referred to from WHERE/HAVING */
used_tables= (join->conds? join->conds->used_tables() : 0) |
(join->having? join->having->used_tables() : 0);
/* Add tables referred to from the select list */
List_iterator<Item> it(join->fields_list);
while ((item= it++))
used_tables |= item->used_tables();
/* Add tables referred to from ORDER BY and GROUP BY lists */
ORDER *all_lists[]= { join->order, join->group_list};
for (int i=0; i < 2; i++)
{
for (ORDER *cur_list= all_lists[i]; cur_list; cur_list= cur_list->next)
used_tables |= (*(cur_list->item))->used_tables();
}
THD* thd= join->thd;
if (join->select_lex == &thd->lex->select_lex)
{
/* Multi-table UPDATE and DELETE: don't eliminate the tables we modify: */
used_tables |= thd->table_map_for_update;
/* Multi-table UPDATE: don't eliminate tables referred from SET statement */
if (thd->lex->sql_command == SQLCOM_UPDATE_MULTI)
{
List_iterator<Item> it2(thd->lex->value_list);
while ((item= it2++))
used_tables |= item->used_tables();
}
}
if (((1 << join->tables) - 1) & ~used_tables)
{
/* There are some time tables that we probably could eliminate */
eliminate_tables_for_join_list(join, join->join_list, used_tables,
const_tbl_count, const_tables);
}
DBUG_VOID_RETURN;
}
/*
Now on to traversal. There can be a situation like this:
FROM t1
LEFT JOIN t2 ON cond(t1,t2)
LEFT JOIN t3 ON cond(..., possibly-t2) // <--(*)
LEFT JOIN t4 ON cond(..., possibly-t2)
Besides that, simplify_joins() may have created back references, so when
we're e.g. looking at outer join (*) we need to look both forward and
backward to check if there are any references in preceding/following
outer joins'
TODO would it create only following-sibling references or
preceding-sibling as well?
And if not, should we rely on that?
*/
static int
eliminate_tables_for_join_list(JOIN *join, List<TABLE_LIST> *join_list,
table_map used_tables_elsewhere,
uint *const_tbl_count, table_map *const_tables)
{
List_iterator<TABLE_LIST> it(*join_list);
table_map used_tables_on_right[MAX_TABLES]; // todo change to alloca
table_map used_tables_on_left;
TABLE_LIST *tbl;
int i, n_tables;
int eliminated=0;
/* Collect the reverse-bitmap-array */
for (i=0; (tbl= it++); i++)
{
used_tables_on_right[i]= 0;
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];
it.rewind();
/* Walk through tables and join nests and see if we can eliminate them */
used_tables_on_left= 0;
i= 1;
while ((tbl= it++))
{
table_map tables_used_outside= used_tables_on_left |
used_tables_on_right[i] |
used_tables_elsewhere;
table_map cur_tables= 0;
if (tbl->nested_join)
{
DBUG_ASSERT(tbl->on_expr);
/*
There can be cases where table removal is applicable for tables
within the outer join but not for the outer join itself. Ask to
remove the children first.
TODO: NoHopelessEliminationAttempts: the below call can return
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;
break;
}
}
if (one_match)
{
it2.rewind();
while ((inner= it2++))
{
mark_table_as_eliminated(join, inner->table, const_tbl_count,
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
{
eliminated += eliminated_in_children;
}
}
}
else if (tbl->on_expr)
{
cur_tables= tbl->on_expr->used_tables();
/* Check and remove */
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,
const_tables);
tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL);
eliminated += 1;
}
}
/* Update bitmap of tables we've seen on the left */
i++;
used_tables_on_left |= cur_tables;
}
return eliminated;
}
/*
Mark table as eliminated:
- Mark it as constant table
- Move it to the front of join order
- Record it in join->eliminated_tables
*/
static
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;
join->eliminated_tables |= table->map;
*const_tables |= table->map;
join->const_table_map|= table->map;
set_position(join, (*const_tbl_count)++, tab, (KEYUSE*)0);
}
}
/*
Check the table will produce at most one matching record
SYNOPSIS
table_has_one_match()
table The [base] table being checked
bound_tables Tables that should be considered bound.
DESCRIPTION
Check if the given table will produce at most one matching record for
each record combination of tables in bound_tables.
RETURN
TRUE Yes, at most one match
FALSE No
*/
static bool table_has_one_match(TABLE *table, table_map bound_tables)
{
KEYUSE *keyuse= table->reginfo.join_tab->keyuse;
if (keyuse)
{
/*
Walk through all of the KEYUSE elements and
- locate unique keys
- check if we have eq_ref access for them
TODO any other reqs?
loops are constructed like in best_access_path
*/
while (keyuse->table == table)
{
uint key= keyuse->key;
key_part_map bound_parts=0;
uint n_unusable=0;
bool ft_key= test(keyuse->keypart == FT_KEYPART);
KEY *keyinfo= table->key_info + key;
KEYUSE *key_start = keyuse;
do /* For each keypart and each way to read it */
{
if (keyuse->usable)
{
if(!(keyuse->used_tables & ~bound_tables) &&
!(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL))
{
bound_parts |= keyuse->keypart_map;
}
}
else
n_unusable++;
keyuse++;
} while (keyuse->table == table && keyuse->key == key);
if (ft_key || ((keyinfo->flags & (HA_NOSAME | HA_NULL_PART_KEY))
!= HA_NOSAME))
{
continue;
}
if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts) ||
extra_keyuses_bind_all_keyparts(bound_tables, table, key_start,
keyuse, n_unusable, bound_parts))
{
return TRUE;
}
}
}
return FALSE;
}
typedef struct st_keyuse_w_needed_reg
{
KEYUSE *keyuse;
key_part_map dependency_parts;
} Keyuse_w_needed_reg;
/*
SYNOPSIS
extra_keyuses_bind_all_keyparts()
bound_tables Tables which can be considered constants
table Table we're examining
key_start Start of KEYUSE array with elements describing the key
of interest
key_end End of the array + 1
n_keyuses Number
bound_parts Key parts whose values are known to be bound.
DESCRIPTION
Check if unusable KEYUSE elements cause all parts of key to be bound. An
unusable keyuse element makes a keypart bound when it
represents the following:
keyXpartY=func(bound_columns, preceding_tables)
RETURN
TRUE Yes, at most one match
FALSE No
*/
static bool
extra_keyuses_bind_all_keyparts(table_map bound_tables, TABLE *table,
KEYUSE *key_start, KEYUSE *key_end,
uint n_keyuses, table_map bound_parts)
{
if (n_keyuses && bound_parts)
{
KEY *keyinfo= table->key_info + key_start->key;
Keyuse_w_needed_reg *uses;
if (!(uses= (Keyuse_w_needed_reg*)my_alloca(sizeof(Keyuse_w_needed_reg)*
n_keyuses)))
return FALSE;
uint n_uses=0;
/* First, collect an array<keyuse, key_parts_it_depends_on>*/
for (KEYUSE *k= key_start; k!=key_end; k++)
{
if (!k->usable && !(k->used_tables & ~bound_tables))
{
Field_processor_info fp= {bound_tables, table, k->key, 0};
if (!k->val->walk(&Item::check_column_usage_processor, FALSE,
(uchar*)&fp))
{
uses[n_uses].keyuse= k;
uses[n_uses].dependency_parts= fp.needed_key_parts;
n_uses++;
}
}
}
/* Now compute transitive closure */
uint n_bounded;
do
{
n_bounded= 0;
for (uint i=0; i< n_uses; i++)
{
/* needed_parts is covered by what is already bound*/
if (!(uses[i].dependency_parts & ~bound_parts))
{
bound_parts|= key_part_map(1) << uses[i].keyuse->keypart;
n_bounded++;
}
if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts))
return TRUE;
}
} while (n_bounded != 0);
}
return FALSE;
}
/**
@} (end of group Table_Elimination)
*/
...@@ -42,11 +42,6 @@ ...@@ -42,11 +42,6 @@
#define TMP_ENGINE_HTON myisam_hton #define TMP_ENGINE_HTON myisam_hton
#endif #endif
#define FT_KEYPART (MAX_REF_PARTS+10)
/* Values in optimize */
#define KEY_OPTIMIZE_EXISTS 1
#define KEY_OPTIMIZE_REF_OR_NULL 2
const char *join_type_str[]={ "UNKNOWN","system","const","eq_ref","ref", const char *join_type_str[]={ "UNKNOWN","system","const","eq_ref","ref",
"MAYBE_REF","ALL","range","index","fulltext", "MAYBE_REF","ALL","range","index","fulltext",
"ref_or_null","unique_subquery","index_subquery", "ref_or_null","unique_subquery","index_subquery",
...@@ -65,7 +60,6 @@ static bool update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse, ...@@ -65,7 +60,6 @@ static bool update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,
table_map table_map, SELECT_LEX *select_lex, table_map table_map, SELECT_LEX *select_lex,
st_sargable_param **sargables); st_sargable_param **sargables);
static int sort_keyuse(KEYUSE *a,KEYUSE *b); static int sort_keyuse(KEYUSE *a,KEYUSE *b);
static void set_position(JOIN *join,uint index,JOIN_TAB *table,KEYUSE *key);
static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse, static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse,
table_map used_tables); table_map used_tables);
static bool choose_plan(JOIN *join,table_map join_tables); static bool choose_plan(JOIN *join,table_map join_tables);
...@@ -2386,10 +2380,13 @@ mysql_select(THD *thd, Item ***rref_pointer_array, ...@@ -2386,10 +2380,13 @@ mysql_select(THD *thd, Item ***rref_pointer_array,
} }
else else
{ {
// psergey{ /*
When in EXPLAIN, delay deleting the joins so that they are still
available when we're producing EXPLAIN EXTENDED warning text.
*/
if (select_options & SELECT_DESCRIBE) if (select_options & SELECT_DESCRIBE)
free_join= 0; free_join= 0;
// }psergey
if (!(join= new JOIN(thd, fields, select_options, result))) if (!(join= new JOIN(thd, fields, select_options, result)))
DBUG_RETURN(TRUE); DBUG_RETURN(TRUE);
thd_proc_info(thd, "init"); thd_proc_info(thd, "init");
...@@ -2477,383 +2474,6 @@ static ha_rows get_quick_record_count(THD *thd, SQL_SELECT *select, ...@@ -2477,383 +2474,6 @@ static ha_rows get_quick_record_count(THD *thd, SQL_SELECT *select,
DBUG_RETURN(HA_POS_ERROR); /* This shouldn't happend */ DBUG_RETURN(HA_POS_ERROR); /* This shouldn't happend */
} }
/********************************************************************
* Table elimination code starts
********************************************************************/
typedef struct st_keyuse_w_needed_reg
{
KEYUSE *first;
key_part_map second;
} Keyuse_w_needed_reg;
static
bool has_eq_ref_access_candidate(TABLE *table, table_map can_refer_to_these)
{
KEYUSE *keyuse= table->reginfo.join_tab->keyuse;
if (keyuse)
{
/*
walk through all of the KEYUSE elements and
- locate unique keys
- check if we have eq_ref access for them
TODO any other reqs?
loops are constructed like in best_access_path
*/
while (keyuse->table == table)
{
uint key= keyuse->key;
key_part_map bound_parts=0;
uint n_unusable=0;
bool ft_key= test(keyuse->keypart == FT_KEYPART);
KEY *keyinfo= table->key_info + key;
KEYUSE *key_start = keyuse;
do /* For each keypart and each way to read it */
{
if (keyuse->usable)
{
if(!(keyuse->used_tables & ~can_refer_to_these) &&
!(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL))
{
bound_parts |= keyuse->keypart_map;
}
}
else
n_unusable++;
keyuse++;
} while (keyuse->table == table && keyuse->key == key);
if (ft_key || ((keyinfo->flags & (HA_NOSAME | HA_NULL_PART_KEY))
!= HA_NOSAME))
{
continue;
}
if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts))
return TRUE;
/*
Ok, usable keyuse elements didn't help us. Try making use of
unusable KEYUSEs (psergey-todo: sane comments:)
*/
if (n_unusable && bound_parts)
{
/*
Check if unusable KEYUSE elements cause all parts of key to be
bound. An unusable keyuse element makes a key part bound when it
represents the following:
keyXpartY=func(bound_columns, preceding_tables)
.
*/
Keyuse_w_needed_reg *uses;
if (!(uses= (Keyuse_w_needed_reg*)my_alloca(sizeof(Keyuse_w_needed_reg)*n_unusable)))
return FALSE;
uint n_uses=0;
for (KEYUSE *k= key_start; k!=keyuse; k++)
{
if (!k->usable && !(k->used_tables & ~can_refer_to_these))
{
//Walk k->val and check which key parts it depends on.
Field_processor_info fp= {can_refer_to_these, table, k->key, 0};
if (!k->val->walk(&Item::check_column_usage_processor, FALSE,
(uchar*)&fp))
{
uses[n_uses].first= k;
uses[n_uses].second= fp.needed_key_parts;
n_uses++;
}
}
}
/* Now compute transitive closure */
uint n_bounded;
do
{
n_bounded= 0;
for (uint i=0; i< n_uses; i++)
{
/* needed_parts is covered by what is already bound*/
if (!(uses[i].second & ~bound_parts))
{
bound_parts|= key_part_map(1) << uses[i].first->keypart;
n_bounded++;
}
if (bound_parts == PREV_BITS(key_part_map, keyinfo->key_parts))
return TRUE;
}
} while (n_bounded != 0);
}
}
}
return FALSE;
}
static 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;
join->eliminated_tables |= table->map;
*const_tables |= table->map;
join->const_table_map|= table->map;
set_position(join, (*const_tbl_count)++, tab, (KEYUSE*)0);
}
}
/*
Now on to traversal. There can be a situation like this:
FROM t1
LEFT JOIN t2 ON cond(t1,t2)
LEFT JOIN t3 ON cond(..., possibly-t2) // <--(*)
LEFT JOIN t4 ON cond(..., possibly-t2)
Besides that, simplify_joins() may have created back references, so when
we're e.g. looking at outer join (*) we need to look both forward and
backward to check if there are any references in preceding/following
outer joins'
TODO would it create only following-sibling references or
preceding-sibling as well?
And if not, should we rely on that?
*/
int
eliminate_tables_for_join_list(JOIN *join, List<TABLE_LIST> *join_list,
table_map used_tables_elsewhere,
uint *const_tbl_count, table_map *const_tables)
{
List_iterator<TABLE_LIST> it(*join_list);
table_map used_tables_on_right[MAX_TABLES]; // todo change to alloca
table_map used_tables_on_left;
TABLE_LIST *tbl;
int i, n_tables;
int eliminated=0;
/* Collect the reverse-bitmap-array */
for (i=0; (tbl= it++); i++)
{
used_tables_on_right[i]= 0;
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];
it.rewind();
/* Walk through tables and join nests and see if we can eliminate them */
used_tables_on_left= 0;
i= 1;
while ((tbl= it++))
{
table_map tables_used_outside= used_tables_on_left |
used_tables_on_right[i] |
used_tables_elsewhere;
table_map cur_tables= 0;
if (tbl->nested_join)
{
DBUG_ASSERT(tbl->on_expr);
/*
There can be cases where table removal is applicable for tables
within the outer join but not for the outer join itself. Ask to
remove the children first.
TODO: NoHopelessEliminationAttempts: the below call can return
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
!has_eq_ref_access_candidate(inner->table,
~tbl->nested_join->used_tables))
{
one_match= FALSE;
break;
}
}
if (one_match)
{
it2.rewind();
while ((inner= it2++))
{
mark_table_as_eliminated(join, inner->table, const_tbl_count,
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
{
eliminated += eliminated_in_children;
}
}
}
else if (tbl->on_expr)
{
cur_tables= tbl->on_expr->used_tables();
/* Check and remove */
if (!(tbl->table->map & tables_used_outside) &&
has_eq_ref_access_candidate(tbl->table, (table_map)-1))
{
mark_table_as_eliminated(join, tbl->table, const_tbl_count,
const_tables);
tbl->on_expr->walk(&Item::mark_as_eliminated_processor, FALSE, NULL);
eliminated += 1;
}
}
/* Update bitmap of tables we've seen on the left */
i++;
used_tables_on_left |= cur_tables;
}
return eliminated;
}
/*
Perform table elimination based on outer join
SELECT * FROM t1 LEFT JOIN
(t2 JOIN t3) ON t3.primary_key=t1.col AND
t4.primary_key= t2.col
CRITERIA FOR REMOVING ONE OJ NEST
we can't rely on sole presense of eq_refs. Because if we do, we'll miss
things like this:
SELECT * FROM flights LEFT JOIN
(pax as S1 JOIN pax as S2 ON S2.id=S1.spouse AND s1.id=s2.spouse)
(no-polygamy schema/query but there can be many couples on the flight)
..
REMOVAL PROCESS
We can remove an inner side of an outer join if it there is a warranty
that it will produce not more than one record:
... t1 LEFT JOIN t2 ON (t2.unique_key = expr) ...
For nested outer joins:
- The process naturally occurs bottom-up (in order to remove an
outer-join we need to analyze its contents)
- If we failed to remove an outer join nest, it makes no sense to
try removing its ancestors, as the
ot LEFT JOIN it ON cond
pair may possibly produce two records (one record via match and
another one as access-method record).
Q: If we haven't removed an OUTER JOIN, does it make sense to attempt
removing its ancestors?
A: No as the innermost outer join will produce two records => no ancestor
outer join nest will be able to provide the max_fanout==1 guarantee.
psergey-todo: .
*/
static void eliminate_tables(JOIN *join, uint *const_tbl_count, table_map *const_tables)
{
Item *item;
table_map used_tables;
DBUG_ENTER("eliminate_tables");
DBUG_ASSERT(join->eliminated_tables == 0);
/* MWL#17 is only about outer join elimination, so: */
if (!join->outer_join)
DBUG_VOID_RETURN;
/* Find the tables that are referred to from WHERE/HAVING */
used_tables= (join->conds? join->conds->used_tables() : 0) |
(join->having? join->having->used_tables() : 0);
/* Add tables referred to from the select list */
List_iterator<Item> it(join->fields_list);
while ((item= it++))
used_tables |= item->used_tables();
/* Add tables referred to from ORDER BY and GROUP BY lists */
ORDER *all_lists[]= { join->order, join->group_list};
for (int i=0; i < 2; i++)
{
for (ORDER *cur_list= all_lists[i]; cur_list; cur_list= cur_list->next)
used_tables |= (*(cur_list->item))->used_tables();
}
THD* thd= join->thd;
if (join->select_lex == &thd->lex->select_lex)
{
/* Multi-table UPDATE and DELETE: don't eliminate the tables we modify: */
used_tables |= thd->table_map_for_update;
/* Multi-table UPDATE: don't eliminate tables referred from SET statement */
if (thd->lex->sql_command == SQLCOM_UPDATE_MULTI)
{
List_iterator<Item> it2(thd->lex->value_list);
while ((item= it2++))
used_tables |= item->used_tables();
}
}
if (((1 << join->tables) - 1) & ~used_tables)
{
/* There are some time tables that we probably could eliminate */
eliminate_tables_for_join_list(join, join->join_list, used_tables,
const_tbl_count, const_tables);
}
DBUG_VOID_RETURN;
}
/********************************************************************
* Table elimination code ends
********************************************************************/
/* /*
This structure is used to collect info on potentially sargable This structure is used to collect info on potentially sargable
...@@ -3219,10 +2839,6 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, ...@@ -3219,10 +2839,6 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds,
} }
} }
//psergey-todo: table elimination
//eliminate_tables(join, &const_count, &found_const_table_map);
//:psergey-todo
/* Calc how many (possible) matched records in each table */ /* Calc how many (possible) matched records in each table */
for (s=stat ; s < stat_end ; s++) for (s=stat ; s < stat_end ; s++)
...@@ -3354,7 +2970,7 @@ typedef struct key_field_t { ...@@ -3354,7 +2970,7 @@ typedef struct key_field_t {
*/ */
bool null_rejecting; bool null_rejecting;
bool *cond_guard; /* See KEYUSE::cond_guard */ bool *cond_guard; /* See KEYUSE::cond_guard */
bool usable; bool usable; /* See KEYUSE::usable */
} KEY_FIELD; } KEY_FIELD;
...@@ -4428,8 +4044,7 @@ add_group_and_distinct_keys(JOIN *join, JOIN_TAB *join_tab) ...@@ -4428,8 +4044,7 @@ add_group_and_distinct_keys(JOIN *join, JOIN_TAB *join_tab)
/** Save const tables first as used tables. */ /** Save const tables first as used tables. */
static void void set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key)
set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key)
{ {
join->positions[idx].table= table; join->positions[idx].table= table;
join->positions[idx].key=key; join->positions[idx].key=key;
...@@ -17021,7 +16636,6 @@ bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result) ...@@ -17021,7 +16636,6 @@ bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result)
unit->fake_select_lex->options|= SELECT_DESCRIBE; unit->fake_select_lex->options|= SELECT_DESCRIBE;
if (!(res= unit->prepare(thd, result, SELECT_NO_UNLOCK | SELECT_DESCRIBE))) if (!(res= unit->prepare(thd, result, SELECT_NO_UNLOCK | SELECT_DESCRIBE)))
res= unit->exec(); res= unit->exec();
//psergey-move: res|= unit->cleanup();
} }
else else
{ {
......
...@@ -28,6 +28,11 @@ ...@@ -28,6 +28,11 @@
#include "procedure.h" #include "procedure.h"
#include <myisam.h> #include <myisam.h>
#define FT_KEYPART (MAX_REF_PARTS+10)
/* Values in optimize */
#define KEY_OPTIMIZE_EXISTS 1
#define KEY_OPTIMIZE_REF_OR_NULL 2
typedef struct keyuse_t { typedef struct keyuse_t {
TABLE *table; TABLE *table;
Item *val; /**< or value if no field */ Item *val; /**< or value if no field */
...@@ -51,6 +56,14 @@ typedef struct keyuse_t { ...@@ -51,6 +56,14 @@ typedef struct keyuse_t {
NULL - Otherwise (the source equality can't be turned off) NULL - Otherwise (the source equality can't be turned off)
*/ */
bool *cond_guard; bool *cond_guard;
/*
TRUE <=> This keyuse can be used to construct key access.
FALSE <=> Otherwise. Currently unusable KEYUSEs represent equalities
where one table column refers to another one, like this:
t.keyXpartA=func(t.keyXpartB)
This equality cannot be used for index access but is useful
for table elimination.
*/
bool usable; bool usable;
} KEYUSE; } KEYUSE;
...@@ -734,9 +747,13 @@ bool error_if_full_join(JOIN *join); ...@@ -734,9 +747,13 @@ bool error_if_full_join(JOIN *join);
int report_error(TABLE *table, int error); int report_error(TABLE *table, int error);
int safe_index_read(JOIN_TAB *tab); int safe_index_read(JOIN_TAB *tab);
COND *remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value); COND *remove_eq_conds(THD *thd, COND *cond, Item::cond_result *cond_value);
void set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key);
inline bool optimizer_flag(THD *thd, uint flag) 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,
table_map *const_tables);
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