/* Copyright (c) 2000, 2016, Oracle and/or its affiliates.
Copyright ( c ) 2009 , 2024 , MariaDB Corporation .
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; version 2 of the License .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
/**
@ file
@ brief
mysql_select and join optimization
@ defgroup Query_Optimizer Query Optimizer
@ {
*/
#include "mariadb.h"
#include "sql_priv.h"
#include "unireg.h"
#include "sql_select.h"
#include "sql_cache.h" // query_cache_*
#include "sql_table.h" // primary_key_name
#include "probes_mysql.h"
#include "key.h" // key_copy, key_cmp, key_cmp_if_same
#include "lock.h" // mysql_unlock_some_tables,
// mysql_unlock_read_tables
#include "sql_show.h" // append_identifier
#include "sql_base.h" // setup_wild, setup_fields, fill_record
#include "sql_parse.h" // check_stack_overrun
#include "sql_partition.h" // make_used_partitions_str
#include "sql_test.h" // print_where, print_keyuse_array,
// print_sjm, print_plan, TEST_join
#include "records.h" // init_read_record, end_read_record
#include "filesort.h" // filesort_free_buffers
#include "filesort_utils.h" // get_qsort_sort_cost
#include "sql_union.h" // mysql_union
#include "opt_subselect.h"
#include "opt_context_store_replay.h"
#include "sql_derived.h"
#include "sql_statistics.h"
#include "sql_cte.h"
#include "sql_window.h"
#include "tztime.h"
#include "debug_sync.h" // DEBUG_SYNC
#include <m_ctype.h>
#include <my_bit.h>
#include <hash.h>
#include <ft_global.h>
#include "sys_vars_shared.h"
#include "sp_head.h"
#include "sp_rcontext.h"
#include "rowid_filter.h"
#include "select_handler.h"
#include "my_json_writer.h"
#include "opt_trace.h"
#include "derived_handler.h"
#include "create_tmp_table.h"
#include "optimizer_defaults.h"
#include "derived_handler.h"
#include "opt_hints.h"
#include "opt_group_by_cardinality.h"
/*
A key part number that means we ' re using a fulltext scan .
In order not to confuse it with regular equalities , we need to pick
a number that ' s greater than MAX_REF_PARTS .
Hash Join code stores field - > field_index in KEYUSE : : keypart , so the
number needs to be bigger than MAX_FIELDS , also .
CAUTION : sql_test . cc has its own definition of FT_KEYPART .
*/
#define FT_KEYPART (MAX_FIELDS+10 )
/*
We assume that when we do hash join , only 10 % rows in the hash will
match the current found row .
*/
#define HASH_FANOUT 0 .1
/*
The following is used to check that A < = B , but with some margin as the
calculation is done slightly differently ( mathematically correct , but
double calculations are not exact ) .
This is only used when comparing read rows and output rows , which
means that we can assume that both values are > = 0 and B cannot be notable
smaller than A .
*/
#define crash_if_first_double_is_bigger(A,B) DBUG_ASSERT(((A) == 0 .0 && (B) == 0 .0 ) || (A)/(B) < 1 .0000001 )
#define double_to_rows(A) ((A) >= ((double )HA_ROWS_MAX) ? HA_ROWS_MAX : (ha_rows) (A))
#define double_to_ulonglong(A) ((A) >= ((double )ULONGLONG_MAX) ? ULONGLONG_MAX : (ulonglong) (A))
/* Used to ensure that costs are calculate the same way */
inline bool compare_cost(double a, double b)
{
DBUG_ASSERT(a >= 0 .0 && b >= 0 .0 );
return (a >= b - b/10000000 .0 && a <= b+b/10000000 .0 );
}
inline double safe_filtered(double a, double b)
{
return b != 0 ? a/b*100 .0 : 0 .0 ;
}
const char *join_type_str[]={ "UNKNOWN" ,"system" ,"const" ,"eq_ref" ,"ref" ,
"MAYBE_REF" ,"ALL" ,"range" ,"index" ,"fulltext" ,
"ref_or_null" ,"unique_subquery" ,"index_subquery" ,
"index_merge" , "hash_ALL" , "hash_range" ,
"hash_index" , "hash_index_merge" };
static const Lex_ident_column group_key= "group_key" _Lex_ident_column;
static const Lex_ident_column distinct_key= "distinct_key" _Lex_ident_column;
struct st_sargable_param;
static bool make_join_statistics(JOIN *join, List<TABLE_LIST> &leaves,
DYNAMIC_ARRAY *keyuse);
static bool update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,
JOIN_TAB *join_tab,
uint tables, COND *conds,
table_map table_map, SELECT_LEX *select_lex,
SARGABLE_PARAM **sargables);
static int sort_keyuse(const void *a, const void *b);
static bool are_tables_local(JOIN_TAB *jtab, table_map used_tables);
static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse,
bool allow_full_scan, table_map used_tables);
static bool get_quick_record_count(THD *thd, SQL_SELECT *select,
TABLE *table,
const key_map *keys,ha_rows limit,
ha_rows *quick_count);
static void optimize_straight_join(JOIN *join, table_map join_tables);
static bool greedy_search(JOIN *join, table_map remaining_tables,
uint depth, uint use_cond_selectivity);
enum enum_best_search {
SEARCH_ABORT= -2 ,
SEARCH_ERROR= -1 ,
SEARCH_OK= 0 ,
SEARCH_FOUND_EDGE=1
};
static enum_best_search
best_extension_by_limited_search(JOIN *join,
table_map remaining_tables,
uint idx, double record_count,
double read_time, uint depth,
uint use_cond_selectivity,
table_map *processed_eq_ref_tables);
static uint determine_search_depth(JOIN* join);
C_MODE_START
static int join_tab_cmp(void *dummy, const void * ptr1, const void * ptr2);
static int join_tab_cmp_straight(void *dummy, const void * ptr1, const void * ptr2);
static int join_tab_cmp_embedded_first(void *emb, const void * ptr1, const void *ptr2);
C_MODE_END
static uint cache_record_length(JOIN *join,uint index);
static store_key *get_store_key(THD *thd,
KEYUSE *keyuse, table_map used_tables,
KEY_PART_INFO *key_part, uchar *key_buff,
uint maybe_null);
static bool make_outerjoin_info(JOIN *join);
static Item*
make_cond_after_sjm(THD *thd, Item *root_cond, Item *cond, table_map tables,
table_map sjm_tables, bool inside_or_clause);
static bool make_join_select(JOIN *join,SQL_SELECT *select,COND *item);
static void revise_cache_usage(JOIN_TAB *join_tab);
static bool make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after);
static bool only_eq_ref_tables(JOIN *join, ORDER *order, table_map tables);
static void update_depend_map(JOIN *join);
static void update_depend_map_for_order(JOIN *join, ORDER *order);
static ORDER *remove_const(JOIN *join,ORDER *first_order,COND *cond,
bool change_list, bool *simple_order);
static int return_zero_rows(JOIN *join, select_result *res,
List<TABLE_LIST> *tables,
List<Item> *fields, bool send_row,
ulonglong select_options, const char *info,
Item *having, List<Item> *all_fields);
static COND *build_equal_items(JOIN *join, COND *cond,
COND_EQUAL *inherited,
List<TABLE_LIST> *join_list,
bool ignore_on_conds,
COND_EQUAL **cond_equal_ref,
bool link_equal_fields= FALSE );
static COND* substitute_for_best_equal_field(THD *thd, JOIN_TAB *context_tab,
COND *cond,
COND_EQUAL *cond_equal,
void *table_join_idx,
bool do_substitution);
static COND *simplify_joins(JOIN *join, List<TABLE_LIST> *join_list,
COND *conds, bool top, bool in_sj);
static bool check_interleaving_with_nj(JOIN_TAB *next);
static void restore_prev_nj_state(JOIN_TAB *last);
static uint reset_nj_counters(JOIN *join, List<TABLE_LIST> *join_list);
static uint build_bitmap_for_nested_joins(List<TABLE_LIST> *join_list,
uint first_unused);
static COND *optimize_cond(JOIN *join, COND *conds,
List<TABLE_LIST> *join_list,
bool ignore_on_conds,
Item::cond_result *cond_value,
COND_EQUAL **cond_equal,
int flags= 0 );
bool const_expression_in_where(COND *conds,Item *item, Item **comp_item);
static int do_select(JOIN *join, Procedure *procedure);
static enum_nested_loop_state evaluate_join_record(JOIN *, JOIN_TAB *, int );
static enum_nested_loop_state
evaluate_null_complemented_join_record(JOIN *join, JOIN_TAB *join_tab);
static enum_nested_loop_state
end_send(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static enum_nested_loop_state
end_write(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static enum_nested_loop_state
end_update(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static enum_nested_loop_state
end_unique_update(JOIN *join, JOIN_TAB *join_tab, bool end_of_records);
static int join_read_const_table(THD *thd, JOIN_TAB *tab, POSITION *pos);
static int join_read_system(JOIN_TAB *tab);
static int join_read_const(JOIN_TAB *tab);
static int join_read_key(JOIN_TAB *tab);
static void join_read_key_unlock_row(st_join_table *tab);
static void join_const_unlock_row(JOIN_TAB *tab);
static int join_read_always_key(JOIN_TAB *tab);
static int join_read_last_key(JOIN_TAB *tab);
static int join_no_more_records(READ_RECORD *info);
static int join_read_next(READ_RECORD *info);
static int join_hlindex_read_next(READ_RECORD *info);
static int join_init_quick_read_record(JOIN_TAB *tab);
static quick_select_return test_if_quick_select(JOIN_TAB *tab);
static int test_if_use_dynamic_range_scan(JOIN_TAB *join_tab);
static int join_read_first(JOIN_TAB *tab);
static int join_read_next_same(READ_RECORD *info);
static int join_read_last(JOIN_TAB *tab);
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);
int join_read_always_key_or_null(JOIN_TAB *tab);
int join_read_next_same_or_null(READ_RECORD *info);
COND *make_cond_for_table(THD *thd, Item *cond,table_map table,
table_map used_table,
int join_tab_idx_arg,
bool exclude_expensive_cond,
bool retain_ref_cond);
COND *make_cond_for_table_from_pred(THD *thd, Item *root_cond,
Item *cond,
table_map tables,
table_map used_table,
int join_tab_idx_arg,
bool exclude_expensive_cond,
bool retain_ref_cond,
bool is_top_and_level);
static Item* part_of_refkey(TABLE *form,Field *field);
static bool test_if_cheaper_ordering(bool in_join_optimizer,
const JOIN_TAB *tab,
ORDER *order, TABLE *table,
key_map usable_keys, int key,
ha_rows select_limit,
int *new_key, int *new_key_direction,
ha_rows *new_select_limit,
double *new_read_time,
uint *new_used_key_parts= NULL,
uint *saved_best_key_parts= NULL);
static int test_if_order_by_key(JOIN *, ORDER *, TABLE *, uint, uint *);
static bool test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,
ha_rows select_limit, bool no_changes,
const key_map *map,
bool *fatal_error);
static bool list_contains_unique_index(TABLE *table,
bool (*find_func) (Field *, void *), void *data);
static bool find_field_in_item_list (Field *field, void *data);
static bool find_field_in_order_list (Field *field, void *data);
int create_sort_index(THD *thd, JOIN *join, JOIN_TAB *tab, Filesort *fsort);
static int remove_dup_with_compare(THD *thd, TABLE *entry, Field **field,
SORT_FIELD *sortorder, ulong keylength,
Item *having);
static int remove_dup_with_hash_index(THD *thd,TABLE *table,
uint field_count, Field **first_field,
SORT_FIELD *sortorder,
ulong key_length,Item *having);
static bool cmp_buffer_with_ref(THD *thd, TABLE *table, TABLE_REF *tab_ref);
static bool setup_new_fields(THD *thd, List<Item> &fields,
List<Item> &all_fields, ORDER *new_order);
static ORDER *create_distinct_group(THD *thd, Ref_ptr_array ref_pointer_array,
ORDER *order, List<Item> &fields,
List<Item> &all_fields,
bool *all_order_by_fields_used);
static bool test_if_subpart(ORDER *group_by, ORDER *order_by);
static TABLE *get_sort_by_table(ORDER *a,ORDER *b,List<TABLE_LIST> &tables,
table_map const_tables);
static void calc_group_buffer(JOIN *join, ORDER *group);
static bool make_group_fields(JOIN *main_join, JOIN *curr_join);
static bool alloc_group_fields(JOIN *join, ORDER *group);
static bool alloc_order_fields(JOIN *join, ORDER *group,
uint max_number_of_elements);
// Create list for using with tempory table
static bool change_to_use_tmp_fields(THD *thd, Ref_ptr_array ref_pointer_array,
List<Item> &new_list1,
List<Item> &new_list2,
uint elements, List<Item> &items);
// Create list for using with tempory table
static bool change_refs_to_tmp_fields(THD *thd, Ref_ptr_array ref_pointer_array,
List<Item> &new_list1,
List<Item> &new_list2,
uint elements, List<Item> &items);
static void init_tmptable_sum_functions(Item_sum **func);
static void update_tmptable_sum_func(Item_sum **func,TABLE *tmp_table);
static void copy_sum_funcs(Item_sum **func_ptr, Item_sum **end);
static bool add_ref_to_table_cond(THD *thd, JOIN_TAB *join_tab);
static bool setup_sum_funcs(THD *thd, Item_sum **func_ptr);
static bool init_sum_functions(Item_sum **func, Item_sum **end);
static bool update_sum_func(Item_sum **func);
static void select_describe(JOIN *join, bool need_tmp_table,bool need_order,
bool distinct, const char *message=NullS);
static void add_group_and_distinct_keys(JOIN *join, JOIN_TAB *join_tab);
static uint make_join_orderinfo(JOIN *join);
static bool generate_derived_keys(DYNAMIC_ARRAY *keyuse_array);
Item_equal *find_item_equal(COND_EQUAL *cond_equal, Field *field,
bool *inherited_fl);
JOIN_TAB *first_depth_first_tab(JOIN* join);
JOIN_TAB *next_depth_first_tab(JOIN* join, JOIN_TAB* tab);
static JOIN_TAB *next_breadth_first_tab(JOIN_TAB *first_top_tab,
uint n_top_tabs_count, JOIN_TAB *tab);
static bool find_order_in_list(THD *, Ref_ptr_array, TABLE_LIST *, ORDER *,
List<Item> &, List<Item> &, bool , bool , bool );
static double table_after_join_selectivity(JOIN *join, uint idx, JOIN_TAB *s,
table_map rem_tables,
double *records_out);
void set_postjoin_aggr_write_func(JOIN_TAB *tab);
static Item **get_sargable_cond(JOIN *join, TABLE *table);
bool is_eq_cond_injected_for_split_opt(Item_func_eq *eq_item);
void print_list_item(String *str, List_item *list,
enum_query_type query_type);
static
bool build_notnull_conds_for_range_scans(JOIN *join, COND *cond,
table_map allowed);
static
void build_notnull_conds_for_inner_nest_of_outer_join(JOIN *join,
TABLE_LIST *nest_tbl);
static void fix_items_after_optimize(THD *thd, SELECT_LEX *select_lex);
static void optimize_rownum(THD *thd, SELECT_LEX_UNIT *unit, Item *cond);
static bool process_direct_rownum_comparison(THD *thd, SELECT_LEX_UNIT *unit,
Item *cond);
static double prev_record_reads(const POSITION *positions, uint idx,
table_map found_ref, double record_count,
double *same_keys);
static
bool join_limit_shortcut_is_applicable(const JOIN *join);
POSITION *join_limit_shortcut_finalize_plan(JOIN *join, double *cost);
static bool find_indexes_matching_order(JOIN *, TABLE *, ORDER *, key_map *);
static void init_join_plan_search_state(JOIN *join);
#ifndef DBUG_OFF
/*
SHOW EXPLAIN testing : wait for , and serve n_calls APC requests .
*/
void dbug_serve_apcs(THD *thd, int n_calls)
{
const char *save_proc_info= thd->proc_info;
/* Busy-wait for n_calls APC requests to arrive and be processed */
int n_apcs= thd->apc_target.n_calls_processed + n_calls;
while (thd->apc_target.n_calls_processed < n_apcs)
{
/* This is so that mysqltest knows we're ready to serve requests: */
thd_proc_info(thd, "show_explain_trap" );
my_sleep(30000 );
thd_proc_info(thd, save_proc_info);
if (unlikely(thd->check_killed(1 )))
break ;
}
}
/*
Debugging : check if @ name = value , comparing as integer
Intended usage :
DBUG_EXECUTE_IF ( " show_explain_probe_2 " ,
if ( dbug_user_var_equals_int ( thd , " select_id " , select_id ) )
dbug_serve_apcs ( thd , 1 ) ;
) ;
*/
bool dbug_user_var_equals_int(THD *thd, const char *name, int value)
{
user_var_entry *var;
LEX_CSTRING varname= { name, strlen(name)};
if ((var= get_variable(&thd->user_vars, &varname, FALSE )))
{
bool null_value;
longlong var_value= var->val_int(&null_value);
if (!null_value && var_value == value)
return TRUE ;
}
return FALSE ;
}
/*
Debugging : check if @ name = value , comparing as string
Intended usage :
DBUG_EXECUTE_IF ( " log_slow_statement_end " ,
if ( dbug_user_var_equals_str ( thd , " show_explain_probe_query " ,
thd - > query ( ) ) )
dbug_serve_apcs ( thd , 1 ) ;
) ;
*/
bool dbug_user_var_equals_str(THD *thd, const char *name, const char * value)
{
user_var_entry *var;
LEX_CSTRING varname= {name, strlen(name)};
if ((var= get_variable(&thd->user_vars, &varname, FALSE )))
{
bool null_value;
String str;
auto var_value= var->val_str(&null_value, &str, 10 )->ptr();
if (!null_value && !strncmp(var_value, value, strlen(value)))
return TRUE ;
}
return FALSE ;
}
#endif /* DBUG_OFF */
JOIN_TAB_RANGE *JOIN_TAB_RANGE::create(THD *thd, uint count)
{
JOIN_TAB *jt;
JOIN_TAB_RANGE *jt_range;
if (!(jt= thd->alloc<JOIN_TAB>(count)) ||
!(jt_range= new JOIN_TAB_RANGE))
return nullptr;
jt_range->start= jt;
jt_range->end= jt + count;
return jt_range;
}
/*
Intialize POSITION structure .
*/
POSITION::POSITION()
{
table= 0 ;
records_read= cond_selectivity= read_time= records_out= records_init= 0 .0 ;
prefix_record_count= 0 .0 ;
key= 0 ;
forced_index= 0 ;
use_join_buffer= 0 ;
firstmatch_with_join_buf= false ;
sj_strategy= SJ_OPT_NONE;
n_sj_tables= 0 ;
spl_plan= 0 ;
range_rowid_filter_info= 0 ;
ref_depend_map= dups_producing_tables= 0 ;
inner_tables_handled_with_other_sjs= 0 ;
type= JT_UNKNOWN;
key_dependent= 0 ;
dups_weedout_picker.set_empty();
firstmatch_picker.set_empty();
loosescan_picker.set_empty();
sjmat_picker.set_empty();
}
void JOIN::init(THD *thd_arg, List<Item> &fields_arg,
ulonglong select_options_arg, select_result *result_arg)
{
join_tab= 0 ;
table= 0 ;
table_count= 0 ;
top_join_tab_count= 0 ;
const_tables= 0 ;
const_table_map= found_const_table_map= not_usable_rowid_map= 0 ;
aggr_tables= 0 ;
eliminated_tables= 0 ;
join_list= 0 ;
implicit_grouping= FALSE ;
sort_and_group= 0 ;
first_record= 0 ;
do_send_rows= 1 ;
duplicate_rows= send_records= 0 ;
found_records= accepted_rows= 0 ;
fetch_limit= HA_POS_ERROR;
thd= thd_arg;
sum_funcs= sum_funcs2= 0 ;
procedure= 0 ;
having= tmp_having= having_history= 0 ;
having_is_correlated= false ;
group_list_for_estimates= 0 ;
select_options= select_options_arg;
result= result_arg;
lock= thd_arg->lock;
select_lex= 0 ; //for safety
select_distinct= MY_TEST(select_options & SELECT_DISTINCT);
no_order= 0 ;
simple_order= 0 ;
simple_group= 0 ;
ordered_index_usage= ordered_index_void;
need_distinct= 0 ;
skip_sort_order= 0 ;
with_two_phase_optimization= 0 ;
save_qep= 0 ;
spl_opt_info= 0 ;
ext_keyuses_for_splitting= 0 ;
spl_opt_info= 0 ;
need_tmp= 0 ;
hidden_group_fields= 0 ; /*safety*/
error= 0 ;
select= 0 ;
return_tab= 0 ;
ref_ptrs.reset();
items0.reset();
items1.reset();
items2.reset();
items3.reset();
zero_result_cause= 0 ;
optimization_state= JOIN::NOT_OPTIMIZED;
have_query_plan= QEP_NOT_PRESENT_YET;
initialized= 0 ;
cleaned= 0 ;
cond_equal= 0 ;
having_equal= 0 ;
exec_const_cond= 0 ;
group_optimized_away= 0 ;
no_rows_in_result_called= 0 ;
positions= best_positions= 0 ;
pushdown_query= 0 ;
original_join_tab= 0 ;
explain= NULL;
tmp_table_keep_current_rowid= 0 ;
allowed_top_level_tables= 0 ;
all_fields= fields_arg;
if (&fields_list != &fields_arg) /* Avoid valgrind-warning */
fields_list= fields_arg;
non_agg_fields.empty();
bzero((char *) &keyuse,sizeof (keyuse));
having_value= Item::COND_UNDEF;
tmp_table_param.init();
tmp_table_param.end_write_records= HA_POS_ERROR;
rollup.state= ROLLUP::STATE_NONE;
no_const_tables= FALSE ;
first_select= sub_select;
group_sent= 0 ;
outer_ref_cond= pseudo_bits_cond= NULL;
in_to_exists_where= NULL;
in_to_exists_having= NULL;
emb_sjm_nest= NULL;
sjm_lookup_tables= 0 ;
sjm_scan_tables= 0 ;
is_orig_degenerated= false ;
with_ties_order_count= 0 ;
prepared= false ;
sql_cmd_dml= NULL;
};
static void trace_table_dependencies(THD *thd,
JOIN_TAB *join_tabs, uint table_count)
{
DBUG_ASSERT(thd->trace_started());
Json_writer_object trace_wrapper(thd);
Json_writer_array trace_dep(thd, "table_dependencies" );
for (uint i= 0 ; i < table_count; i++)
{
TABLE_LIST *table_ref= join_tabs[i].tab_list;
Json_writer_object trace_one_table(thd);
trace_one_table.
add_table_name(&join_tabs[i]).
add("row_may_be_null" ,
(bool )table_ref->table->maybe_null);
const table_map map= table_ref->get_map();
DBUG_ASSERT(map < (1 ULL << table_count));
for (uint j= 0 ; j < table_count; j++)
{
if (map & (1 ULL << j))
{
trace_one_table.add("map_bit" , j);
break ;
}
}
Json_writer_array depends_on(thd, "depends_on_map_bits" );
Table_map_iterator it(join_tabs[i].dependent);
uint dep_bit;
while ((dep_bit= it++) != Table_map_iterator::BITMAP_END)
depends_on.add(static_cast <longlong>(dep_bit));
}
}
/**
This handles SELECT with and without UNION .
*/
bool handle_select(THD *thd, LEX *lex, select_result *result,
ulonglong setup_tables_done_option)
{
bool res;
SELECT_LEX *select_lex= lex->first_select_lex();
DBUG_ENTER("handle_select" );
MYSQL_SELECT_START(thd->query());
if (select_lex->master_unit()->is_unit_op() ||
select_lex->master_unit()->fake_select_lex)
res= mysql_union(thd, lex, result, &lex->unit, setup_tables_done_option);
else
{
SELECT_LEX_UNIT *unit= &lex->unit;
unit->set_limit(unit->global_parameters());
/*
' options ' of mysql_select will be set in JOIN , as far as JOIN for
every PS / SP execution new , we will not need reset this flag if
setup_tables_done_option changed for next reexecution
*/
res= mysql_select(thd,
select_lex->table_list.first,
select_lex->item_list,
select_lex->where,
select_lex->order_list.elements +
select_lex->group_list.elements,
select_lex->order_list.first,
select_lex->group_list.first,
select_lex->having,
lex->proc_list.first,
select_lex->options | thd->variables.option_bits |
setup_tables_done_option,
result, unit, select_lex);
}
DBUG_PRINT("info" ,("res: %d is_error(): %d" , res,
thd->is_error()));
res|= thd->is_error();
if (unlikely(res))
result->abort_result_set();
if (unlikely(thd->killed == ABORT_QUERY && !thd->no_errors))
{
/*
If LIMIT ROWS EXAMINED interrupted query execution
continue with normal processing and produce an incomplete query
result .
A warning was generated by killed_for_exceeding_limit_rows ( ) .
*/
thd->reset_killed();
}
/* Deactivate LIMIT ROWS EXAMINED after query execution. */
thd->lex->deactivate_limit_rows_examined();
thd->killed_for_exceeding_limit_rows_warning_given= 0 ;
MYSQL_SELECT_DONE((int ) res, (ulong) thd->limit_found_rows);
DBUG_RETURN(res);
}
/**
Fix fields referenced from inner selects .
@ param thd Thread handle
@ param all_fields List of all fields used in select
@ param select Current select
@ param ref_pointer_array Array of references to Items used in current select
@ param group_list GROUP BY list ( is NULL by default )
@ details
The function serves 3 purposes
- adds fields referenced from inner query blocks to the current select list
- Decides which class to use to reference the items ( Item_ref or
Item_direct_ref )
- fixes references ( Item_ref objects ) to these fields .
If a field isn ' t already on the select list and the ref_pointer_array
is provided then it is added to the all_fields list and the pointer to
it is saved in the ref_pointer_array .
The class to access the outer field is determined by the following rules :
- # . If the outer field isn ' t used under an aggregate function then the
Item_ref class should be used .
- # . If the outer field is used under an aggregate function and this
function is , in turn , aggregated in the query block where the outer
field was resolved or some query nested therein , then the
Item_direct_ref class should be used . Also it should be used if we are
grouping by a subquery that references this outer field .
The resolution is done here and not at the fix_fields ( ) stage as
it can be done only after aggregate functions are fixed and pulled up to
selects where they are to be aggregated .
When the class is chosen it substitutes the original field in the
Item_outer_ref object .
After this we proceed with fixing references ( Item_outer_ref objects ) to
this field from inner subqueries .
@ return Status
@ retval true An error occurred .
@ retval false OK .
*/
bool
fix_inner_refs(THD *thd, List<Item> &all_fields, SELECT_LEX *select,
Ref_ptr_array ref_pointer_array)
{
Item_outer_ref *ref;
/*
Mark the references from the inner_refs_list that are occurred in
the group by expressions . Those references will contain direct
references to the referred fields . The markers are set in
the found_in_group_by field of the references from the list .
*/
List_iterator_fast <Item_outer_ref> ref_it(select->inner_refs_list);
for (ORDER *group= select->join->group_list; group; group= group->next)
{
(*group->item)->walk(&Item::check_inner_refs_processor,
&ref_it, WALK_SUBQUERY);
}
while ((ref= ref_it++))
{
bool direct_ref= false ;
Item *item= ref->outer_ref;
Item **item_ref= ref->ref;
Item_ref *new_ref;
/*
TODO : this field item already might be present in the select list .
In this case instead of adding new field item we could use an
existing one . The change will lead to less operations for copying fields ,
smaller temporary tables and less data passed through filesort .
*/
if (!ref_pointer_array.is_null() && !ref->found_in_select_list)
{
int el= all_fields.elements;
ref_pointer_array[el]= item;
/* Add the field item to the select list of the current select. */
all_fields.push_front(item, thd->mem_root);
/*
If it ' s needed reset each Item_ref item that refers this field with
a new reference taken from ref_pointer_array .
*/
item_ref= &ref_pointer_array[el];
}
if (ref->in_sum_func)
{
Item_sum *sum_func;
if (ref->in_sum_func->nest_level > select->nest_level)
direct_ref= TRUE ;
else
{
for (sum_func= ref->in_sum_func; sum_func &&
sum_func->aggr_level >= select->nest_level;
sum_func= sum_func->in_sum_func)
{
if (sum_func->aggr_level == select->nest_level)
{
direct_ref= TRUE ;
break ;
}
}
}
}
else if (ref->found_in_group_by)
direct_ref= TRUE ;
new_ref= direct_ref ?
new (thd->mem_root) Item_direct_ref(thd, ref->context, item_ref, ref->table_name,
ref->field_name, ref->alias_name_used) :
new (thd->mem_root) Item_ref(thd, ref->context, item_ref, ref->table_name,
ref->field_name, ref->alias_name_used);
if (!new_ref)
return TRUE ;
ref->outer_ref= new_ref;
ref->ref= &ref->outer_ref;
if (ref->fix_fields_if_needed(thd, 0 ))
return TRUE ;
thd->lex->used_tables|= item->used_tables();
thd->lex->current_select->select_list_tables|= item->used_tables();
}
return false ;
}
/**
The following clauses are redundant for subqueries :
DISTINCT
GROUP BY if there are no aggregate functions and no HAVING
clause
Because redundant clauses are removed both from JOIN and
select_lex , the removal is permanent . Thus , it only makes sense to
call this function for normal queries and on first execution of
SP / PS
@ param subq_select_lex select_lex that is part of a subquery
predicate . This object and the associated
join is modified .
*/
static
void remove_redundant_subquery_clauses(st_select_lex *subq_select_lex)
{
DBUG_ENTER("remove_redundant_subquery_clauses" );
Item_subselect *subq_predicate= subq_select_lex->master_unit()->item;
/*
The removal should happen for IN , ALL , ANY and EXISTS subqueries ,
which means all but single row subqueries . Example single row
subqueries :
a ) SELECT * FROM t1 WHERE t1 . a = ( < single row subquery > )
b ) SELECT a , ( < single row subquery ) FROM t1
*/
if (subq_predicate->substype() == Item_subselect::SINGLEROW_SUBS)
DBUG_VOID_RETURN;
/* A subquery that is not single row should be one of IN/ALL/ANY/EXISTS. */
DBUG_ASSERT (subq_predicate->substype() == Item_subselect::EXISTS_SUBS ||
subq_predicate->is_in_predicate());
if (subq_select_lex->options & SELECT_DISTINCT)
{
subq_select_lex->join->select_distinct= false ;
subq_select_lex->options&= ~SELECT_DISTINCT;
DBUG_PRINT("info" , ("DISTINCT removed" ));
}
/*
Remove GROUP BY if there are no aggregate functions and no HAVING
clause
*/
if (subq_select_lex->group_list.elements &&
!subq_select_lex->with_sum_func && !subq_select_lex->join->having)
{
/*
Temporary workaround for MDEV - 28621 : Do not remove GROUP BY expression
if it has any subqueries in it .
*/
bool have_subquery= false ;
for (ORDER *ord= subq_select_lex->group_list.first; ord; ord= ord->next)
{
if ((*ord->item)->with_subquery())
{
have_subquery= true ;
break ;
}
}
if (!have_subquery)
{
for (ORDER *ord= subq_select_lex->group_list.first; ord; ord= ord->next)
{
/*
Do not remove the item if it is used in select list and then referred
from GROUP BY clause by its name or number . Example :
select ( select . . . ) as SUBQ . . . group by SUBQ
Here SUBQ cannot be removed .
*/
if (!ord->in_field_list)
{
/*
Not necessary due to workaround for MDEV - 28621 :
( * ord - > item ) - > walk ( & Item : : eliminate_subselect_processor , FALSE , NULL ) ;
*/
/*
Remove from the JOIN : : all_fields list any reference to the elements
of the eliminated GROUP BY list unless it is ' in_field_list ' .
This is needed in order not to confuse JOIN : : make_aggr_tables_info ( )
when it constructs different structure for execution phase .
*/
List_iterator<Item> li(subq_select_lex->join->all_fields);
Item *item;
while ((item= li++))
{
if (item == *ord->item)
li.remove();
}
}
}
subq_select_lex->join->group_list= NULL;
subq_select_lex->group_list.empty();
DBUG_PRINT("info" , ("GROUP BY removed" ));
}
}
/*
TODO : This would prevent processing queries with ORDER BY . . . LIMIT
therefore we disable this optimization for now .
Remove GROUP BY if there are no aggregate functions and no HAVING
clause
if ( subq_select_lex - > group_list . elements & &
! subq_select_lex - > with_sum_func & & ! subq_select_lex - > join - > having )
{
subq_select_lex - > join - > group_list = NULL ;
subq_select_lex - > group_list . empty ( ) ;
}
*/
DBUG_VOID_RETURN;
}
/**
Function to setup clauses without sum functions .
*/
static inline int
setup_without_group(THD *thd, Ref_ptr_array ref_pointer_array,
TABLE_LIST *tables,
List<TABLE_LIST> &leaves,
List<Item> &fields,
List<Item> &all_fields,
COND **conds,
ORDER *order,
ORDER *group,
List<Window_spec> &win_specs,
List<Item_window_func> &win_funcs,
bool *hidden_group_fields)
{
int res;
enum_parsing_place save_place;
st_select_lex *const select= thd->lex->current_select;
nesting_map save_allow_sum_func= thd->lex->allow_sum_func;
/*
Need to stave the value , so we can turn off only any new non_agg_field_used
additions coming from the WHERE
*/
const bool saved_non_agg_field_used= select->non_agg_field_used();
DBUG_ENTER("setup_without_group" );
thd->lex->allow_sum_func.clear_bit(select->nest_level);
res= setup_conds(thd, tables, leaves, conds, &all_fields);
/* it's not wrong to have non-aggregated columns in a WHERE */
select->set_non_agg_field_used(saved_non_agg_field_used);
thd->lex->allow_sum_func.set_bit(select->nest_level);
save_place= thd->lex->current_select->context_analysis_place;
thd->lex->current_select->context_analysis_place= IN_ORDER_BY;
res= res || setup_order(thd, ref_pointer_array, tables, fields, all_fields,
order);
thd->lex->allow_sum_func.clear_bit(select->nest_level);
thd->lex->current_select->context_analysis_place= IN_GROUP_BY;
res= res || setup_group(thd, ref_pointer_array, tables, fields, all_fields,
group, hidden_group_fields);
thd->lex->current_select->context_analysis_place= save_place;
thd->lex->allow_sum_func.set_bit(select->nest_level);
res= res || setup_windows(thd, ref_pointer_array, tables, fields, all_fields,
win_specs, win_funcs);
thd->lex->allow_sum_func= save_allow_sum_func;
DBUG_RETURN(res);
}
bool vers_select_conds_t::init_from_sysvar(THD *thd)
{
vers_asof_timestamp_t &in= thd->variables.vers_asof_timestamp;
type= (vers_system_time_t) in.type;
delete_history= false ;
start.unit= VERS_TIMESTAMP;
if (type != SYSTEM_TIME_UNSPECIFIED && type != SYSTEM_TIME_ALL)
{
DBUG_ASSERT(type == SYSTEM_TIME_AS_OF);
Datetime dt(in.unix_time, in.second_part, thd->variables.time_zone);
start.item= new (thd->mem_root)
Item_datetime_literal(thd, &dt, TIME_SECOND_PART_DIGITS);
if (!start.item)
return true ;
}
else
start.item= NULL;
end.empty();
return false ;
}
void vers_select_conds_t::print(String *str, enum_query_type query_type) const
{
switch (orig_type) {
case SYSTEM_TIME_UNSPECIFIED:
break ;
case SYSTEM_TIME_AS_OF:
start.print(str, query_type, STRING_WITH_LEN(" FOR SYSTEM_TIME AS OF " ));
break ;
case SYSTEM_TIME_FROM_TO:
start.print(str, query_type, STRING_WITH_LEN(" FOR SYSTEM_TIME FROM " ));
end.print(str, query_type, STRING_WITH_LEN(" TO " ));
break ;
case SYSTEM_TIME_BETWEEN:
start.print(str, query_type, STRING_WITH_LEN(" FOR SYSTEM_TIME BETWEEN " ));
end.print(str, query_type, STRING_WITH_LEN(" AND " ));
break ;
case SYSTEM_TIME_BEFORE:
start.print(str, query_type, STRING_WITH_LEN(" FOR SYSTEM_TIME BEFORE " ));
break ;
case SYSTEM_TIME_HISTORY:
// nothing to add
break ;
case SYSTEM_TIME_ALL:
str->append(STRING_WITH_LEN(" FOR SYSTEM_TIME ALL" ));
break ;
}
}
static
Item* period_get_condition(THD *thd, TABLE_LIST *table, SELECT_LEX *select,
vers_select_conds_t *conds, bool timestamp)
{
DBUG_ASSERT(table);
DBUG_ASSERT(table->table);
#define newx new (thd->mem_root)
TABLE_SHARE *share= table->table->s;
const TABLE_SHARE::period_info_t *period= conds->period;
const LEX_CSTRING &fstart= period->start_field(share)->field_name;
const LEX_CSTRING &fend= period->end_field(share)->field_name;
conds->field_start= newx Item_field(thd, &select->context,
table->db, table->alias,
thd->strmake_lex_cstring(fstart));
conds->field_end= newx Item_field(thd, &select->context,
table->db, table->alias,
thd->strmake_lex_cstring(fend));
Item *cond1= NULL, *cond2= NULL, *cond3= NULL, *curr= NULL;
if (timestamp)
{
MYSQL_TIME max_time;
switch (conds->type)
{
case SYSTEM_TIME_UNSPECIFIED:
case SYSTEM_TIME_HISTORY:
{
thd->variables.time_zone->gmt_sec_to_TIME(&max_time, TIMESTAMP_MAX_VALUE);
max_time.second_part= TIME_MAX_SECOND_PART;
Datetime dt(&max_time);
curr= newx Item_datetime_literal(thd, &dt, TIME_SECOND_PART_DIGITS);
if (conds->type == SYSTEM_TIME_UNSPECIFIED)
cond1= newx Item_func_eq(thd, conds->field_end, curr);
else
cond1= newx Item_func_lt(thd, conds->field_end, curr);
break ;
}
case SYSTEM_TIME_AS_OF:
cond1= newx Item_func_le(thd, conds->field_start, conds->start.item);
cond2= newx Item_func_gt(thd, conds->field_end, conds->start.item);
break ;
case SYSTEM_TIME_FROM_TO:
cond1= newx Item_func_lt(thd, conds->field_start, conds->end.item);
cond2= newx Item_func_gt(thd, conds->field_end, conds->start.item);
cond3= newx Item_func_lt(thd, conds->start.item, conds->end.item);
break ;
case SYSTEM_TIME_BETWEEN:
cond1= newx Item_func_le(thd, conds->field_start, conds->end.item);
cond2= newx Item_func_gt(thd, conds->field_end, conds->start.item);
cond3= newx Item_func_le(thd, conds->start.item, conds->end.item);
break ;
case SYSTEM_TIME_BEFORE:
cond1= newx Item_func_history(thd, conds->field_end);
cond2= newx Item_func_lt(thd, conds->field_end, conds->start.item);
break ;
default :
DBUG_ASSERT(0 );
}
}
else
{
DBUG_ASSERT(table->table->s && table->table->s->db_plugin);
Item *trx_id0= conds->start.item;
Item *trx_id1= conds->end.item;
if (conds->start.item && conds->start.unit == VERS_TIMESTAMP)
{
bool backwards= conds->type != SYSTEM_TIME_AS_OF;
trx_id0= newx Item_func_trt_id(thd, conds->start.item,
TR_table::FLD_TRX_ID, backwards);
}
if (conds->end.item && conds->end.unit == VERS_TIMESTAMP)
{
trx_id1= newx Item_func_trt_id(thd, conds->end.item,
TR_table::FLD_TRX_ID, false );
}
switch (conds->type)
{
case SYSTEM_TIME_UNSPECIFIED:
case SYSTEM_TIME_HISTORY:
curr= newx Item_int(thd, ULONGLONG_MAX);
if (conds->type == SYSTEM_TIME_UNSPECIFIED)
cond1= newx Item_func_eq(thd, conds->field_end, curr);
else
cond1= newx Item_func_lt(thd, conds->field_end, curr);
break ;
DBUG_ASSERT(!conds->start.item);
DBUG_ASSERT(!conds->end.item);
break ;
case SYSTEM_TIME_AS_OF:
cond1= newx Item_func_trt_trx_sees_eq(thd, trx_id0, conds->field_start);
cond2= newx Item_func_trt_trx_sees(thd, conds->field_end, trx_id0);
DBUG_ASSERT(!conds->end.item);
break ;
case SYSTEM_TIME_FROM_TO:
cond1= newx Item_func_trt_trx_sees(thd, trx_id1, conds->field_start);
cond2= newx Item_func_trt_trx_sees_eq(thd, conds->field_end, trx_id0);
cond3= newx Item_func_lt(thd, conds->start.item, conds->end.item);
break ;
case SYSTEM_TIME_BETWEEN:
cond1= newx Item_func_trt_trx_sees_eq(thd, trx_id1, conds->field_start);
cond2= newx Item_func_trt_trx_sees_eq(thd, conds->field_end, trx_id0);
cond3= newx Item_func_le(thd, conds->start.item, conds->end.item);
break ;
case SYSTEM_TIME_BEFORE:
cond1= newx Item_func_history(thd, conds->field_end);
cond2= newx Item_func_trt_trx_sees(thd, trx_id0, conds->field_end);
break ;
default :
DBUG_ASSERT(0 );
}
}
if (cond1)
{
cond1= and_items(thd, cond2, cond1);
cond1= and_items(thd, cond3, cond1);
}
return cond1;
}
static
bool skip_setup_conds(THD *thd)
{
return (!thd->stmt_arena->is_conventional()
&& !thd->stmt_arena->is_stmt_prepare_or_first_sp_execute())
|| thd->lex->is_view_context_analysis();
}
int SELECT_LEX::period_setup_conds(THD *thd, TABLE_LIST *tables)
{
DBUG_ENTER("SELECT_LEX::period_setup_conds" );
const bool update_conds= !skip_setup_conds(thd);
Query_arena backup;
Query_arena *arena= thd->activate_stmt_arena_if_needed(&backup);
DBUG_ASSERT(!tables->next_local && tables->table);
Item *result= NULL;
for (TABLE_LIST *table= tables; table; table= table->next_local)
{
if (!table->table)
continue ;
vers_select_conds_t &conds= table->period_conditions;
if (!table->table->s->period.name.streq_safe(conds.name))
{
my_error(ER_PERIOD_NOT_FOUND, MYF(0 ), conds.name.str);
if (arena)
thd->restore_active_arena(arena, &backup);
DBUG_RETURN(-1 );
}
if (update_conds)
{
conds.period= &table->table->s->period;
result= and_items(thd, result,
period_get_condition(thd, table, this , &conds, true ));
}
}
if (update_conds)
where= and_items(thd, where, result);
if (arena)
thd->restore_active_arena(arena, &backup);
DBUG_RETURN(0 );
}
int SELECT_LEX::vers_setup_conds(THD *thd, TABLE_LIST *tables)
{
DBUG_ENTER("SELECT_LEX::vers_setup_conds" );
const bool update_conds= !skip_setup_conds(thd);
if (!versioned_tables)
{
for (TABLE_LIST *table= tables; table; table= table->next_local)
{
if (table->table && table->table->versioned())
versioned_tables++;
else if (table->vers_conditions.is_set() &&
(table->is_non_derived() || !table->vers_conditions.used))
{
my_error(ER_VERS_NOT_VERSIONED, MYF(0 ), table->alias.str);
DBUG_RETURN(-1 );
}
}
}
if (versioned_tables == 0 )
DBUG_RETURN(0 );
/* For prepared statements we create items on statement arena,
because they must outlive execution phase for multiple executions. */
Query_arena_stmt on_stmt_arena(thd);
// find outer system_time
SELECT_LEX *outer_slex= outer_select();
TABLE_LIST* outer_table= NULL;
if (outer_slex)
{
TABLE_LIST* derived= master_unit()->derived;
// inner SELECT may not be a derived table (derived == NULL)
while (derived && outer_slex && !derived->vers_conditions.is_set())
{
derived= outer_slex->master_unit()->derived;
outer_slex= outer_slex->outer_select();
}
if (derived && outer_slex)
{
DBUG_ASSERT(derived->vers_conditions.is_set());
outer_table= derived;
}
}
bool is_select= false ;
bool use_sysvar= false ;
switch (thd->lex->sql_command)
{
case SQLCOM_SELECT:
use_sysvar= true ;
/* fall through */
case SQLCOM_CREATE_TABLE:
case SQLCOM_INSERT_SELECT:
case SQLCOM_REPLACE_SELECT:
case SQLCOM_DELETE_MULTI:
case SQLCOM_UPDATE_MULTI:
is_select= true ;
default :
break ;
}
for (TABLE_LIST *table= tables; table; table= table->next_local)
{
if (!table->table || table->is_view() || !table->table->versioned())
continue ;
vers_select_conds_t &vers_conditions= table->vers_conditions;
#ifdef WITH_PARTITION_STORAGE_ENGINE
/*
if the history is stored in partitions , then partitions
themselves are not versioned
*/
if (table->partition_names && table->table->part_info->vers_info)
{
/* If the history is stored in partitions, then partitions
themselves are not versioned. */
if (vers_conditions.was_set())
{
my_error(ER_VERS_QUERY_IN_PARTITION, MYF(0 ), table->alias.str);
DBUG_RETURN(-1 );
}
else if (!vers_conditions.is_set())
vers_conditions.set_all();
}
#endif
if (outer_table && !vers_conditions.is_set())
{
// propagate system_time from nearest outer SELECT_LEX
vers_conditions= outer_table->vers_conditions;
outer_table->vers_conditions.used= true ;
}
// propagate system_time from sysvar
if (!vers_conditions.is_set() && use_sysvar)
{
if (vers_conditions.init_from_sysvar(thd))
DBUG_RETURN(-1 );
}
if (vers_conditions.is_set())
{
if (vers_conditions.was_set() &&
table->lock_type >= TL_FIRST_WRITE &&
!vers_conditions.delete_history)
{
my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0 ), table->alias.str);
DBUG_RETURN(-1 );
}
if (vers_conditions.type == SYSTEM_TIME_ALL)
continue ;
}
bool timestamps_only= table->table->versioned(VERS_TIMESTAMP);
bool update_this= update_conds;
if (vers_conditions.is_set() && vers_conditions.type != SYSTEM_TIME_HISTORY)
{
thd->where= THD_WHERE::FOR_SYSTEM_TIME;
/* TODO: do resolve fix_length_and_dec(), fix_fields(). This requires
storing vers_conditions as Item and make some magic related to
vers_system_time_t / VERS_TRX_ID at stage of fix_fields ( )
(this is large refactoring). */
if (vers_conditions.check_units(thd))
DBUG_RETURN(-1 );
if (timestamps_only && (vers_conditions.start.unit == VERS_TRX_ID ||
vers_conditions.end.unit == VERS_TRX_ID))
{
my_error(ER_VERS_ENGINE_UNSUPPORTED, MYF(0 ), table->table_name.str);
DBUG_RETURN(-1 );
}
if (vers_conditions.has_param)
{
/*
PS parameter in history expression requires processing at execution
stage when parameters has values substituted . So at prepare continue
the loop , but at execution enter update_this . The second execution
is skipped on vers_conditions . type = = SYSTEM_TIME_ALL condition .
*/
if (thd->stmt_arena->is_stmt_prepare())
continue ;
DBUG_ASSERT(thd->stmt_arena->is_stmt_execute());
update_this= true ;
}
}
if (update_this)
{
vers_conditions.period = &table->table->s->vers;
Item *cond= period_get_condition(thd, table, this , &vers_conditions,
timestamps_only);
if (is_select)
table->on_expr= and_items(thd, table->on_expr, cond);
else
{
if (join)
{
where= and_items(thd, join->conds, cond);
join->conds= where;
}
else
where= and_items(thd, where, cond);
table->where= and_items(thd, table->where, cond);
if (where && vers_conditions.has_param && vers_conditions.delete_history)
prep_where= where->copy_andor_structure(thd);
}
table->vers_conditions.set_all();
}
} // for (table= tables; ...)
DBUG_RETURN(0 );
}
/*****************************************************************************
Check fields , find best join , do the select and output fields .
mysql_select assumes that all tables are already opened
*****************************************************************************/
/*
Check if we have a field reference . If yes , we have to use
mixed_implicit_grouping .
*/
static bool check_list_for_field(List<Item> *items)
{
List_iterator_fast <Item> select_it(*items);
Item *select_el;
while ((select_el= select_it++))
{
if (select_el->with_field())
return true ;
}
return false ;
}
static bool check_list_for_field(ORDER *order)
{
for (; order; order= order->next)
{
if (order->item[0 ]->with_field())
return true ;
}
return false ;
}
/**
Prepare of whole select ( including sub queries in future ) .
@ todo
Add check of calculation of GROUP functions and fields :
SELECT COUNT ( * ) + table . col1 from table1 ;
@ retval
- 1 on error
@ retval
0 on success
*/
int
JOIN::prepare(TABLE_LIST *tables_init, COND *conds_init, uint og_num,
ORDER *order_init, bool skip_order_by,
ORDER *group_init, Item *having_init,
ORDER *proc_param_init, SELECT_LEX *select_lex_arg,
SELECT_LEX_UNIT *unit_arg)
{
DBUG_ENTER("JOIN::prepare" );
// to prevent double initialization on EXPLAIN
if (optimization_state != JOIN::NOT_OPTIMIZED)
DBUG_RETURN(0 );
conds= conds_init;
order= order_init;
group_list= group_init;
having= having_init;
proc_param= proc_param_init;
tables_list= tables_init;
select_lex= select_lex_arg;
DBUG_PRINT("info" , ("select %p (%u) = JOIN %p" ,
select_lex, select_lex->select_number, this ));
select_lex->join= this ;
join_list= &select_lex->top_join_list;
union_part= unit_arg->is_unit_op();
Json_writer_object trace_wrapper(thd);
Json_writer_object trace_prepare(thd, "join_preparation" );
trace_prepare.add_select_number(select_lex->select_number);
Json_writer_array trace_steps(thd, "steps" );
// simple check that we got usable conds
dbug_print_item(conds);
/* Fix items that requires the join structure to exist */
fix_items_after_optimize(thd, select_lex);
/*
It is hack which force creating EXPLAIN object always on runt - time arena
( because very top JOIN : : prepare executes always with runtime arena , but
constant subquery like ( SELECT ' x ' ) can be called with statement arena
during prepare phase of top SELECT ) .
*/
if (!(thd->lex->context_analysis_only & CONTEXT_ANALYSIS_ONLY_PREPARE))
create_explain_query_if_not_exists(thd->lex, thd->mem_root);
if (select_lex->handle_derived(thd->lex, DT_PREPARE))
DBUG_RETURN(-1 );
thd->lex->current_select->context_analysis_place= NO_MATTER;
thd->lex->current_select->is_item_list_lookup= 1 ;
/*
If we have already executed SELECT , then it have not sense to prevent
its table from update ( see unique_table ( ) )
Affects only materialized derived tables .
*/
/* Check that all tables, fields, conds and order are ok */
if (!(select_options & OPTION_SETUP_TABLES_DONE) &&
setup_tables_and_check_access(thd, &select_lex->context, join_list,
tables_list, select_lex->leaf_tables,
false , SELECT_ACL, SELECT_ACL, false ))
DBUG_RETURN(-1 );
if (thd->lex->opt_hints_global && select_lex->select_number == 1 )
{
thd->lex->opt_hints_global->fix_hint(thd);
/*
There ' s no need to call opt_hints_global - > check_unresolved ( ) ,
this is done for each query block individually
*/
}
if (select_lex->opt_hints_qb)
select_lex->opt_hints_qb->check_unfixed(thd);
/* System Versioning: handle FOR SYSTEM_TIME clause. */
if (select_lex->vers_setup_conds(thd, tables_list) < 0 )
DBUG_RETURN(-1 );
/*
mixed_implicit_grouping will be set to TRUE if the SELECT list
mixes elements with and without grouping , and there is no GROUP BY
clause .
Mixing non - aggregated fields with aggregate functions in the
SELECT list or HAVING is a MySQL extension that is allowed only if
the ONLY_FULL_GROUP_BY sql mode is not set .
*/
mixed_implicit_grouping= false ;
if ((~thd->variables.sql_mode & MODE_ONLY_FULL_GROUP_BY) &&
select_lex->with_sum_func && !group_list)
{
if (check_list_for_field(&fields_list) ||
check_list_for_field(order))
{
List_iterator_fast<TABLE_LIST> li(select_lex->leaf_tables);
mixed_implicit_grouping= true ; // mark for future
while (TABLE_LIST *tbl= li++)
{
/*
If the query uses implicit grouping where the select list
contains both aggregate functions and non - aggregate fields ,
any non - aggregated field may produce a NULL value . Set all
fields of each table as nullable before semantic analysis to
take into account this change of nullability .
Note : this loop doesn ' t touch tables inside merged
semi - joins , because subquery - to - semijoin conversion has not
been done yet . This is intended .
*/
if (tbl->table)
tbl->table->maybe_null= 1 ;
}
}
}
table_count= select_lex->leaf_tables.elements;
uint real_og_num= og_num;
if (skip_order_by &&
select_lex != select_lex->master_unit()->global_parameters())
real_og_num+= select_lex->order_list.elements;
DBUG_ASSERT(select_lex->hidden_bit_fields == 0 );
if (setup_wild(thd, tables_list, fields_list, &all_fields, select_lex, false ))
DBUG_RETURN(-1 );
/*
If the select_lex is immediately contained within a derived table
AND this derived table is a CTE
WITH supplied column names
AND we have the correct number of elements in both lists
( mismatches found in mysql_derived_prepare / rename_columns_of_derived_unit )
THEN NOW is the time to take a copy of these item_names for
later restoration if required .
*/
TABLE_LIST *derived= select_lex->master_unit()->derived;
if (derived &&
derived->with &&
derived->with->column_list.elements &&
(derived->with->column_list.elements == select_lex->item_list.elements))
{
if (select_lex->save_item_list_names(thd))
DBUG_RETURN(-1 );
}
if (thd->lex->current_select->first_cond_optimization)
{
if ( conds && ! thd->lex->current_select->merged_into)
select_lex->select_n_reserved= conds->exists2in_reserved_items();
else
select_lex->select_n_reserved= 0 ;
}
if (select_lex->setup_ref_array(thd, real_og_num))
DBUG_RETURN(-1 );
ref_ptrs= ref_ptr_array_slice(0 );
enum_parsing_place save_place=
thd->lex->current_select->context_analysis_place;
thd->lex->current_select->context_analysis_place= SELECT_LIST;
{
List_iterator_fast<TABLE_LIST> it(select_lex->leaf_tables);
while (TABLE_LIST *tbl= it++)
{
if (tbl->table_function &&
tbl->table_function->setup(thd, tbl, select_lex_arg))
DBUG_RETURN(-1 );
}
}
if (setup_fields(thd, ref_ptrs, fields_list, select_lex->item_list_usage,
&all_fields, &select_lex->pre_fix, 1 ))
DBUG_RETURN(-1 );
thd->lex->current_select->context_analysis_place= save_place;
if (setup_without_group(thd, ref_ptrs, tables_list,
select_lex->leaf_tables, fields_list,
all_fields, &conds, order, group_list,
select_lex->window_specs,
select_lex->window_funcs,
&hidden_group_fields))
DBUG_RETURN(-1 );
/*
Permanently remove redundant parts from the query if
1 ) This is a subquery
2 ) This is the first time this query is optimized ( since the
transformation is permanent
3 ) Not normalizing a view . Removal should take place when a
query involving a view is optimized , not when the view
is created
*/
if (select_lex->master_unit()->item && // 1)
select_lex->first_cond_optimization && // 2)
!thd->lex->is_view_context_analysis()) // 3)
{
remove_redundant_subquery_clauses(select_lex);
}
/* Resolve the ORDER BY that was skipped, then remove it. */
if (skip_order_by && select_lex !=
select_lex->master_unit()->global_parameters())
{
nesting_map save_allow_sum_func= thd->lex->allow_sum_func;
thd->lex->allow_sum_func.set_bit(select_lex->nest_level);
thd->where= 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, ref_ptrs, tables_list, order,
fields_list, all_fields, false , false , false ))
DBUG_RETURN(-1 );
}
thd->lex->allow_sum_func= save_allow_sum_func;
select_lex->order_list.empty();
}
if (having)
{
nesting_map save_allow_sum_func= thd->lex->allow_sum_func;
thd->where= THD_WHERE::HAVING_CLAUSE;
thd->lex->allow_sum_func.set_bit(select_lex_arg->nest_level);
select_lex->having_fix_field= 1 ;
/*
Wrap alone field in HAVING clause in case it will be outer field
of subquery which need persistent pointer on it , but having
could be changed by optimizer
*/
if (having->type() == Item::REF_ITEM &&
((Item_ref *)having)->ref_type() == Item_ref::REF)
wrap_ident(thd, &having);
bool having_fix_rc= having->fix_fields_if_needed_for_bool(thd, &having);
select_lex->having_fix_field= 0 ;
if (unlikely(having_fix_rc || thd->is_error()))
DBUG_RETURN(-1 ); /* purecov: inspected */
thd->lex->allow_sum_func= save_allow_sum_func;
if (having->with_window_func())
{
my_error(ER_WRONG_PLACEMENT_OF_WINDOW_FUNCTION, MYF(0 ));
DBUG_RETURN(-1 );
}
}
/*
After setting up window functions , we may have discovered additional
used tables from the PARTITION BY and ORDER BY list . Update all items
that contain window functions .
*/
if (select_lex->have_window_funcs())
{
List_iterator_fast<Item> it(select_lex->item_list);
Item *item;
while ((item= it++))
{
if (item->with_window_func())
item->update_used_tables();
}
}
With_element *with_elem= select_lex->get_with_element();
if (with_elem &&
select_lex->check_unrestricted_recursive(
thd->variables.only_standard_compliant_cte))
DBUG_RETURN(-1 );
if (!(select_lex->changed_elements & TOUCHED_SEL_COND))
select_lex->check_subqueries_with_recursive_references();
int res= check_and_do_in_subquery_rewrites(this );
select_lex->fix_prepare_information(thd, &conds, &having);
if (res)
DBUG_RETURN(res);
if (order)
{
bool requires_sorting= FALSE ;
/*
WITH TIES forces the results to be sorted , even if it ' s not sanely
sortable .
*/
if (select_lex->limit_params.with_ties)
requires_sorting= true ;
/*
Go through each ORDER BY item and perform the following :
1 . Detect if none of the items contain meaningful data , which means we
can drop the sorting altogether .
2 . Split any columns with aggregation functions or window functions into
their base components and store them as separate fields .
( see split_sum_func ) for more details .
*/
for (ORDER *ord= order; ord; ord= ord->next)
{
Item *item= *ord->item;
/*
Disregard sort order if there ' s only
zero length NOT NULL fields ( e . g . { VAR } CHAR ( 0 ) NOT NULL " ) or
zero length NOT NULL string functions there .
Such tuples don ' t contain any data to sort .
*/
if (!requires_sorting &&
/* Not a zero length NOT NULL field */
((item->type() != Item::FIELD_ITEM ||
((Item_field *) item)->field->maybe_null() ||
((Item_field *) item)->field->sort_length()) &&
/* AND not a zero length NOT NULL string function. */
(item->type() != Item::FUNC_ITEM ||
item->maybe_null() ||
item->result_type() != STRING_RESULT ||
item->max_length)))
requires_sorting= TRUE ;
if ((item->with_sum_func() && item->type() != Item::SUM_FUNC_ITEM) ||
item->with_window_func())
item->split_sum_func(thd, ref_ptrs, all_fields, SPLIT_SUM_SELECT);
}
/* Drop the ORDER BY clause if none of the columns contain any data that
can produce a meaningful sorted set. */
if (!requires_sorting)
order= NULL;
}
else
{
/* The current select does not have an ORDER BY */
if (select_lex->limit_params.with_ties)
{
my_error(ER_WITH_TIES_NEEDS_ORDER, MYF(0 ));
DBUG_RETURN(-1 );
}
}
if (having && (having->with_sum_func() || having->with_rownum_func()))
having->split_sum_func2(thd, ref_ptrs, all_fields,
&having, SPLIT_SUM_SKIP_REGISTERED);
if (select_lex->inner_sum_func_list)
{
Item_sum *end=select_lex->inner_sum_func_list;
Item_sum *item_sum= end;
do
{
item_sum= item_sum->next;
item_sum->split_sum_func2(thd, ref_ptrs,
all_fields, item_sum->ref_by, 0 );
} while (item_sum != end);
}
if (select_lex->inner_refs_list.elements &&
fix_inner_refs(thd, all_fields, select_lex, ref_ptrs))
DBUG_RETURN(-1 );
if (group_list)
{
/*
Because HEAP tables can ' t index BIT fields we need to use an
additional hidden field for grouping because later it will be
converted to a LONG field . Original field will remain of the
BIT type and will be returned to a client .
*/
for (ORDER *ord= group_list; ord; ord= ord->next)
{
if ((*ord->item)->type() == Item::FIELD_ITEM &&
(*ord->item)->field_type() == MYSQL_TYPE_BIT)
{
Item_field *field= new (thd->mem_root) Item_field(thd, *(Item_field**)ord->item);
if (!field)
DBUG_RETURN(-1 );
int el= all_fields.elements;
ref_ptrs[el]= field;
all_fields.push_front(field, thd->mem_root);
ord->item= &ref_ptrs[el];
}
}
}
/*
Check if there are references to un - aggregated columns when computing
aggregate functions with implicit grouping ( there is no GROUP BY ) .
*/
if (thd->variables.sql_mode & MODE_ONLY_FULL_GROUP_BY && !group_list &&
!(select_lex->master_unit()->item &&
select_lex->master_unit()->item->is_in_predicate() &&
select_lex->master_unit()->item->get_IN_subquery()->
test_set_strategy(SUBS_MAXMIN_INJECTED)) &&
select_lex->non_agg_field_used() &&
select_lex->agg_func_used())
{
my_message(ER_MIX_OF_GROUP_FUNC_AND_FIELDS,
ER_THD(thd, ER_MIX_OF_GROUP_FUNC_AND_FIELDS), MYF(0 ));
DBUG_RETURN(-1 );
}
{
/* Caclulate the number of groups */
send_group_parts= 0 ;
for (ORDER *group_tmp= group_list ; group_tmp ; group_tmp= group_tmp->next)
send_group_parts++;
}
procedure= setup_procedure(thd, proc_param, result, fields_list, &error);
if (unlikely(error))
goto err; /* purecov: inspected */
if (procedure)
{
if (setup_new_fields(thd, fields_list, all_fields,
procedure->param_fields))
goto err; /* purecov: inspected */
if (procedure->group)
{
if (!test_if_subpart(procedure->group,group_list))
{ /* purecov: inspected */
my_message(ER_DIFF_GROUPS_PROC, ER_THD(thd, ER_DIFF_GROUPS_PROC),
MYF(0 )); /* purecov: inspected */
goto err; /* purecov: inspected */
}
}
if (order && (procedure->flags & PROC_NO_SORT))
{ /* purecov: inspected */
my_message(ER_ORDER_WITH_PROC, ER_THD(thd, ER_ORDER_WITH_PROC),
MYF(0 )); /* purecov: inspected */
goto err; /* purecov: inspected */
}
if (thd->lex->derived_tables)
{
/*
Queries with derived tables and PROCEDURE are not allowed .
Many of such queries are disallowed grammatically , but there
are still some complex cases :
SELECT 1 FROM ( SELECT 1 ) a PROCEDURE ANALYSE ( )
*/
my_error(ER_WRONG_USAGE, MYF(0 ), "PROCEDURE" ,
thd->lex->derived_tables & DERIVED_VIEW ?
"view" : "subquery" );
goto err;
}
if (thd->lex->sql_command != SQLCOM_SELECT)
{
// EXPLAIN SELECT * FROM t1 PROCEDURE ANALYSE()
my_error(ER_WRONG_USAGE, MYF(0 ), "PROCEDURE" , "non-SELECT" );
goto err;
}
}
if (unlikely(thd->trace_started()))
{
Json_writer_object trace_wrapper(thd);
opt_trace_print_expanded_query(thd, select_lex, &trace_wrapper);
}
if (!procedure && result && result->prepare(fields_list, unit_arg))
goto err; /* purecov: inspected */
select_lex->where_cond_after_prepare= conds;
unit= unit_arg;
if (prepare_stage2())
goto err;
prepared= true ;
DBUG_RETURN(0 ); // All OK
err:
delete procedure; /* purecov: inspected */
procedure= 0 ;
DBUG_RETURN(-1 ); /* purecov: inspected */
}
/**
Second phase of prepare where we collect some statistic .
@ details
We made this part separate to be able recalculate some statistic after
transforming subquery on optimization phase .
*/
bool JOIN::prepare_stage2()
{
bool res= TRUE ;
DBUG_ENTER("JOIN::prepare_stage2" );
/* Init join struct */
count_field_types(select_lex, &tmp_table_param, all_fields, 0 );
this ->group= group_list != 0 ;
if (tmp_table_param.sum_func_count && !group_list)
{
implicit_grouping= TRUE ;
// Result will contain zero or one row - ordering is meaningless
order= NULL;
}
#ifdef RESTRICTED_GROUP
if (implicit_grouping)
{
my_message(ER_WRONG_SUM_SELECT,ER_THD(thd, ER_WRONG_SUM_SELECT),MYF(0 ));
goto err;
}
#endif
if (select_lex->olap == ROLLUP_TYPE && rollup_init())
goto err;
if (alloc_func_list() ||
make_sum_func_list(all_fields, fields_list, false ))
goto err;
res= FALSE ;
err:
DBUG_RETURN(res); /* purecov: inspected */
}
bool JOIN::build_explain()
{
DBUG_ENTER("JOIN::build_explain" );
have_query_plan= QEP_AVAILABLE;
/*
explain data must be created on the Explain_query : : mem_root . Because it ' s
just a memroot , not an arena , explain data must not contain any Items
*/
MEM_ROOT *old_mem_root= thd->mem_root;
Item *old_free_list __attribute__((unused))= thd->free_list;
thd->mem_root= thd->lex->explain->mem_root;
bool res= save_explain_data(thd->lex->explain, false /* can overwrite */,
need_tmp,
!skip_sort_order && !no_order && (order || group_list),
select_distinct);
thd->mem_root= old_mem_root;
DBUG_ASSERT(thd->free_list == old_free_list); // no Items were created
if (res)
DBUG_RETURN(1 );
uint select_nr= select_lex->select_number;
JOIN_TAB *curr_tab= join_tab + exec_join_tab_cnt();
for (uint i= 0 ; i < aggr_tables; i++, curr_tab++)
{
if (select_nr == FAKE_SELECT_LEX_ID)
{
/* this is a fake_select_lex of a union */
select_nr= select_lex->master_unit()->first_select()->select_number;
curr_tab->tracker= thd->lex->explain->get_union(select_nr)->
get_tmptable_read_tracker();
}
else if (select_nr < INT_MAX)
{
Explain_select *tmp= thd->lex->explain->get_select(select_nr);
if (tmp)
curr_tab->tracker= tmp->get_using_temporary_read_tracker();
}
}
if (is_in_subquery())
{
Item_in_subselect *subq= unit->item->get_IN_subquery();
subq->init_subq_materialization_tracker(thd);
}
DBUG_RETURN(0 );
}
int JOIN::optimize()
{
int res= 0 ;
if (select_lex->pushdown_select)
{
if (optimization_state == JOIN::OPTIMIZATION_DONE)
return 0 ;
DBUG_ASSERT(optimization_state == JOIN::NOT_OPTIMIZED);
// Do same as JOIN::optimize_inner does:
fields= &select_lex->item_list;
if (!(select_options & SELECT_DESCRIBE))
{
/* Prepare to execute the query pushed into a foreign engine */
res= select_lex->pushdown_select->prepare();
}
with_two_phase_optimization= false ;
}
else
{
/*
This function may be invoked multiple times . Do nothing if the
optimization ( either full or stage1 ) are already done .
*/
if (optimization_state != JOIN::NOT_OPTIMIZED)
return FALSE ;
optimization_state= JOIN::OPTIMIZATION_IN_PROGRESS;
res= optimize_inner();
}
if (!with_two_phase_optimization)
{
if (!res && have_query_plan != QEP_DELETED)
res= build_explain();
optimization_state= JOIN::OPTIMIZATION_DONE;
}
/*
Store the cost of this query into a user variable
TODO : calculate a correct cost for a query with subqueries and UNIONs .
*/
if (select_lex->select_number == 1 )
thd->status_var.last_query_cost= best_read;
return res;
}
/*
@ brief
Call optimize_stage2 ( ) and save the query plan .
*/
int JOIN::optimize_stage2_and_finish()
{
int res= 0 ;
DBUG_ASSERT(with_two_phase_optimization);
DBUG_ASSERT(optimization_state == OPTIMIZATION_PHASE_1_DONE);
if (optimize_stage2())
res= 1 ;
else
{
if (have_query_plan != JOIN::QEP_DELETED)
res= build_explain();
optimization_state= JOIN::OPTIMIZATION_DONE;
}
return res;
}
/**
@ brief
Create range filters objects needed in execution for all join tables
@ details
For each join table from the chosen execution plan such that a range filter
is used when joining this table the function creates a Rowid_filter object
for this range filter . In order to do this the function first constructs
a quick select to scan the range for this range filter . Then it creates
a container for the range filter and finally constructs a Range_rowid_filter
object a pointer to which is set in the field JOIN_TAB : : rowid_filter of
the joined table .
@ retval false Ok
@ retval true Error , query should abort
*/
bool JOIN::make_range_rowid_filters()
{
DBUG_ENTER("make_range_rowid_filters" );
/*
Do not build range filters with detected impossible WHERE .
Anyway conditions cannot be used anymore to extract ranges for filters .
*/
if (const_table_map != found_const_table_map)
DBUG_RETURN(0 );
JOIN_TAB *tab;
for (tab= first_linear_tab(this , WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES);
tab;
tab= next_linear_tab(this , tab, WITH_BUSH_ROOTS))
{
if (!tab->range_rowid_filter_info)
continue ;
DBUG_ASSERT(!(tab->ref.key >= 0 &&
tab->ref.key == (int ) tab->range_rowid_filter_info->get_key_no()));
DBUG_ASSERT(!(tab->ref.key == -1 && tab->quick &&
tab->quick->index == tab->range_rowid_filter_info->get_key_no()));
int err;
SQL_SELECT *sel= NULL;
Rowid_filter_container *filter_container= NULL;
Item **sargable_cond= get_sargable_cond(this , tab->table);
sel= make_select(tab->table, const_table_map, const_table_map,
*sargable_cond, (SORT_INFO*) 0 , 1 , &err);
if (!sel)
continue ;
key_map filter_map;
filter_map.clear_all();
filter_map.set_bit(tab->range_rowid_filter_info->get_key_no());
filter_map.merge(tab->table->with_impossible_ranges);
quick_select_return rc;
/*
EQ_FUNC and EQUAL_FUNC already sent unusable key notes ( if any )
during update_ref_and_keys ( ) . Have only other functions raise notes
from can_optimize_scalar_range ( ) .
*/
rc= sel->test_quick_select(thd, filter_map, (table_map) 0 ,
(ha_rows) HA_POS_ERROR, true , false , true ,
true , Item_func::BITMAP_EXCEPT_ANY_EQUALITY);
if (rc == SQL_SELECT::ERROR || thd->is_error() || thd->check_killed())
{
delete sel;
DBUG_RETURN(true ); /* Fatal error */
}
/*
If SUBS_IN_TO_EXISTS strategy is chosen for the subquery then
additional conditions are injected into WHERE / ON / HAVING and it may
happen that the call of test_quick_select ( ) discovers impossible range .
*/
if (rc == SQL_SELECT::IMPOSSIBLE_RANGE)
{
const_table_map|= tab->table->map;
goto no_filter;
}
// Hints may cause test_quick_select not to find the best table read plan.
if (!sel->quick)
goto no_filter;
filter_container=
tab->range_rowid_filter_info->create_container();
if (filter_container)
{
tab->rowid_filter=
new (thd->mem_root) Range_rowid_filter(tab->table,
tab->range_rowid_filter_info,
filter_container, sel);
if (tab->rowid_filter)
{
tab->need_to_build_rowid_filter= true ;
continue ;
}
}
no_filter:
delete sel;
}
DBUG_RETURN(0 );
}
/**
@ brief
Allocate memory the rowid containers of the used the range filters
@ details
For each join table from the chosen execution plan such that a range filter
is used when joining this table the function allocate memory for the
rowid container employed by the filter . On success it lets the table engine
know that what rowid filter will be used when accessing the table rows .
@ retval
false OK
true Error , query should abort
*/
bool
JOIN::init_range_rowid_filters()
{
JOIN_TAB *tab;
DBUG_ENTER("init_range_rowid_filters" );
for (tab= first_linear_tab(this , WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES);
tab;
tab= next_linear_tab(this , tab, WITH_BUSH_ROOTS))
{
tab->need_to_build_rowid_filter= false ; // Safety
if (!tab->rowid_filter)
continue ;
if (tab->rowid_filter->get_container()->alloc())
{
tab->clear_range_rowid_filter();
continue ;
}
tab->table->file->rowid_filter_push(tab->rowid_filter);
tab->need_to_build_rowid_filter= true ;
}
DBUG_RETURN(0 );
}
/**
global select optimisation .
@ note
error code saved in field ' error '
@ retval
0 success
@ retval
1 error
*/
int
JOIN::optimize_inner()
{
DBUG_ENTER("JOIN::optimize_inner" );
subq_exit_fl= false ;
best_read= 0 .0 ;
DEBUG_SYNC(thd, "before_join_optimize" );
THD_STAGE_INFO(thd, stage_optimizing);
#ifndef DBUG_OFF
dbug_join_tab_array_size= 0 ;
#endif
// rownum used somewhere in query, no limits and it is derived
if (unlikely(thd->lex->with_rownum &&
select_lex->first_cond_optimization &&
select_lex->master_unit()->derived))
optimize_upper_rownum_func();
do_send_rows = (unit->lim.get_select_limit()) ? 1 : 0 ;
set_allowed_join_cache_types();
need_distinct= TRUE ;
Json_writer_object trace_wrapper(thd);
Json_writer_object trace_prepare(thd, "join_optimization" );
trace_prepare.add_select_number(select_lex->select_number);
Json_writer_array trace_steps(thd, "steps" );
if (select_lex->opt_hints_qb)
select_lex->opt_hints_qb->trace_hints(thd);
/*
Needed in case optimizer short - cuts ,
set properly in make_aggr_tables_info ( )
*/
fields= &select_lex->item_list;
if (select_lex->first_cond_optimization)
{
//Do it only for the first execution
/* Merge all mergeable derived tables/views in this SELECT. */
if (select_lex->handle_derived(thd->lex, DT_MERGE))
DBUG_RETURN(TRUE );
table_count= select_lex->leaf_tables.elements;
}
if (select_lex->first_cond_optimization &&
transform_in_predicates_into_in_subq(thd))
DBUG_RETURN(1 );
/*
Update used tables after all handling derived table procedures
After this call , select_lex - > select_list_tables contains the table
bits of all items in the select list ( but not bits from WHERE clause or
other items ) .
*/
select_lex->update_used_tables();
/*
In fact we transform underlying subqueries after their ' prepare ' phase and
before ' optimize ' from upper query ' optimize ' to allow semijoin
conversion happened ( which done in the same way .
*/
if (select_lex->first_cond_optimization &&
conds && conds->walk(&Item::exists2in_processor, thd, 0 ))
DBUG_RETURN(1 );
/*
TODO
make view to decide if it is possible to write to WHERE directly or make Semi - Joins able to process ON condition if it is possible
for ( TABLE_LIST * tbl = tables_list ; tbl ; tbl = tbl - > next_local )
{
if ( tbl - > on_expr & &
tbl - > on_expr - > walk ( & Item : : exists2in_processor , 0 , thd ) )
DBUG_RETURN ( 1 ) ;
}
*/
if (transform_max_min_subquery())
DBUG_RETURN(1 ); /* purecov: inspected */
if (select_lex->first_cond_optimization)
{
/* dump_TABLE_LIST_graph(select_lex, select_lex->leaf_tables); */
if (convert_join_subqueries_to_semijoins(this ))
DBUG_RETURN(1 ); /* purecov: inspected */
/* dump_TABLE_LIST_graph(select_lex, select_lex->leaf_tables); */
select_lex->update_used_tables();
}
eval_select_list_used_tables();
table_count= select_lex->leaf_tables.elements;
if (select_lex->options & OPTION_SCHEMA_TABLE &&
optimize_schema_tables_memory_usage(select_lex->leaf_tables))
DBUG_RETURN(1 );
if (setup_ftfuncs(select_lex)) /* should be after having->fix_fields */
DBUG_RETURN(-1 );
row_limit= ((select_distinct || order || group_list) ? HA_POS_ERROR :
unit->lim.get_select_limit());
/* select_limit is used to decide if we are likely to scan the whole table */
select_limit= unit->lim.get_select_limit();
if (having || (select_options & OPTION_FOUND_ROWS))
select_limit= HA_POS_ERROR;
#ifdef HAVE_REF_TO_FIELDS // Not done yet
/* Add HAVING to WHERE if possible */
if (having && !group_list && !sum_func_count)
{
if (!conds)
{
conds= having;
having= 0 ;
}
else if ((conds=new (thd->mem_root) Item_cond_and(conds,having)))
{
/*
Item_cond_and can ' t be fixed after creation , so we do not check
conds - > fixed ( )
*/
conds->fix_fields(thd, &conds);
conds->change_ref_to_fields(thd, tables_list);
conds->top_level_item();
having= 0 ;
}
}
#endif
SELECT_LEX *sel= select_lex;
if (sel->first_cond_optimization)
{
bool error= false ;
/*
The following code will allocate the new items in a permanent
MEMROOT for prepared statements and stored procedures .
But first we need to ensure that thd - > lex - > explain is allocated
in the execution arena
*/
create_explain_query_if_not_exists(thd->lex, thd->mem_root);
Query_arena *arena, backup;
arena= thd->activate_stmt_arena_if_needed(&backup);
sel->first_cond_optimization= 0 ;
/* Convert all outer joins to inner joins if possible */
conds= simplify_joins(this , join_list, conds, TRUE , FALSE );
add_table_function_dependencies(join_list, table_map(-1 ), &error);
if (thd->is_error() ||
(!select_lex->leaf_tables_saved && select_lex->save_leaf_tables(thd)))
{
/*
If there was an error above , the data structures may have been left in
some undefined state . If this is a PS / SP statement , it might not be
safe to run it again . Note that it needs to be re - prepared .
*/
thd->lex->needs_reprepare= true ;
if (arena)
thd->restore_active_arena(arena, &backup);
DBUG_RETURN(1 );
}
select_lex->leaf_tables_saved= true ;
build_bitmap_for_nested_joins(join_list, 0 );
sel->prep_where= conds ? conds->copy_andor_structure(thd) : 0 ;
sel->where= conds;
select_lex->update_used_tables();
if (arena)
thd->restore_active_arena(arena, &backup);
}
if (!allowed_top_level_tables)
calc_allowed_top_level_tables(select_lex);
if (select_lex->optimize_constant_subqueries())
DBUG_RETURN(1 );
if (conds && conds->with_subquery())
(void ) conds->walk(&Item::cleanup_is_expensive_cache_processor,
0 , 0 );
if (having && having->with_subquery())
(void ) having->walk(&Item::cleanup_is_expensive_cache_processor,
0 , 0 );
List<Item> eq_list;
if (setup_degenerate_jtbm_semi_joins(this , join_list, eq_list))
DBUG_RETURN(1 );
if (eq_list.elements != 0 )
{
Item *new_cond;
if (eq_list.elements == 1 )
new_cond= eq_list.pop();
else
new_cond= new (thd->mem_root) Item_cond_and(thd, eq_list);
if (new_cond &&
((new_cond->fix_fields(thd, &new_cond) ||
!(conds= and_items(thd, conds, new_cond)) ||
conds->fix_fields(thd, &conds))))
DBUG_RETURN(TRUE );
}
eq_list.empty();
if (select_lex->cond_pushed_into_where)
{
conds= and_conds(thd, conds, select_lex->cond_pushed_into_where);
if (conds && conds->fix_fields(thd, &conds))
DBUG_RETURN(1 );
}
if (select_lex->cond_pushed_into_having)
{
having= and_conds(thd, having, select_lex->cond_pushed_into_having);
if (having)
{
select_lex->having_fix_field= 1 ;
select_lex->having_fix_field_for_pushed_cond= 1 ;
if (having->fix_fields(thd, &having))
DBUG_RETURN(1 );
select_lex->having_fix_field= 0 ;
select_lex->having_fix_field_for_pushed_cond= 0 ;
}
}
bool ignore_on_expr= false ;
/*
PS / SP note : on_expr of versioned table can not be reallocated
( see build_equal_items ( ) below ) because it can be not rebuilt
at second invocation .
*/
if (!thd->stmt_arena->is_conventional() &&
thd->mem_root != thd->stmt_arena->mem_root)
for (TABLE_LIST *tbl= tables_list; tbl; tbl= tbl->next_local)
if (tbl->table && tbl->on_expr && tbl->table->versioned())
{
ignore_on_expr= true ;
break ;
}
transform_in_predicates_into_equalities(thd);
if (thd->lex->are_date_funcs_used())
transform_date_conds_into_sargable();
if (optimizer_flag(thd, OPTIMIZER_SWITCH_SARGABLE_CASEFOLD))
{
transform_all_conds_and_on_exprs(
thd, &Item::varchar_upper_cmp_transformer);
}
if (substitute_indexed_vcols_for_join(this ))
{
error= 1 ;
DBUG_RETURN(1 );
}
conds= optimize_cond(this , conds, join_list, ignore_on_expr,
&cond_value, &cond_equal, OPT_LINK_EQUAL_FIELDS);
if (thd->is_error())
{
error= 1 ;
DBUG_PRINT("error" ,("Error from optimize_cond" ));
DBUG_RETURN(1 );
}
if (select_lex->with_rownum && ! order && ! group_list &&
!select_distinct && conds && select_lex == unit->global_parameters() &&
select_lex->first_rownum_optimization)
{
optimize_rownum(thd, unit, conds);
select_lex->first_rownum_optimization= false ;
}
having= optimize_cond(this , having, join_list, TRUE ,
&having_value, &having_equal);
if (thd->is_error())
{
error= 1 ;
DBUG_PRINT("error" ,("Error from optimize_cond" ));
DBUG_RETURN(1 );
}
/* Do not push into WHERE from HAVING if cond_value == Item::COND_FALSE */
if (thd->lex->sql_command == SQLCOM_SELECT &&
optimizer_flag(thd, OPTIMIZER_SWITCH_COND_PUSHDOWN_FROM_HAVING) &&
cond_value != Item::COND_FALSE)
{
having=
select_lex->pushdown_from_having_into_where(thd, having);
if (select_lex->attach_to_conds.elements != 0 )
{
conds= and_new_conditions_to_optimized_cond(thd, conds, &cond_equal,
select_lex->attach_to_conds,
&cond_value);
sel->attach_to_conds.empty();
Json_writer_object wrapper(thd);
Json_writer_object pushd(thd, "condition_pushdown_from_having" );
pushd.add("conds" , conds);
pushd.add("having" , having);
}
}
if (optimizer_flag(thd, OPTIMIZER_SWITCH_COND_PUSHDOWN_FOR_SUBQUERY))
{
TABLE_LIST *tbl;
List_iterator_fast<TABLE_LIST> li(select_lex->leaf_tables);
while ((tbl= li++))
if (tbl->jtbm_subselect)
{
if (tbl->jtbm_subselect->pushdown_cond_for_in_subquery(thd, conds))
DBUG_RETURN(1 );
}
}
if (setup_jtbm_semi_joins(this , join_list, eq_list))
DBUG_RETURN(1 );
if (eq_list.elements != 0 )
{
conds= and_new_conditions_to_optimized_cond(thd, conds, &cond_equal,
eq_list, &cond_value);
if (!conds &&
cond_value != Item::COND_FALSE && cond_value != Item::COND_TRUE)
DBUG_RETURN(TRUE );
}
TABLE_LIST *tbl;
List_iterator_fast<TABLE_LIST> li(select_lex->leaf_tables);
while ((tbl= li++))
{
const bool is_derived_pushdown_allowed= hint_table_state(
thd, tbl->table, DERIVED_CONDITION_PUSHDOWN_HINT_ENUM,
optimizer_flag(thd, OPTIMIZER_SWITCH_COND_PUSHDOWN_FOR_DERIVED));
if (!is_derived_pushdown_allowed)
{
/* Run optimize phase on this derived table/view. */
if (tbl->is_view_or_derived() &&
tbl->handle_derived(thd->lex, DT_OPTIMIZE))
DBUG_RETURN(1 );
continue ;
}
if (tbl->is_materialized_derived())
{
JOIN *join= tbl->get_unit()->first_select()->join;
if (join &&
join->optimization_state == JOIN::OPTIMIZATION_PHASE_1_DONE &&
join->with_two_phase_optimization)
continue ;
/*
Do not push conditions from where into materialized inner tables
of outer joins : this is not valid .
*/
if (!tbl->is_inner_table_of_outer_join())
{
if (pushdown_cond_for_derived(thd, conds, tbl))
DBUG_RETURN(1 );
}
if (mysql_handle_single_derived(thd->lex, tbl, DT_OPTIMIZE))
{
error= 1 ;
DBUG_RETURN(1 );
}
}
}
{
if (select_lex->where)
{
select_lex->cond_value= cond_value;
if (sel->where != conds && cond_value == Item::COND_OK)
thd->change_item_tree(&sel->where, conds);
}
if (select_lex->having)
{
select_lex->having_value= having_value;
if (sel->having != having && having_value == Item::COND_OK)
thd->change_item_tree(&sel->having, having);
}
if (cond_value == Item::COND_FALSE || having_value == Item::COND_FALSE ||
(!unit->lim.get_select_limit() &&
!(select_options & OPTION_FOUND_ROWS)))
{ /* Impossible cond */
if (unit->lim.get_select_limit())
{
DBUG_PRINT("info" , (having_value == Item::COND_FALSE ?
"Impossible HAVING" : "Impossible WHERE" ));
zero_result_cause= having_value == Item::COND_FALSE ?
"Impossible HAVING" : "Impossible WHERE" ;
}
else
{
DBUG_PRINT("info" , ("Zero limit" ));
zero_result_cause= "Zero limit" ;
}
table_count= top_join_tab_count= 0 ;
handle_implicit_grouping_with_window_funcs();
error= 0 ;
subq_exit_fl= true ;
goto setup_subq_exit;
}
}
#ifdef WITH_PARTITION_STORAGE_ENGINE
{
TABLE_LIST *tbl;
List_iterator_fast<TABLE_LIST> li(select_lex->leaf_tables);
while ((tbl= li++))
{
Item **prune_cond= get_sargable_cond(this , tbl->table);
tbl->table->all_partitions_pruned_away=
prune_partitions(thd, tbl->table, *prune_cond);
}
}
#endif
/*
Try to optimize count ( * ) , MY_MIN ( ) and MY_MAX ( ) to const fields if
there is implicit grouping ( aggregate functions but no
group_list ) . In this case , the result set shall only contain one
row .
*/
if (tables_list && implicit_grouping)
{
int res;
/*
opt_sum_query ( ) returns HA_ERR_KEY_NOT_FOUND if no rows match
to the WHERE conditions ,
or 1 if all items were resolved ( optimized away ) ,
or 0 , or an error number HA_ERR_ . . .
If all items were resolved by opt_sum_query , there is no need to
open any tables .
*/
/*
The following resetting and restoring of sum_funcs is needed to
go around a bug in spider where it assumes that
make_sum_func_list ( ) has not been called yet and do logical
choices based on this if special handling of min / max functions should
be done . We disable this special handling while we are trying to find
out if we can replace MIN / MAX values with constants .
*/
Item_sum **save_func_sums= sum_funcs, *tmp_sum_funcs= 0 ;
sum_funcs= &tmp_sum_funcs;
res= opt_sum_query(thd, select_lex->leaf_tables, all_fields, conds);
sum_funcs= save_func_sums;
if (res)
{
DBUG_ASSERT(res >= 0 );
if (res == HA_ERR_KEY_NOT_FOUND)
{
DBUG_PRINT("info" ,("No matching min/max row" ));
zero_result_cause= "No matching min/max row" ;
table_count= top_join_tab_count= 0 ;
error=0 ;
subq_exit_fl= true ;
handle_implicit_grouping_with_window_funcs();
goto setup_subq_exit;
}
if (res > 1 )
{
error= res;
DBUG_PRINT("error" ,("Error from opt_sum_query" ));
DBUG_RETURN(1 );
}
DBUG_PRINT("info" ,("Select tables optimized away" ));
if (!select_lex->have_window_funcs())
zero_result_cause= "Select tables optimized away" ;
tables_list= 0 ; // All tables resolved
select_lex->min_max_opt_list.empty();
const_tables= top_join_tab_count= table_count;
handle_implicit_grouping_with_window_funcs();
/*
Extract all table - independent conditions and replace the WHERE
clause with them . All other conditions were computed by opt_sum_query
and the MIN / MAX / COUNT function ( s ) have been replaced by constants ,
so there is no need to compute the whole WHERE clause again .
Notice that make_cond_for_table ( ) will always succeed to remove all
computed conditions , because opt_sum_query ( ) is applicable only to
conjunctions .
Preserve conditions for EXPLAIN .
*/
if (conds && !(thd->lex->describe & DESCRIBE_EXTENDED))
{
COND *table_independent_conds=
make_cond_for_table(thd, conds, PSEUDO_TABLE_BITS, 0 , -1 ,
FALSE , FALSE );
if (!table_independent_conds && thd->is_error())
DBUG_RETURN(1 );
DBUG_EXECUTE("where" ,
print_where(table_independent_conds,
"where after opt_sum_query()" ,
QT_ORDINARY););
conds= table_independent_conds;
}
}
}
if (!tables_list)
{
DBUG_PRINT("info" ,("No tables" ));
error= 0 ;
subq_exit_fl= true ;
goto setup_subq_exit;
}
error= -1 ; // Error is sent to client
/* get_sort_by_table() call used to be here: */
MEM_UNDEFINED(&sort_by_table, sizeof (sort_by_table));
/*
We have to remove constants and duplicates from group_list before
calling make_join_statistics ( ) as this may call get_best_group_min_max ( )
which needs a simplified group_list .
*/
if (group_list && table_count == 1 )
{
group_list= remove_const(this , group_list, conds,
rollup.state == ROLLUP::STATE_NONE,
&simple_group);
if (unlikely(thd->is_error()))
{
error= 1 ;
DBUG_RETURN(1 );
}
if (!group_list)
{
/* The output has only one row */
order=0 ;
simple_order=1 ;
group_optimized_away= 1 ;
select_distinct=0 ;
}
}
/* Calculate how to do the join */
THD_STAGE_INFO(thd, stage_statistics);
result->prepare_to_read_rows();
if (unlikely(make_join_statistics(this , select_lex->leaf_tables,
&keyuse)) ||
unlikely(thd->is_error()))
{
DBUG_PRINT("error" ,("Error: make_join_statistics() failed" ));
DBUG_RETURN(1 );
}
/*
If a splittable materialized derived / view dt_i is embedded into
into another splittable materialized derived / view dt_o then
splitting plans for dt_i and dt_o are evaluated independently .
First the optimizer looks for the best splitting plan sp_i for dt_i .
It happens when non - splitting plans for dt_o are evaluated .
The cost of sp_i is considered as the cost of materialization of dt_i
when evaluating any splitting plan for dt_o .
*/
if (fix_all_splittings_in_plan())
DBUG_RETURN(1 );
setup_subq_exit:
with_two_phase_optimization= check_two_phase_optimization(thd);
if (with_two_phase_optimization)
optimization_state= JOIN::OPTIMIZATION_PHASE_1_DONE;
else
{
if (optimize_stage2())
DBUG_RETURN(1 );
}
DBUG_RETURN(0 );
}
/*
@ brief
In the Stage 1 we ' ve picked the join order .
Now , refine the query plan and sort out all the details .
The choice how to handle GROUP / ORDER BY is also made here .
@ detail
The main reason this is a separate function is Split - Materialized
optimization . There , we first consider doing non - split Materialization for
a SELECT . After that , the parent SELECT will attempt doing Splitting in
multiple ways and make the final choice .
*/
int JOIN::optimize_stage2()
{
ulonglong select_opts_for_readinfo;
uint no_jbuf_after;
JOIN_TAB *tab;
DBUG_ENTER("JOIN::optimize_stage2" );
if (subq_exit_fl)
goto setup_subq_exit;
if (unlikely(thd->check_killed()))
DBUG_RETURN(1 );
/* Generate an execution plan from the found optimal join order. */
if (get_best_combination())
DBUG_RETURN(1 );
if (make_range_rowid_filters())
DBUG_RETURN(1 );
if (select_lex->handle_derived(thd->lex, DT_OPTIMIZE_STAGE2))
DBUG_RETURN(1 );
/*
We have to call drop_unused_derived_keys ( ) even if we don ' t have any
generated keys ( enabled with OPTIMIZER_SWITCH_DERIVED_WITH_KEYS )
as we may still have unique constraints we have to get rid of .
*/
drop_unused_derived_keys();
if (rollup.state != ROLLUP::STATE_NONE)
{
if (rollup_process_const_fields())
{
DBUG_PRINT("error" , ("Error: rollup_process_fields() failed" ));
DBUG_RETURN(1 );
}
}
else
{
/* Remove distinct if only const tables */
select_distinct= select_distinct && (const_tables != table_count);
}
THD_STAGE_INFO(thd, stage_preparing);
if (result->initialize_tables(this ))
{
DBUG_PRINT("error" ,("Error: initialize_tables() failed" ));
DBUG_RETURN(1 ); // error == -1
}
if (const_table_map != found_const_table_map &&
!(select_options & SELECT_DESCRIBE))
{
// There is at least one empty const table
zero_result_cause= "no matching row in const table" ;
DBUG_PRINT("error" ,("Error: %s" , zero_result_cause));
error= 0 ;
handle_implicit_grouping_with_window_funcs();
goto setup_subq_exit;
}
if (!(thd->variables.option_bits & OPTION_BIG_SELECTS) &&
join_record_count > (double ) thd->variables.max_join_size &&
!(select_options & SELECT_DESCRIBE))
{ /* purecov: inspected */
my_message(ER_TOO_BIG_SELECT, ER_THD(thd, ER_TOO_BIG_SELECT), MYF(0 ));
error= -1 ;
DBUG_RETURN(1 );
}
if (const_tables && !thd->locked_tables_mode &&
!(select_options & SELECT_NO_UNLOCK))
{
/*
Unlock all tables , except sequences , as accessing these may still
require table updates . It ' s safe to ignore result code as all
tables where opened for read only .
*/
(void ) mysql_unlock_some_tables(thd, table, const_tables,
GET_LOCK_SKIP_SEQUENCES);
}
if (!conds && outer_join)
{
/* Handle the case where we have an OUTER JOIN without a WHERE */
conds= (Item*) Item_true;
}
if (impossible_where)
{
zero_result_cause=
"Impossible WHERE noticed after reading const tables" ;
select_lex->mark_const_derived(zero_result_cause);
handle_implicit_grouping_with_window_funcs();
goto setup_subq_exit;
}
select= make_select(*table, const_table_map,
const_table_map, conds, (SORT_INFO*) 0 , 1 , &error);
if (unlikely(error))
{ /* purecov: inspected */
error= -1 ; /* purecov: inspected */
DBUG_PRINT("error" ,("Error: make_select() failed" ));
DBUG_RETURN(1 );
}
reset_nj_counters(this , join_list);
if (make_outerjoin_info(this ))
{
DBUG_RETURN(1 );
}
/*
Among the equal fields belonging to the same multiple equality
choose the one that is to be retrieved first and substitute
all references to these in where condition for a reference for
the selected field .
*/
if (conds)
{
conds= substitute_for_best_equal_field(thd, NO_PARTICULAR_TAB, conds,
cond_equal, map2table, true );
if (unlikely(thd->is_error()))
{
error= 1 ;
DBUG_PRINT("error" ,("Error from substitute_for_best_equal" ));
DBUG_RETURN(1 );
}
conds->update_used_tables();
if (unlikely(thd->trace_started()))
trace_condition(thd, "WHERE" , "substitute_best_equal" , conds);
DBUG_EXECUTE("where" ,
print_where(conds,
"after substitute_best_equal" ,
QT_ORDINARY););
}
if (having)
{
having= substitute_for_best_equal_field(thd, NO_PARTICULAR_TAB, having,
having_equal, map2table, false );
if (thd->is_error())
{
error= 1 ;
DBUG_PRINT("error" ,("Error from substitute_for_best_equal" ));
DBUG_RETURN(1 );
}
if (having)
{
having->update_used_tables();
if (unlikely(thd->trace_started()))
trace_condition(thd, "HAVING" , "substitute_best_equal" , having);
}
DBUG_EXECUTE("having" ,
print_where(having,
"after substitute_best_equal" ,
QT_ORDINARY););
}
/*
Perform the optimization on fields evaluation mentioned above
for all on expressions .
*/
for (tab= first_linear_tab(this , WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); tab;
tab= next_linear_tab(this , tab, WITH_BUSH_ROOTS))
{
if (*tab->on_expr_ref)
{
*tab->on_expr_ref= substitute_for_best_equal_field(thd, NO_PARTICULAR_TAB,
*tab->on_expr_ref,
tab->cond_equal,
map2table, true );
if (unlikely(thd->is_error()))
{
error= 1 ;
DBUG_PRINT("error" ,("Error from substitute_for_best_equal" ));
DBUG_RETURN(1 );
}
(*tab->on_expr_ref)->update_used_tables();
if (unlikely(thd->trace_started()))
{
trace_condition(thd, "ON expr" , "substitute_best_equal" ,
(*tab->on_expr_ref), tab->table->alias.c_ptr());
}
}
}
/*
Perform the optimization on fields evaluation mentioned above
for all used ref items .
*/
for (tab= first_linear_tab(this , WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); tab;
tab= next_linear_tab(this , tab, WITH_BUSH_ROOTS))
{
uint key_copy_index=0 ;
for (uint i=0 ; i < tab->ref.key_parts; i++)
{
Item **ref_item_ptr= tab->ref.items+i;
Item *ref_item= *ref_item_ptr;
if (!ref_item->used_tables() && !(select_options & SELECT_DESCRIBE))
continue ;
COND_EQUAL *equals= cond_equal;
JOIN_TAB *first_inner= tab->first_inner;
while (equals)
{
ref_item= substitute_for_best_equal_field(thd, tab, ref_item,
equals, map2table, true );
if (unlikely(thd->is_error()))
DBUG_RETURN(1 );
if (first_inner)
{
equals= first_inner->cond_equal;
first_inner= first_inner->first_upper;
}
else
equals= 0 ;
}
ref_item->update_used_tables();
if (*ref_item_ptr != ref_item)
{
*ref_item_ptr= ref_item;
Item *item= ref_item->real_item();
store_key *key_copy= tab->ref.key_copy[key_copy_index];
if (key_copy->type() == store_key::FIELD_STORE_KEY)
{
if (item->basic_const_item())
{
/* It is constant propagated here */
tab->ref.key_copy[key_copy_index]=
new store_key_const_item(*tab->ref.key_copy[key_copy_index],
item);
}
else if (item->const_item())
{
tab->ref.key_copy[key_copy_index]=
new store_key_item(*tab->ref.key_copy[key_copy_index],
item, TRUE );
}
else
{
store_key_field *field_copy= ((store_key_field *)key_copy);
DBUG_ASSERT(item->type() == Item::FIELD_ITEM);
field_copy->change_source_field((Item_field *) item);
}
}
}
key_copy_index++;
}
}
if (conds && const_table_map != found_const_table_map &&
(select_options & SELECT_DESCRIBE))
conds= (Item*) Item_false;
/* Cache constant expressions in WHERE, HAVING, ON clauses. */
cache_const_exprs();
if (setup_semijoin_loosescan(this ))
DBUG_RETURN(1 );
if (make_join_select(this , select, conds))
{
if (thd->is_error())
DBUG_RETURN(1 );
zero_result_cause=
"Impossible WHERE noticed after reading const tables" ;
select_lex->mark_const_derived(zero_result_cause);
handle_implicit_grouping_with_window_funcs();
goto setup_subq_exit;
}
error= -1 ; /* if goto err */
/* Optimize distinct away if possible */
{
ORDER *org_order= order;
order=remove_const(this , order,conds,1 , &simple_order);
if (unlikely(thd->is_error()))
{
error= 1 ;
DBUG_RETURN(1 );
}
/*
If we are using ORDER BY NULL or ORDER BY const_expression ,
return result in any order ( even if we are using a GROUP BY )
*/
if (!order && org_order)
skip_sort_order= 1 ;
}
/*
For FETCH . . . WITH TIES save how many items order by had , after we ' ve
removed constant items that have no relevance on the final sorting .
*/
if (unit->lim.is_with_ties())
{
DBUG_ASSERT(with_ties_order_count == 0 );
for (ORDER *it= order; it; it= it->next)
with_ties_order_count+= 1 ;
}
/*
Check if we can optimize away GROUP BY / DISTINCT .
We can do that if there are no aggregate functions , the
fields in DISTINCT clause ( if present ) and / or columns in GROUP BY
( if present ) contain direct references to all key parts of
an unique index ( in whatever order ) and if the key parts of the
unique index cannot contain NULLs .
Note that the unique keys for DISTINCT and GROUP BY should not
be the same ( as long as they are unique ) .
The FROM clause must contain a single non - constant table .
*/
if (table_count - const_tables == 1 && (group || select_distinct) &&
!tmp_table_param.sum_func_count &&
(!join_tab[const_tables].select ||
!join_tab[const_tables].select->quick ||
join_tab[const_tables].select->quick->get_type() !=
QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX) &&
!select_lex->have_window_funcs())
{
if (group && rollup.state == ROLLUP::STATE_NONE &&
list_contains_unique_index(join_tab[const_tables].table,
find_field_in_order_list,
(void *) group_list))
{
/*
We have found that grouping can be removed since groups correspond to
only one row anyway , but we still have to guarantee correct result
order . The line below effectively rewrites the query from GROUP BY
< fields > to ORDER BY < fields > . There are three exceptions :
- if skip_sort_order is set ( see above ) , then we can simply skip
GROUP BY ;
- if we are in a subquery , we don ' t have to maintain order unless there
is a limit clause in the subquery .
- we can only rewrite ORDER BY if the ORDER BY fields are ' compatible '
with the GROUP BY ones , i . e . either one is a prefix of another .
We only check if the ORDER BY is a prefix of GROUP BY . In this case
test_if_subpart ( ) copies the ASC / DESC attributes from the original
ORDER BY fields .
If GROUP BY is a prefix of ORDER BY , then it is safe to leave
' order ' as is .
*/
if (!order || test_if_subpart(group_list, order))
{
if (skip_sort_order ||
(select_lex->master_unit()->item && select_limit == HA_POS_ERROR)) // This is a subquery
order= NULL;
else
order= group_list;
}
/*
If we have an IGNORE INDEX FOR GROUP BY ( fields ) clause , this must be
rewritten to IGNORE INDEX FOR ORDER BY ( fields ) .
*/
join_tab->table->keys_in_use_for_order_by=
join_tab->table->keys_in_use_for_group_by;
group_list= 0 ;
group= 0 ;
}
if (select_distinct &&
list_contains_unique_index(join_tab[const_tables].table,
find_field_in_item_list,
(void *) &fields_list))
{
select_distinct= 0 ;
}
}
if (group || tmp_table_param.sum_func_count)
{
if (! hidden_group_fields && rollup.state == ROLLUP::STATE_NONE
&& !select_lex->have_window_funcs())
select_distinct=0 ;
}
else if (select_distinct && table_count - const_tables == 1 &&
rollup.state == ROLLUP::STATE_NONE &&
!select_lex->have_window_funcs())
{
/*
We are only using one table . In this case we change DISTINCT to a
GROUP BY query if :
- The GROUP BY can be done through indexes ( no sort ) and the ORDER
BY only uses selected fields .
( In this case we can later optimize away GROUP BY and ORDER BY )
- We are scanning the whole table without LIMIT
This can happen if :
- We are using CALC_FOUND_ROWS
- We are using an ORDER BY that can ' t be optimized away .
We don ' t want to use this optimization when we are using LIMIT
because in this case we can just create a temporary table that
holds LIMIT rows and stop when this table is full .
*/
bool all_order_fields_used;
tab= &join_tab[const_tables];
if (order)
{
bool fatal_err;
skip_sort_order=
test_if_skip_sort_order(tab, order, select_limit,
true , // no_changes
&tab->table->keys_in_use_for_order_by,
&fatal_err);
if (fatal_err)
DBUG_RETURN(1 );
}
if ((group_list=create_distinct_group(thd, select_lex->ref_pointer_array,
order, fields_list, all_fields,
&all_order_fields_used)))
{
bool fatal_err= 0 ;
const bool skip_group=
skip_sort_order &&
test_if_skip_sort_order(tab, group_list, select_limit,
true , // no_changes
&tab->table->keys_in_use_for_group_by,
&fatal_err);
if (fatal_err)
DBUG_RETURN(1 );
count_field_types(select_lex, &tmp_table_param, all_fields, 0 );
if ((skip_group && all_order_fields_used) ||
select_limit == HA_POS_ERROR ||
(order && !skip_sort_order))
{
/* Change DISTINCT to GROUP BY */
select_distinct= 0 ;
no_order= !order;
if (all_order_fields_used)
{
if (order && skip_sort_order)
{
/*
Force MySQL to read the table in sorted order to get result in
ORDER BY order .
*/
tmp_table_param.quick_group=0 ;
}
order=0 ;
}
group=1 ; // For end_write_group
}
else
group_list= 0 ;
}
else if (thd->is_error()) // End of memory
DBUG_RETURN(1 );
}
simple_group= rollup.state == ROLLUP::STATE_NONE;
if (group)
{
/*
Update simple_group and group_list as we now have more information , like
which tables or columns are constant .
*/
group_list= remove_const(this , group_list, conds,
rollup.state == ROLLUP::STATE_NONE,
&simple_group);
if (unlikely(thd->is_error()))
{
error= 1 ;
DBUG_RETURN(1 );
}
if (!group_list)
{
/* The output has only one row */
order=0 ;
simple_order=1 ;
select_distinct= 0 ;
group_optimized_away= 1 ;
}
}
calc_group_buffer(this , group_list);
send_group_parts= tmp_table_param.group_parts; /* Save org parts */
if (procedure && procedure->group)
{
group_list= procedure->group= remove_const(this , procedure->group, conds,
1 , &simple_group);
if (unlikely(thd->is_error()))
{
error= 1 ;
DBUG_RETURN(1 );
}
calc_group_buffer(this , group_list);
}
/*
We can ignore ORDER BY if it ' s a prefix of the GROUP BY list
( as MariaDB is by default sorting on GROUP BY ) or
if there is no GROUP BY and aggregate functions are used
( as the result will only contain one row ) .
( 1 ) - Do not remove ORDER BY if we have WITH TIES and are using
QUICK_GROUP_MIN_MAX_SELECT to handle GROUP BY . See the comment
for using_with_ties_and_group_min_max ( ) for details .
*/
if (order && (test_if_subpart(group_list, order) ||
(!group_list && tmp_table_param.sum_func_count)) &&
!using_with_ties_and_group_min_max(this )) // (1)
order=0 ;
// Can't use sort on head table if using join buffering
if (full_join || hash_join)
{
TABLE *stable= (sort_by_table == (TABLE *) 1 ?
join_tab[const_tables].table : sort_by_table);
/*
FORCE INDEX FOR ORDER BY can be used to prevent join buffering when
sorting on the first table .
*/
if (!stable || (!stable->force_index_order &&
!map2table[stable->tablenr]->keep_current_rowid))
{
if (group_list)
simple_group= 0 ;
if (order)
simple_order= 0 ;
}
}
need_tmp= test_if_need_tmp_table();
/*
If window functions are present then we can ' t have simple_order set to
TRUE as the window function needs a temp table for computation .
ORDER BY is computed after the window function computation is done , so
the sort will be done on the temp table .
*/
if (select_lex->have_window_funcs())
simple_order= FALSE ;
/*
If the hint FORCE INDEX FOR ORDER BY / GROUP BY is used for the table
whose columns are required to be returned in a sorted order , then
the proper value for no_jbuf_after should be yielded by a call to
the make_join_orderinfo function .
Yet the current implementation of FORCE INDEX hints does not
allow us to do it in a clean manner .
*/
no_jbuf_after= 1 ? table_count : make_join_orderinfo(this );
// Don't use join buffering when we use MATCH
select_opts_for_readinfo=
(select_options & (SELECT_DESCRIBE | SELECT_NO_JOIN_CACHE)) |
(select_lex->ftfunc_list->elements ? SELECT_NO_JOIN_CACHE : 0 );
if (select_lex->options & OPTION_SCHEMA_TABLE &&
optimize_schema_tables_reads(this ))
DBUG_RETURN(1 );
if (make_join_readinfo(this , select_opts_for_readinfo, no_jbuf_after))
DBUG_RETURN(1 );
/* Perform FULLTEXT search before all regular searches */
if (!(select_options & SELECT_DESCRIBE))
if (init_ftfuncs(thd, select_lex, MY_TEST(order)))
DBUG_RETURN(1 );
/*
It ' s necessary to check const part of HAVING cond as
there is a chance that some cond parts may become
const items after make_join_statistics ( for example
when Item is a reference to cost table field from
outer join ) .
This check is performed only for those conditions
which do not use aggregate functions . In such case
temporary table may not be used and const condition
elements may be lost during further having
condition transformation .
*/
if (having && const_table_map && !having->with_sum_func())
{
having->update_used_tables();
if (having->const_item() && !having->is_expensive())
{
if (!having->val_bool())
{
having= Item_false;
zero_result_cause= "Impossible HAVING noticed after reading const tables" ;
error= 0 ;
select_lex->mark_const_derived(zero_result_cause);
goto setup_subq_exit;
}
having= Item_true;
}
}
if (optimize_unflattened_subqueries())
DBUG_RETURN(1 );
int res;
if ((res= rewrite_to_index_subquery_engine(this )) != -1 )
DBUG_RETURN(res);
if (setup_subquery_caches())
DBUG_RETURN(-1 );
/*
Need to tell handlers that to play it safe , it should fetch all
columns of the primary key of the tables : this is because MySQL may
build row pointers for the rows , and for all columns of the primary key
the read set has not necessarily been set by the server code .
*/
if (need_tmp || select_distinct || group_list || order)
{
for (uint i= 0 ; i < table_count; i++)
{
if (!(table[i]->map & const_table_map))
table[i]->prepare_for_position();
}
}
DBUG_EXECUTE("info" ,TEST_join(this ););
if (!only_const_tables())
{
JOIN_TAB *tab= &join_tab[const_tables];
if (order && !need_tmp)
{
/*
Force using of tmp table if sorting by a SP or UDF function due to
their expensive and probably non - deterministic nature .
*/
for (ORDER *tmp_order= order; tmp_order ; tmp_order=tmp_order->next)
{
Item *item= *tmp_order->item;
if (item->is_expensive())
{
/* Force tmp table without sort */
need_tmp=1 ; simple_order=simple_group=0 ;
break ;
}
}
}
/*
Because filesort always does a full table scan or a quick range scan
we must add the removed reference to the select for the table .
We only need to do this when we have a simple_order or simple_group
as in other cases the join is done before the sort .
*/
if ((order || group_list) &&
tab->type != JT_ALL &&
tab->type != JT_RANGE &&
tab->type != JT_NEXT &&
tab->type != JT_FT &&
tab->type != JT_REF_OR_NULL &&
((order && simple_order) || (group_list && simple_group)))
{
if (add_ref_to_table_cond(thd,tab)) {
DBUG_RETURN(1 );
}
}
/*
Investigate whether we may use an ordered index as part of either
DISTINCT , GROUP BY or ORDER BY execution . An ordered index may be
used for only the first of any of these terms to be executed . This
is reflected in the order which we check for test_if_skip_sort_order ( )
below . However we do not check for DISTINCT here , as it would have
been transformed to a GROUP BY at this stage if it is a candidate for
ordered index optimization .
If a decision was made to use an ordered index , the availability
of such an access path is stored in ' ordered_index_usage ' for later
use by ' execute ' or ' explain '
*/
DBUG_ASSERT(ordered_index_usage == ordered_index_void);
if (group_list) // GROUP BY honoured first
// (DISTINCT was rewritten to GROUP BY if skippable)
{
/*
When there is SQL_BIG_RESULT do not sort using index for GROUP BY ,
and thus force sorting on disk unless a group min - max optimization
is going to be used as it is applied now only for one table queries
with covering indexes .
*/
if (!(select_options & SELECT_BIG_RESULT) ||
(tab->select &&
tab->select->quick &&
tab->select->quick->get_type() ==
QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX))
{
if (simple_group && // GROUP BY is possibly skippable
!select_distinct) // .. if not preceded by a DISTINCT
{
/*
Calculate a possible ' limit ' of table rows for ' GROUP BY ' :
A specified ' LIMIT ' is relative to the final resultset .
' need_tmp ' implies that there will be more postprocessing
so the specified ' limit ' should not be enforced yet .
*/
bool fatal_err;
const ha_rows limit = need_tmp ? HA_POS_ERROR : select_limit;
if (test_if_skip_sort_order(tab, group_list, limit, false ,
&tab->table->keys_in_use_for_group_by,
&fatal_err))
{
ordered_index_usage= ordered_index_group_by;
}
if (fatal_err)
DBUG_RETURN(1 );
}
/*
If we are going to use semi - join LooseScan , it will depend
on the selected index scan to be used . If index is not used
for the GROUP BY , we risk that sorting is put on the LooseScan
table . In order to avoid this , force use of temporary table .
TODO : Explain the quick_group part of the test below .
*/
if ((ordered_index_usage != ordered_index_group_by) &&
((tmp_table_param.quick_group && !procedure) ||
(tab->emb_sj_nest &&
best_positions[const_tables].sj_strategy == SJ_OPT_LOOSE_SCAN)))
{
need_tmp=1 ;
simple_order= simple_group= false ; // Force tmp table without sort
}
}
}
else if (order && // ORDER BY wo/ preceding GROUP BY
(simple_order || skip_sort_order)) // which is possibly skippable
{
bool fatal_err;
if (test_if_skip_sort_order(tab, order, select_limit, false ,
&tab->table->keys_in_use_for_order_by,
&fatal_err))
{
ordered_index_usage= ordered_index_order_by;
}
if (fatal_err)
DBUG_RETURN(1 );
}
}
if (having)
having_is_correlated= MY_TEST(having->used_tables() & OUTER_REF_TABLE_BIT);
tmp_having= having;
if (unlikely(thd->is_error()))
DBUG_RETURN(TRUE );
/*
The loose index scan access method guarantees that all grouping or
duplicate row elimination ( for distinct ) is already performed
during data retrieval , and that all MIN / MAX functions are already
computed for each group . Thus all MIN / MAX functions should be
treated as regular functions , and there is no need to perform
grouping in the main execution loop .
Notice that currently loose index scan is applicable only for
single table queries , thus it is sufficient to test only the first
join_tab element of the plan for its access method .
*/
if (join_tab->is_using_loose_index_scan())
{
tmp_table_param.precomputed_group_by= TRUE ;
if (join_tab->is_using_agg_loose_index_scan())
{
need_distinct= FALSE ;
tmp_table_param.precomputed_group_by= FALSE ;
}
}
if (make_aggr_tables_info())
DBUG_RETURN(1 );
init_join_cache_and_keyread();
if (init_range_rowid_filters())
DBUG_RETURN(1 );
error= 0 ;
if (select_options & SELECT_DESCRIBE)
goto derived_exit;
DBUG_RETURN(0 );
setup_subq_exit:
/* Choose an execution strategy for this JOIN. */
if (!tables_list || !table_count)
{
choose_tableless_subquery_plan();
/* The output has atmost one row */
if (group_list)
{
group_list= NULL;
group_optimized_away= 1 ;
rollup.state= ROLLUP::STATE_NONE;
}
order= NULL;
simple_order= TRUE ;
select_distinct= FALSE ;
if (select_lex->have_window_funcs())
{
if (!(join_tab= thd->alloc<JOIN_TAB>(1 )))
DBUG_RETURN(1 );
#ifndef DBUG_OFF
dbug_join_tab_array_size= 1 ;
#endif
need_tmp= 1 ;
}
if (make_aggr_tables_info())
DBUG_RETURN(1 );
/*
It could be that we ' ve only done optimization stage 1 for
some of the derived tables , and never did stage 2 .
Do it now , otherwise Explain data structure will not be complete .
*/
if (select_lex->handle_derived(thd->lex, DT_OPTIMIZE_STAGE2))
DBUG_RETURN(1 );
}
/*
Even with zero matching rows , subqueries in the HAVING clause may
need to be evaluated if there are aggregate functions in the query .
*/
if (optimize_unflattened_subqueries())
DBUG_RETURN(1 );
error= 0 ;
derived_exit:
select_lex->mark_const_derived(zero_result_cause);
DBUG_RETURN(0 );
}
/**
Add having condition as a where clause condition of the given temp table .
@ param tab Table to which having condition is added .
@ returns false if success , true if error .
*/
bool JOIN::add_having_as_table_cond(JOIN_TAB *tab)
{
tmp_having->update_used_tables();
table_map used_tables= tab->table->map | OUTER_REF_TABLE_BIT;
/* If tmp table is not used then consider conditions of const table also */
if (!need_tmp)
used_tables|= const_table_map;
DBUG_ENTER("JOIN::add_having_as_table_cond" );
Item* sort_table_cond= make_cond_for_table(thd, tmp_having, used_tables,
(table_map) 0 , 0 , false , false );
if (sort_table_cond)
{
if (!tab->select)
{
if (!(tab->select= new SQL_SELECT))
DBUG_RETURN(true );
tab->select->head= tab->table;
}
if (!tab->select->cond)
tab->select->cond= sort_table_cond;
else
{
if (!(tab->select->cond=
new (thd->mem_root) Item_cond_and(thd,
tab->select->cond,
sort_table_cond)))
DBUG_RETURN(true );
}
if (tab->pre_idx_push_select_cond)
{
if (sort_table_cond->type() == Item::COND_ITEM)
sort_table_cond= sort_table_cond->copy_andor_structure(thd);
if (!(tab->pre_idx_push_select_cond=
new (thd->mem_root) Item_cond_and(thd,
tab->pre_idx_push_select_cond,
sort_table_cond)))
DBUG_RETURN(true );
}
if (tab->select->cond)
tab->select->cond->fix_fields_if_needed(thd, 0 );
if (tab->pre_idx_push_select_cond)
tab->pre_idx_push_select_cond->fix_fields_if_needed(thd, 0 );
tab->select->pre_idx_push_select_cond= tab->pre_idx_push_select_cond;
tab->set_select_cond(tab->select->cond, __LINE__);
tab->select_cond->top_level_item();
DBUG_EXECUTE("where" ,print_where(tab->select->cond,
"select and having" ,
QT_ORDINARY););
having= make_cond_for_table(thd, tmp_having, ~ (table_map) 0 ,
~used_tables, 0 , false , false );
if (!having && thd->is_error())
DBUG_RETURN(true );
DBUG_EXECUTE("where" ,
print_where(having, "having after sort" , QT_ORDINARY););
}
else if (thd->is_error())
DBUG_RETURN(true );
DBUG_RETURN(false );
}
bool JOIN::add_fields_for_current_rowid(JOIN_TAB *cur, List<Item> *table_fields)
{
/*
this will not walk into semi - join materialization nests but this is ok
because we will never need to save current rowids for those .
*/
for (JOIN_TAB *tab=join_tab; tab < cur; tab++)
{
if (!tab->keep_current_rowid)
continue ;
Item *item= new (thd->mem_root) Item_temptable_rowid(tab->table);
item->fix_fields(thd, 0 );
/*
table_fields points to JOIN : : all_fields or JOIN : : tmp_all_fields_ * .
These lists start with " added " fields and then their suffix is shared
with JOIN : : fields_list or JOIN : : tmp_fields_list * .
Because of that , new elements can only be added to the front of the list ,
not to the back .
*/
table_fields->push_front(item, thd->mem_root);
cur->tmp_table_param->func_count++;
}
return 0 ;
}
/**
Set info for aggregation tables
@ details
This function finalizes execution plan by taking following actions :
. ) aggregation temporary tables are created , but not instantiated
( this is done during execution ) .
JOIN_TABs for aggregation tables are set appropriately
( see JOIN : : create_postjoin_aggr_table ) .
. ) prepare fields lists ( fields , all_fields , ref_pointer_array slices ) for
each required stage of execution . These fields lists are set for
working tables ' tabs and for the tab of last table in the join .
. ) info for sorting / grouping / dups removal is prepared and saved in
appropriate tabs . Here is an example :
@ returns
false - Ok
true - Error
*/
bool JOIN::make_aggr_tables_info()
{
List<Item> *curr_all_fields= &all_fields;
List<Item> *curr_fields_list= &fields_list;
// Avoid UB (applying .. offset to nullptr) when join_tab is nullptr
JOIN_TAB *curr_tab= join_tab ? join_tab + const_tables : nullptr;
TABLE *exec_tmp_table= NULL;
bool distinct= false ;
const bool has_group_by= this ->group;
bool keep_row_order= thd->lex->with_rownum && (group_list || order);
bool is_having_added_as_table_cond= false ;
DBUG_ENTER("JOIN::make_aggr_tables_info" );
DBUG_ASSERT(current_ref_ptrs == items0);
sort_and_group_aggr_tab= NULL;
if (group_optimized_away)
implicit_grouping= true ;
bool implicit_grouping_with_window_funcs= implicit_grouping &&
select_lex->have_window_funcs();
bool implicit_grouping_without_tables= implicit_grouping &&
!tables_list;
/*
Setup last table to provide fields and all_fields lists to the next
node in the plan .
*/
if (join_tab && top_join_tab_count && tables_list)
{
join_tab[top_join_tab_count - 1 ].fields= &fields_list;
join_tab[top_join_tab_count - 1 ].all_fields= &all_fields;
}
/*
All optimization is done . Check if we can use the storage engines
group by handler to evaluate the group by .
Some storage engines , like spider can also do joins , group by and
distinct in the engine , so we do this for all queries , not only
GROUP BY queries .
*/
if (tables_list && top_join_tab_count && !only_const_tables() && !procedure)
{
/*
At the moment we only support push down for queries where
all tables are in the same storage engine
*/
TABLE_LIST *tbl= tables_list;
handlerton *ht= tbl && tbl->table ? tbl->table->file->partition_ht() : 0 ;
for (tbl= tbl->next_local; ht && tbl; tbl= tbl->next_local)
{
if (!tbl->table || tbl->table->file->partition_ht() != ht)
ht= 0 ;
}
if (ht && ht->create_group_by)
{
/*
Check if the storage engine can intercept the query
JOIN : : optimize_stage2 ( ) might convert DISTINCT into GROUP BY and then
optimize away GROUP BY ( group_list ) . In such a case , we need to notify
a storage engine supporting a group by handler of the existence of the
original DISTINCT . Thus , we set select_distinct | | group_optimized_away
to Query : : distinct .
*/
Query query= {&all_fields,
(int ) all_fields.elements - (int ) fields_list.elements,
select_distinct || group_optimized_away,
tables_list, conds,
group_list, order ? order : group_list, having,
&select_lex->master_unit()->lim};
group_by_handler *gbh= ht->create_group_by(thd, &query);
if (gbh)
{
if (!(pushdown_query= new (thd->mem_root) Pushdown_query(select_lex,
gbh)))
DBUG_RETURN(1 );
/*
We must store rows in the tmp table if we need to do an ORDER BY
or DISTINCT and the storage handler can ' t handle it .
*/
need_tmp= query.order_by || query.group_by || query.distinct;
distinct= query.distinct;
keep_row_order= query.order_by || query.group_by;
order= query.order_by;
aggr_tables++;
curr_tab= join_tab + exec_join_tab_cnt();
bzero((void *)curr_tab, sizeof (JOIN_TAB));
curr_tab->ref.key= -1 ;
curr_tab->join= this ;
if (!(curr_tab->tmp_table_param= new TMP_TABLE_PARAM(tmp_table_param)))
DBUG_RETURN(1 );
curr_tab->tmp_table_param->func_count= all_fields.elements;
TABLE* table= create_tmp_table(thd, curr_tab->tmp_table_param,
all_fields,
NULL, distinct,
TRUE , select_options, HA_ROWS_MAX,
&empty_clex_str, !need_tmp,
keep_row_order);
if (!table)
DBUG_RETURN(1 );
if (!(curr_tab->aggr= new (thd->mem_root) AGGR_OP(curr_tab)))
DBUG_RETURN(1 );
curr_tab->aggr->set_write_func(::end_send);
curr_tab->table= table;
/*
Setup reference fields , used by summary functions and group by fields ,
to point to the temporary table .
The actual switching to the temporary tables fields for HAVING
and ORDER BY is done in do_select ( ) by calling
set_items_ref_array ( items1 ) .
*/
init_items_ref_array();
items1= ref_ptr_array_slice(2 );
if (change_to_use_tmp_fields(thd, items1,
tmp_fields_list1, tmp_all_fields1,
fields_list.elements, all_fields))
DBUG_RETURN(1 );
/* Give storage engine access to temporary table */
gbh->table= table;
pushdown_query->store_data_in_temp_table= need_tmp;
pushdown_query->having= having;
/*
Group by and having is calculated by the group_by handler .
Reset the group by and having
*/
DBUG_ASSERT(query.group_by == NULL);
group= 0 ; group_list= 0 ;
having= tmp_having= 0 ;
/*
Select distinct is handled by handler or by creating an unique index
over all fields in the temporary table
*/
select_distinct= 0 ;
order= query.order_by;
tmp_table_param.field_count+= tmp_table_param.sum_func_count;
tmp_table_param.sum_func_count= 0 ;
fields= curr_fields_list;
//todo: new:
curr_tab->ref_array= &items1;
curr_tab->all_fields= &tmp_all_fields1;
curr_tab->fields= &tmp_fields_list1;
DBUG_RETURN(thd->is_error());
}
}
}
/*
The loose index scan access method guarantees that all grouping or
duplicate row elimination ( for distinct ) is already performed
during data retrieval , and that all MIN / MAX functions are already
computed for each group . Thus all MIN / MAX functions should be
treated as regular functions , and there is no need to perform
grouping in the main execution loop .
Notice that currently loose index scan is applicable only for
single table queries , thus it is sufficient to test only the first
join_tab element of the plan for its access method .
*/
if (join_tab && top_join_tab_count && tables_list &&
join_tab->is_using_loose_index_scan())
tmp_table_param.precomputed_group_by=
!join_tab->is_using_agg_loose_index_scan();
group_list_for_estimates= group_list;
/* Create a tmp table if distinct or if the sort is too complicated */
if (need_tmp)
{
aggr_tables++;
curr_tab= join_tab + exec_join_tab_cnt();
DBUG_ASSERT(curr_tab - join_tab < dbug_join_tab_array_size);
bzero((void *)curr_tab, sizeof (JOIN_TAB));
curr_tab->ref.key= -1 ;
if (only_const_tables())
first_select= sub_select_postjoin_aggr;
/*
Create temporary table on first execution of this join .
( Will be reused if this is a subquery that is executed several times . )
*/
init_items_ref_array();
ORDER *tmp_group= (ORDER *) 0 ;
if (!simple_group && !procedure && !(test_flags & TEST_NO_KEY_GROUP))
tmp_group= group_list;
tmp_table_param.hidden_field_count=
all_fields.elements - fields_list.elements;
distinct= select_distinct && !group_list &&
!select_lex->have_window_funcs();
keep_row_order= thd->lex->with_rownum && (group_list || order);
bool save_sum_fields= (group_list && simple_group) ||
implicit_grouping_with_window_funcs;
if (create_postjoin_aggr_table(curr_tab,
&all_fields, tmp_group,
save_sum_fields,
distinct, keep_row_order))
DBUG_RETURN(true );
exec_tmp_table= curr_tab->table;
if (exec_tmp_table->distinct)
optimize_distinct();
/* Change sum_fields reference to calculated fields in tmp_table */
items1= ref_ptr_array_slice(2 );
if ((sort_and_group || curr_tab->table->group ||
tmp_table_param.precomputed_group_by) &&
!implicit_grouping_without_tables)
{
if (change_to_use_tmp_fields(thd, items1,
tmp_fields_list1, tmp_all_fields1,
fields_list.elements, all_fields))
DBUG_RETURN(true );
}
else
{
if (change_refs_to_tmp_fields(thd, items1,
tmp_fields_list1, tmp_all_fields1,
fields_list.elements, all_fields))
DBUG_RETURN(true );
}
curr_all_fields= &tmp_all_fields1;
curr_fields_list= &tmp_fields_list1;
// Need to set them now for correct group_fields setup, reset at the end.
set_items_ref_array(items1);
curr_tab->ref_array= &items1;
curr_tab->all_fields= &tmp_all_fields1;
curr_tab->fields= &tmp_fields_list1;
set_postjoin_aggr_write_func(curr_tab);
/*
If having is not handled here , it will be checked before the row is sent
to the client .
*/
if (tmp_having &&
(sort_and_group || (exec_tmp_table->distinct && !group_list) ||
select_lex->have_window_funcs()))
{
/*
If there is no select distinct and there are no window functions
then move the having to table conds of tmp table .
NOTE : We cannot apply having after distinct or window functions
If columns of having are not part of select distinct ,
then distinct may remove rows which can satisfy having .
In the case of window functions we * must * make sure to not
store any rows which don ' t match HAVING within the temp table ,
as rows will end up being used during their computation .
*/
if (!select_distinct && !select_lex->have_window_funcs() &&
add_having_as_table_cond(curr_tab))
DBUG_RETURN(true );
is_having_added_as_table_cond= tmp_having != having;
/*
Having condition which we are not able to add as tmp table conds are
kept as before . And , this will be applied before storing the rows in
tmp table .
*/
curr_tab->having= having;
having= NULL; // Already done
}
tmp_table_param.func_count= 0 ;
tmp_table_param.field_count+= tmp_table_param.func_count;
if (sort_and_group || curr_tab->table->group)
{
tmp_table_param.field_count+= tmp_table_param.sum_func_count;
tmp_table_param.sum_func_count= 0 ;
}
if (exec_tmp_table->group)
{ // Already grouped
if (!order && !no_order && !skip_sort_order)
order= group_list; /* order by group */
group_list= NULL;
}
/*
If we have different sort & group then we must sort the data by group
and copy it to another tmp table .
This code is also used if we are using distinct something
we haven ' t been able to store in the temporary table yet
like SEC_TO_TIME ( SUM ( . . . ) ) .
3 . Also , this is used when
- the query has Window functions ,
- the GROUP BY operation is done with OrderedGroupBy algorithm .
In this case , the first temptable will contain pre - GROUP - BY data . Force
the creation of the second temporary table . Post - GROUP - BY dataset will be
written there , and then Window Function processing code will be able to
process it .
*/
if ((group_list &&
(!test_if_subpart(group_list, order) || select_distinct)) ||
(select_distinct && tmp_table_param.using_outer_summary_function) ||
(group_list && !tmp_table_param.quick_group && // (3)
select_lex->have_window_funcs())) // (3)
{ /* Must copy to another table */
DBUG_PRINT("info" ,("Creating group table" ));
calc_group_buffer(this , group_list);
count_field_types(select_lex, &tmp_table_param, tmp_all_fields1,
select_distinct && !group_list);
tmp_table_param.hidden_field_count=
tmp_all_fields1.elements - tmp_fields_list1.elements;
curr_tab++;
aggr_tables++;
DBUG_ASSERT(curr_tab - join_tab < dbug_join_tab_array_size);
bzero((void *)curr_tab, sizeof (JOIN_TAB));
curr_tab->ref.key= -1 ;
/* group data to new table */
/*
If the access method is loose index scan then all MIN / MAX
functions are precomputed , and should be treated as regular
functions . See extended comment above .
*/
if (join_tab->is_using_loose_index_scan())
tmp_table_param.precomputed_group_by= TRUE ;
tmp_table_param.hidden_field_count=
curr_all_fields->elements - curr_fields_list->elements;
ORDER *dummy= NULL; //TODO can use table->group here also
if (create_postjoin_aggr_table(curr_tab, curr_all_fields, dummy, true ,
distinct, keep_row_order))
DBUG_RETURN(true );
if (group_list)
{
if (!only_const_tables()) // No need to sort a single row
{
if (add_sorting_to_table(curr_tab - 1 , group_list))
DBUG_RETURN(true );
}
if (make_group_fields(this , this ))
DBUG_RETURN(true );
}
// Setup sum funcs only when necessary, otherwise we might break info
// for the first table
if (group_list || tmp_table_param.sum_func_count)
{
if (make_sum_func_list(*curr_all_fields, *curr_fields_list, true ))
DBUG_RETURN(true );
if (prepare_sum_aggregators(thd, sum_funcs,
!join_tab->is_using_agg_loose_index_scan()))
DBUG_RETURN(true );
group_list= NULL;
if (setup_sum_funcs(thd, sum_funcs))
DBUG_RETURN(true );
}
// No sum funcs anymore
DBUG_ASSERT(items2.is_null());
items2= ref_ptr_array_slice(3 );
if (change_to_use_tmp_fields(thd, items2,
tmp_fields_list2, tmp_all_fields2,
fields_list.elements, tmp_all_fields1))
DBUG_RETURN(true );
curr_fields_list= &tmp_fields_list2;
curr_all_fields= &tmp_all_fields2;
set_items_ref_array(items2);
curr_tab->ref_array= &items2;
curr_tab->all_fields= &tmp_all_fields2;
curr_tab->fields= &tmp_fields_list2;
set_postjoin_aggr_write_func(curr_tab);
tmp_table_param.field_count+= tmp_table_param.sum_func_count;
tmp_table_param.sum_func_count= 0 ;
}
if (curr_tab->table->distinct)
select_distinct= false ; /* Each row is unique */
if (select_distinct && !group_list)
{
if (having)
{
curr_tab->having= having;
having->update_used_tables();
}
/*
We only need DISTINCT operation if the join is not degenerate .
If it is , we must not request DISTINCT processing , because
remove_duplicates ( ) assumes there is a preceding computation step ( and
in the degenerate join , there ' s none )
*/
if (top_join_tab_count && tables_list)
curr_tab->distinct= true ;
having= NULL;
select_distinct= false ;
}
/* Clean tmp_table_param for the next tmp table. */
tmp_table_param.field_count= tmp_table_param.sum_func_count=
tmp_table_param.func_count= 0 ;
tmp_table_param.copy_field= tmp_table_param.copy_field_end=0 ;
first_record= sort_and_group=0 ;
if (!group_optimized_away || implicit_grouping_with_window_funcs)
{
group= false ;
}
else
{
/*
If grouping has been optimized away , a temporary table is
normally not needed unless we ' re explicitly requested to create
one ( e . g . due to a SQL_BUFFER_RESULT hint or INSERT . . . SELECT ) .
In this case ( grouping was optimized away ) , temp_table was
created without a grouping expression and JOIN : : exec ( ) will not
perform the necessary grouping ( by the use of end_send_group ( )
or end_write_group ( ) ) if JOIN : : group is set to false .
*/
// the temporary table was explicitly requested
DBUG_ASSERT(select_options & OPTION_BUFFER_RESULT);
// the temporary table does not have a grouping expression
DBUG_ASSERT(!curr_tab->table->group);
}
calc_group_buffer(this , group_list);
count_field_types(select_lex, &tmp_table_param, *curr_all_fields, false );
}
if (group ||
(implicit_grouping && !implicit_grouping_with_window_funcs) ||
tmp_table_param.sum_func_count)
{
if (make_group_fields(this , this ))
DBUG_RETURN(true );
DBUG_ASSERT(items3.is_null());
if (items0.is_null())
init_items_ref_array();
items3= ref_ptr_array_slice(4 );
setup_copy_fields(thd, &tmp_table_param,
items3, tmp_fields_list3, tmp_all_fields3,
curr_fields_list->elements, *curr_all_fields);
curr_fields_list= &tmp_fields_list3;
curr_all_fields= &tmp_all_fields3;
set_items_ref_array(items3);
if (join_tab)
{
JOIN_TAB *last_tab= join_tab + top_join_tab_count + aggr_tables - 1 ;
// Set grouped fields on the last table
last_tab->ref_array= &items3;
last_tab->all_fields= &tmp_all_fields3;
last_tab->fields= &tmp_fields_list3;
}
if (make_sum_func_list(*curr_all_fields, *curr_fields_list, true ))
DBUG_RETURN(true );
if (prepare_sum_aggregators(thd, sum_funcs,
!join_tab ||
!join_tab-> is_using_agg_loose_index_scan()))
DBUG_RETURN(true );
if (unlikely(setup_sum_funcs(thd, sum_funcs) || thd->is_error()))
DBUG_RETURN(true );
}
if (group_list || order)
{
DBUG_PRINT("info" ,("Sorting for send_result_set_metadata" ));
THD_STAGE_INFO(thd, stage_sorting_result);
/* If we have already done the group, add HAVING to sorted table */
if (tmp_having && !is_having_added_as_table_cond &&
!group_list && !sort_and_group)
{
if (add_having_as_table_cond(curr_tab))
DBUG_RETURN(true );
}
if (group)
select_limit= HA_POS_ERROR;
else if (!need_tmp)
{
/*
We can abort sorting after thd - > select_limit rows if there are no
filter conditions for any tables after the sorted one .
Filter conditions come in several forms :
1 . as a condition item attached to the join_tab , or
2 . as a keyuse attached to the join_tab ( ref access ) .
*/
for (uint i= const_tables + 1 ; i < top_join_tab_count; i++)
{
JOIN_TAB *const tab= join_tab + i;
if (tab->select_cond || // 1
(tab->keyuse && !tab->first_inner)) // 2
{
/* We have to sort all rows */
select_limit= HA_POS_ERROR;
break ;
}
}
}
/*
Here we add sorting stage for ORDER BY / GROUP BY clause , if the
optimiser chose FILESORT to be faster than INDEX SCAN or there is
no suitable index present .
OPTION_FOUND_ROWS supersedes LIMIT and is taken into account .
*/
DBUG_PRINT("info" ,("Sorting for order by/group by" ));
ORDER *order_arg= group_list ? group_list : order;
if (top_join_tab_count + aggr_tables > const_tables &&
ordered_index_usage !=
(group_list ? ordered_index_group_by : ordered_index_order_by) &&
curr_tab->type != JT_CONST &&
curr_tab->type != JT_EQ_REF) // Don't sort 1 row
{
// Sort either first non-const table or the last tmp table
JOIN_TAB *sort_tab= curr_tab;
if (add_sorting_to_table(sort_tab, order_arg))
DBUG_RETURN(true );
/*
filesort_limit : Return only this many rows from filesort ( ) .
We can use select_limit_cnt only if we have no group_by and 1 table .
This allows us to use Bounded_queue for queries like :
" select SQL_CALC_FOUND_ROWS * from t1 order by b desc limit 1 ; "
m_select_limit = = HA_POS_ERROR ( we need a full table scan )
unit - > select_limit_cnt = = 1 ( we only need one row in the result set )
*/
sort_tab->filesort->limit=
(has_group_by || (join_tab + top_join_tab_count > curr_tab + 1 )) ?
select_limit : unit->lim.get_select_limit();
if (unit->lim.is_with_ties())
sort_tab->filesort->limit= HA_POS_ERROR;
}
if (!only_const_tables() &&
!join_tab[const_tables].filesort &&
!(select_options & SELECT_DESCRIBE))
{
/*
If no IO cache exists for the first table then we are using an
INDEX SCAN and no filesort . Thus we should not remove the sorted
attribute on the INDEX SCAN .
*/
skip_sort_order= true ;
}
}
/*
Window functions computation step should be attached to the last join_tab
that ' s doing aggregation .
The last join_tab reads the data from the temp . table . It also may do
- sorting
- duplicate value removal
Both of these operations are done after window function computation step .
*/
if (select_lex->window_funcs.elements)
{
curr_tab= join_tab + total_join_tab_cnt();
if (!(curr_tab->window_funcs_step= new Window_funcs_computation))
DBUG_RETURN(true );
if (curr_tab->window_funcs_step->setup(thd, &select_lex->window_funcs,
curr_tab))
DBUG_RETURN(true );
/* Count that we're using window functions. */
status_var_increment(thd->status_var.feature_window_functions);
}
if (select_lex->custom_agg_func_used())
status_var_increment(thd->status_var.feature_custom_aggregate_functions);
/*
Allocate Cached_items of ORDER BY for FETCH FIRST . . WITH TIES .
The order list might have been modified prior to this , but we are
only interested in the initial order by columns , after all const
elements are removed .
*/
if (unit->lim.is_with_ties())
{
/*
When ORDER BY is eliminated , we make use of the GROUP BY list .
We ' ve already counted how many elements from ORDER BY
must be evaluated as part of WITH TIES so we use that .
*/
ORDER *order_src = order ? order : group_list;
if (alloc_order_fields(this , order_src,
with_ties_order_count))
DBUG_RETURN(true );
}
fields= curr_fields_list;
// Reset before execution
set_items_ref_array(items0);
if (join_tab)
join_tab[exec_join_tab_cnt() + aggr_tables - 1 ].next_select=
setup_end_select_func(this );
group= has_group_by;
DBUG_RETURN(false );
}
bool
JOIN::create_postjoin_aggr_table(JOIN_TAB *tab, List<Item> *table_fields,
ORDER *table_group,
bool save_sum_fields,
bool distinct,
bool keep_row_order)
{
DBUG_ENTER("JOIN::create_postjoin_aggr_table" );
THD_STAGE_INFO(thd, stage_creating_tmp_table);
/*
Pushing LIMIT to the post - join temporary table creation is not applicable
when there is ORDER BY or GROUP BY or there is no GROUP BY , but
there are aggregate functions , because in all these cases we need
all result rows .
We also can not push limit if the limit is WITH TIES , as we do not know
how many rows we will actually have . This can happen if ORDER BY was
a constant and removed ( during remove_const ) , thus we have an " unlimited "
WITH TIES .
*/
ha_rows table_rows_limit= ((order == NULL || skip_sort_order) &&
!table_group &&
!select_lex->with_sum_func &&
!unit->lim.is_with_ties()) ? select_limit
: HA_POS_ERROR;
if (!(tab->tmp_table_param= new TMP_TABLE_PARAM(tmp_table_param)))
DBUG_RETURN(true );
if (tmp_table_keep_current_rowid)
add_fields_for_current_rowid(tab, table_fields);
tab->tmp_table_param->skip_create_table= true ;
TABLE* table= create_tmp_table(thd, tab->tmp_table_param, *table_fields,
table_group, distinct,
save_sum_fields, select_options,
table_rows_limit,
&empty_clex_str, true , keep_row_order);
if (!table)
DBUG_RETURN(true );
tmp_table_param.using_outer_summary_function=
tab->tmp_table_param->using_outer_summary_function;
tab->join= this ;
DBUG_ASSERT(tab > tab->join->join_tab || !top_join_tab_count ||
!tables_list);
tab->table= table;
if (tab > join_tab)
(tab - 1 )->next_select= sub_select_postjoin_aggr;
/* if group or order on first table, sort first */
if ((group_list && simple_group) ||
(implicit_grouping && select_lex->have_window_funcs()))
{
DBUG_PRINT("info" ,("Sorting for group" ));
THD_STAGE_INFO(thd, stage_sorting_for_group);
if (ordered_index_usage != ordered_index_group_by &&
!only_const_tables() &&
(join_tab + const_tables)->type != JT_CONST && // Don't sort 1 row
!implicit_grouping &&
add_sorting_to_table(join_tab + const_tables, group_list))
goto err;
if (alloc_group_fields(this , group_list))
goto err;
if (make_sum_func_list(all_fields, fields_list, true ))
goto err;
if (prepare_sum_aggregators(thd, sum_funcs,
!(tables_list &&
join_tab->is_using_agg_loose_index_scan())))
goto err;
if (setup_sum_funcs(thd, sum_funcs))
goto err;
group_list= NULL;
}
else
{
if (prepare_sum_aggregators(thd, sum_funcs,
!join_tab->is_using_agg_loose_index_scan()))
goto err;
if (setup_sum_funcs(thd, sum_funcs))
goto err;
if (!group_list && !table->distinct && order && simple_order &&
tab == join_tab + const_tables)
{
DBUG_PRINT("info" ,("Sorting for order" ));
THD_STAGE_INFO(thd, stage_sorting_for_order);
if (ordered_index_usage != ordered_index_order_by &&
!only_const_tables() &&
add_sorting_to_table(join_tab + const_tables, order))
goto err;
order= NULL;
}
}
if (!(tab->aggr= new (thd->mem_root) AGGR_OP(tab)))
goto err;
table->reginfo.join_tab= tab;
DBUG_RETURN(false );
err:
if (table != NULL)
free_tmp_table(thd, table);
tab->table= NULL;
DBUG_RETURN(true );
}
void
JOIN::optimize_distinct()
{
for (JOIN_TAB *last_join_tab= join_tab + top_join_tab_count - 1 ; ;)
{
if (select_lex->select_list_tables & last_join_tab->table->map ||
last_join_tab->use_join_cache)
break ;
last_join_tab->shortcut_for_distinct= true ;
if (last_join_tab == join_tab)
break ;
--last_join_tab;
}
/* Optimize "select distinct b from t1 order by key_part_1 limit #" */
if (order && skip_sort_order && !unit->lim.is_with_ties()
&& (*order->item)->type() == Item::FIELD_ITEM)
{
/* Should already have been optimized away */
DBUG_ASSERT(ordered_index_usage == ordered_index_order_by);
if (ordered_index_usage == ordered_index_order_by)
{
order= NULL;
}
}
}
/**
@ brief Add Filesort object to the given table to sort if with filesort
@ param tab the JOIN_TAB object to attach created Filesort object to
@ param order List of expressions to sort the table by
@ note This function moves tab - > select , if any , to filesort - > select
@ return false on success , true on OOM
*/
bool
JOIN::add_sorting_to_table(JOIN_TAB *tab, ORDER *order)
{
tab->filesort=
new (thd->mem_root) Filesort(order, HA_ROWS_MAX, tab->keep_current_rowid,
tab->select);
if (!tab->filesort)
return true ;
TABLE *table= tab->table;
if ((tab == join_tab + const_tables) &&
table->pos_in_table_list->is_sjm_scan_table())
{
tab->filesort->set_all_read_bits= TRUE ;
tab->filesort->unpack= unpack_to_base_table_fields;
}
/*
Select was moved to filesort - > select to force join_init_read_record to use
sorted result instead of reading table through select .
*/
if (tab->select)
{
tab->select= NULL;
tab->set_select_cond(NULL, __LINE__);
}
tab->read_first_record= join_init_read_record;
return false ;
}
/**
Setup expression caches for subqueries that need them
@ details
The function wraps correlated subquery expressions that return one value
into objects of the class Item_cache_wrapper setting up an expression
cache for each of them . The result values of the subqueries are to be
cached together with the corresponding sets of the parameters - outer
references of the subqueries .
@ retval FALSE OK
@ retval TRUE Error
*/
bool JOIN::setup_subquery_caches()
{
DBUG_ENTER("JOIN::setup_subquery_caches" );
/*
We have to check all this condition together because items created in
one of this clauses can be moved to another one by optimizer
*/
if (select_lex->expr_cache_may_be_used[IN_WHERE] ||
select_lex->expr_cache_may_be_used[IN_HAVING] ||
select_lex->expr_cache_may_be_used[IN_ON] ||
select_lex->expr_cache_may_be_used[NO_MATTER])
{
JOIN_TAB *tab;
if (conds &&
!(conds= conds->transform(thd, &Item::expr_cache_insert_transformer,
NULL)))
DBUG_RETURN(TRUE );
for (tab= first_linear_tab(this , WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES);
tab; tab= next_linear_tab(this , tab, WITH_BUSH_ROOTS))
{
if (tab->select_cond &&
!(tab->select_cond=
tab->select_cond->transform(thd,
&Item::expr_cache_insert_transformer,
NULL)))
DBUG_RETURN(TRUE );
if (tab->cache_select && tab->cache_select->cond)
if (!(tab->cache_select->cond=
tab->cache_select->
cond->transform(thd, &Item::expr_cache_insert_transformer,
NULL)))
DBUG_RETURN(TRUE );
}
if (having &&
!(having= having->transform(thd,
&Item::expr_cache_insert_transformer,
NULL)))
DBUG_RETURN(TRUE );
if (tmp_having)
{
DBUG_ASSERT(having == NULL);
if (!(tmp_having=
tmp_having->transform(thd,
&Item::expr_cache_insert_transformer,
NULL)))
DBUG_RETURN(TRUE );
}
}
if (select_lex->expr_cache_may_be_used[SELECT_LIST] ||
select_lex->expr_cache_may_be_used[IN_GROUP_BY] ||
select_lex->expr_cache_may_be_used[NO_MATTER])
{
List_iterator<Item> li(all_fields);
Item *item;
while ((item= li++))
{
Item *new_item;
if (!(new_item=
item->transform(thd, &Item::expr_cache_insert_transformer,
NULL)))
DBUG_RETURN(TRUE );
if (new_item != item)
{
thd->change_item_tree(li.ref(), new_item);
}
}
for (ORDER *tmp_group= group_list; tmp_group ; tmp_group= tmp_group->next)
{
if (!(*tmp_group->item=
(*tmp_group->item)->transform(thd,
&Item::expr_cache_insert_transformer,
NULL)))
DBUG_RETURN(TRUE );
}
}
if (select_lex->expr_cache_may_be_used[NO_MATTER])
{
for (ORDER *ord= order; ord; ord= ord->next)
{
if (!(*ord->item=
(*ord->item)->transform(thd,
&Item::expr_cache_insert_transformer,
NULL)))
DBUG_RETURN(TRUE );
}
}
DBUG_RETURN(FALSE );
}
/*
Shrink join buffers used for preceding tables to reduce the occupied space
SYNOPSIS
shrink_join_buffers ( )
jt table up to which the buffers are to be shrunk
curr_space the size of the space used by the buffers for tables 1 . . jt
needed_space the size of the space that has to be used by these buffers
DESCRIPTION
The function makes an attempt to shrink all join buffers used for the
tables starting from the first up to jt to reduce the total size of the
space occupied by the buffers used for tables 1 , . . . , jt from curr_space
to needed_space .
The function assumes that the buffer for the table jt has not been
allocated yet .
RETURN
FALSE if all buffer have been successfully shrunk
TRUE otherwise
*/
bool JOIN::shrink_join_buffers(JOIN_TAB *jt,
ulonglong curr_space,
ulonglong needed_space)
{
JOIN_TAB *tab;
JOIN_CACHE *cache;
for (tab= first_linear_tab(this , WITHOUT_BUSH_ROOTS, WITHOUT_CONST_TABLES);
tab != jt;
tab= next_linear_tab(this , tab, WITHOUT_BUSH_ROOTS))
{
cache= tab->cache;
if (cache)
{
size_t buff_size;
if (needed_space < cache->get_min_join_buffer_size())
return TRUE ;
if (cache->shrink_join_buffer_in_ratio(curr_space, needed_space))
{
revise_cache_usage(tab);
return TRUE ;
}
buff_size= cache->get_join_buffer_size();
curr_space-= buff_size;
if (needed_space < buff_size)
{
/*
Safety : fail if we ' ve exhausted available buffer space with
reduced join buffers .
*/
DBUG_ASSERT(0 );
return TRUE ;
}
needed_space-= buff_size;
}
}
cache= jt->cache;
DBUG_ASSERT(cache);
if (needed_space < cache->get_min_join_buffer_size())
return TRUE ;
cache->set_join_buffer_size((size_t)needed_space);
return FALSE ;
}
int
JOIN::reinit()
{
DBUG_ENTER("JOIN::reinit" );
first_record= false ;
group_sent= false ;
cleaned= false ;
accepted_rows= 0 ;
if (aggr_tables)
{
JOIN_TAB *curr_tab= join_tab + exec_join_tab_cnt();
JOIN_TAB *end_tab= curr_tab + aggr_tables;
for ( ; curr_tab < end_tab; curr_tab++)
{
TABLE *tmp_table= curr_tab->table;
if (!tmp_table->is_created())
continue ;
tmp_table->file->extra(HA_EXTRA_RESET_STATE);
tmp_table->file->ha_delete_all_rows();
}
}
clear_sj_tmp_tables(this );
if (current_ref_ptrs != items0)
{
set_items_ref_array(items0);
}
/* need to reset ref access state (see join_read_key) */
if (join_tab)
{
JOIN_TAB *tab;
for (tab= first_linear_tab(this , WITH_BUSH_ROOTS, WITH_CONST_TABLES); tab;
tab= next_linear_tab(this , tab, WITH_BUSH_ROOTS))
{
tab->ref.key_err= TRUE ;
}
}
clear_sum_funcs();
if (no_rows_in_result_called)
{
/* Reset effect of possible no_rows_in_result() */
List_iterator_fast<Item> it(fields_list);
Item *item;
no_rows_in_result_called= 0 ;
while ((item= it++))
item->restore_to_before_no_rows_in_result();
}
if (!(select_options & SELECT_DESCRIBE))
if (init_ftfuncs(thd, select_lex, MY_TEST(order)))
DBUG_RETURN(1 );
DBUG_RETURN(0 );
}
/**
Prepare join result .
@ details Prepare join result prior to join execution or describing .
Instantiate derived tables and get schema tables result if necessary .
@ return
TRUE An error during derived or schema tables instantiation .
FALSE Ok
*/
bool JOIN::prepare_result(List<Item> **columns_list)
{
DBUG_ENTER("JOIN::prepare_result" );
error= 0 ;
/* Create result tables for materialized views. */
if (!zero_result_cause &&
select_lex->handle_derived(thd->lex, DT_CREATE))
goto err;
if (result->prepare2(this ))
goto err;
if ((select_lex->options & OPTION_SCHEMA_TABLE) &&
get_schema_tables_result(this , PROCESSED_BY_JOIN_EXEC))
goto err;
DBUG_RETURN(FALSE );
err:
error= 1 ;
DBUG_RETURN(TRUE );
}
/**
@ retval
0 ok
1 error
*/
bool JOIN::save_explain_data(Explain_query *output, bool can_overwrite,
bool need_tmp_table, bool need_order,
bool distinct)
{
DBUG_ENTER("JOIN::save_explain_data" );
DBUG_PRINT("enter" , ("Save explain Select_lex: %u (%p) parent lex: %p stmt_lex: %p present select: %u (%p)" ,
select_lex->select_number, select_lex,
select_lex->parent_lex, thd->lex->stmt_lex,
(output->get_select(select_lex->select_number) ?
select_lex->select_number : 0 ),
(output->get_select(select_lex->select_number) ?
output->get_select(select_lex->select_number)
->select_lex : NULL)));
/*
If there is SELECT in this statement with the same number it must be the
same SELECT
*/
DBUG_ASSERT(select_lex->select_number == FAKE_SELECT_LEX_ID || !output ||
!output->get_select(select_lex->select_number) ||
output->get_select(select_lex->select_number)->select_lex ==
select_lex);
if (select_lex->select_number != FAKE_SELECT_LEX_ID &&
have_query_plan != JOIN::QEP_NOT_PRESENT_YET &&
have_query_plan != JOIN::QEP_DELETED && // this happens when there was
// no QEP ever, but then
//cleanup() is called multiple times
output && // for "SET" command in SPs.
(can_overwrite? true : !output->get_select(select_lex->select_number)))
{
const char *message= NULL;
if (!table_count || !tables_list || zero_result_cause)
{
/* It's a degenerate join */
message= zero_result_cause ? zero_result_cause : "No tables used" ;
}
bool rc= save_explain_data_intern(thd->lex->explain, need_tmp_table,
need_order, distinct, message);
DBUG_RETURN(rc);
}
/*
Can have join_tab = = NULL for degenerate cases ( e . g . SELECT . . UNION . . . SELECT LIMIT 0 )
*/
if (select_lex == select_lex->master_unit()->fake_select_lex && join_tab)
{
/*
This is fake_select_lex . It has no query plan , but we need to set up a
tracker for ANALYZE
*/
uint nr= select_lex->master_unit()->first_select()->select_number;
Explain_union *eu= output->get_union(nr);
explain= &eu->fake_select_lex_explain;
join_tab[0 ].tracker= eu->get_fake_select_lex_tracker();
for (uint i=0 ; i < exec_join_tab_cnt() + aggr_tables; i++)
{
if (join_tab[i].filesort)
{
if (!(join_tab[i].filesort->tracker=
new Filesort_tracker(thd->lex->analyze_stmt)))
DBUG_RETURN(1 );
}
}
}
DBUG_RETURN(0 );
}
int JOIN::exec()
{
int res;
DBUG_ASSERT(optimization_state == OPTIMIZATION_DONE);
DBUG_EXECUTE_IF("show_explain_probe_join_exec_start" ,
if (dbug_user_var_equals_int(thd,
"show_explain_probe_select_id" ,
select_lex->select_number))
dbug_serve_apcs(thd, 1 );
);
ANALYZE_START_TRACKING(thd, &explain->time_tracker);
res= exec_inner();
ANALYZE_STOP_TRACKING(thd, &explain->time_tracker);
DBUG_EXECUTE_IF("show_explain_probe_join_exec_end" ,
if (dbug_user_var_equals_int(thd,
"show_explain_probe_select_id" ,
select_lex->select_number))
dbug_serve_apcs(thd, 1 );
);
return res;
}
int JOIN::exec_inner()
{
List<Item> *columns_list= &fields_list;
DBUG_ENTER("JOIN::exec_inner" );
DBUG_ASSERT(optimization_state == JOIN::OPTIMIZATION_DONE);
THD_STAGE_INFO(thd, stage_executing);
/*
Activate enforcement of LIMIT ROWS EXAMINED during query execution if :
( 1 ) This JOIN is the outermost query ( not a subquery or derived table )
This ensures that the limit is enabled when actual execution begins ,
and not if a subquery is evaluated during optimization of the outer
query .
( 2 ) This JOIN is not the result of a UNION . In this case do not apply the
limit in order to produce the partial query result stored in the
UNION temp table .
*/
if (!select_lex->outer_select() && // (1)
select_lex != select_lex->master_unit()->fake_select_lex) // (2)
thd->lex->set_limit_rows_examined();
if (procedure)
{
procedure_fields_list= fields_list;
if (procedure->change_columns(thd, procedure_fields_list) ||
result->prepare(procedure_fields_list, unit))
{
thd->limit_found_rows= 0 ;
DBUG_RETURN(0 );
}
columns_list= &procedure_fields_list;
}
if (result->prepare2(this ))
DBUG_RETURN(error);
if (!tables_list && (table_count || !select_lex->with_sum_func) &&
!select_lex->have_window_funcs())
{ // Only test of functions
if (select_options & SELECT_DESCRIBE)
select_describe(this , FALSE , FALSE , FALSE ,
(zero_result_cause?zero_result_cause:"No tables used" ));
else
{
if (result->send_result_set_metadata(*columns_list,
Protocol::SEND_NUM_ROWS |
Protocol::SEND_EOF))
{
DBUG_RETURN(error);
}
/*
We have to test for ' conds ' here as the WHERE may not be constant
even if we don ' t have any tables for prepared statements or if
conds uses something like ' rand ( ) ' .
If the HAVING clause is either impossible or always true , then
JOIN : : having is set to NULL by optimize_cond .
In this case JOIN : : exec must check for JOIN : : having_value , in the
same way it checks for JOIN : : cond_value .
*/
DBUG_ASSERT(error == 0 );
if (cond_value != Item::COND_FALSE &&
having_value != Item::COND_FALSE &&
(!conds || conds->val_bool()) &&
(!having || having->val_bool()))
{
if (do_send_rows &&
(procedure ? (procedure->send_row(procedure_fields_list) ||
procedure->end_of_records()):
result->send_data_with_check(fields_list, unit, 0 )> 0 ))
error= 1 ;
else
send_records= ((select_options & OPTION_FOUND_ROWS) ? 1 :
thd->get_sent_row_count());
}
else
send_records= 0 ;
if (likely(!error))
{
join_free(); // Unlock all cursors
error= (int ) result->send_eof();
}
}
/* Single select (without union) always returns 0 or 1 row */
thd->limit_found_rows= send_records;
DBUG_RETURN(error);
}
/*
Evaluate expensive constant conditions that were not evaluated during
optimization . Do not evaluate them for EXPLAIN statements as these
conditions may be arbitrarily costly , and because the optimize phase
might not have produced a complete executable plan for EXPLAINs .
*/
if (!zero_result_cause &&
exec_const_cond && !(select_options & SELECT_DESCRIBE) &&
!exec_const_cond->val_bool())
zero_result_cause= "Impossible WHERE noticed after reading const tables" ;
/*
We ' ve called exec_const_cond - > val_bool ( ) . This may have caused an error .
*/
if (unlikely(thd->is_error()))
{
error= thd->is_error();
DBUG_RETURN(error);
}
if (zero_result_cause)
{
if (select_lex->have_window_funcs() && send_row_on_empty_set())
{
/*
The query produces just one row but it has window functions .
The only way to compute the value of window function ( s ) is to
run the entire window function computation step ( there is no shortcut ) .
*/
const_tables= table_count;
first_select= sub_select_postjoin_aggr;
}
else
{
(void ) return_zero_rows(this , result, &select_lex->leaf_tables,
columns_list,
send_row_on_empty_set(),
select_options,
zero_result_cause,
having ? having : tmp_having, &all_fields);
DBUG_RETURN(0 );
}
}
/*
Evaluate all constant expressions with subqueries in the
ORDER / GROUP clauses to make sure that all subqueries return a
single row . The evaluation itself will trigger an error if that is
not the case .
*/
if (exec_const_order_group_cond.elements &&
!(select_options & SELECT_DESCRIBE) &&
!select_lex->pushdown_select)
{
List_iterator_fast<Item> const_item_it(exec_const_order_group_cond);
Item *cur_const_item;
StringBuffer<MAX_FIELD_WIDTH> tmp;
while ((cur_const_item= const_item_it++))
{
tmp.set_buffer_if_not_allocated(&my_charset_bin);
cur_const_item->val_str(&tmp);
if (unlikely(thd->is_error()))
{
error= thd->is_error();
DBUG_RETURN(error);
}
}
}
if ((this ->select_lex->options & OPTION_SCHEMA_TABLE) &&
get_schema_tables_result(this , PROCESSED_BY_JOIN_EXEC))
{
error= thd->is_error();
DBUG_RETURN(error);
}
if (select_options & SELECT_DESCRIBE)
{
select_describe(this , need_tmp,
order != 0 && !skip_sort_order,
select_distinct,
!table_count ? "No tables used" : NullS);
DBUG_RETURN(0 );
}
else if (select_lex->pushdown_select)
{
/* Execute the query pushed into a foreign engine */
error= select_lex->pushdown_select->execute();
DBUG_RETURN(error);
}
else
{
/* it's a const select, materialize it. */
select_lex->mark_const_derived(zero_result_cause);
}
/* XXX: When can we have here thd->is_error() not zero? */
if (unlikely(thd->is_error()))
{
error= thd->is_error();
DBUG_RETURN(error);
}
THD_STAGE_INFO(thd, stage_sending_data);
DBUG_PRINT("info" , ("%s" , thd->proc_info));
result->send_result_set_metadata(
procedure ? procedure_fields_list : *fields,
Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF);
error= result->view_structure_only() ? false : do_select(this , procedure);
/* Accumulate the counts from all join iterations of all join parts. */
thd->ps_report_examined_row_count();
DBUG_PRINT("counts" , ("thd->examined_row_count: %lu" ,
(ulong) thd->get_examined_row_count()));
DBUG_RETURN(error);
}
/**
Clean up join .
@ return
Return error that hold JOIN .
*/
int
JOIN::destroy()
{
DBUG_ENTER("JOIN::destroy" );
DBUG_PRINT("info" , ("select %p (%u) <> JOIN %p" ,
select_lex, select_lex->select_number, this ));
select_lex->join= 0 ;
cond_equal= 0 ;
having_equal= 0 ;
cleanup(1 );
if (join_tab)
{
for (JOIN_TAB *tab= first_linear_tab(this , WITH_BUSH_ROOTS,
WITH_CONST_TABLES);
tab; tab= next_linear_tab(this , tab, WITH_BUSH_ROOTS))
{
if (tab->aggr)
{
free_tmp_table(thd, tab->table);
delete tab->tmp_table_param;
tab->tmp_table_param= NULL;
tab->aggr= NULL;
}
tab->table= NULL;
}
}
/* Cleanup items referencing temporary table columns */
cleanup_item_list(tmp_all_fields1);
cleanup_item_list(tmp_all_fields3);
destroy_sj_tmp_tables(this );
delete_dynamic(&keyuse);
if (save_qep)
delete (save_qep);
if (ext_keyuses_for_splitting)
delete (ext_keyuses_for_splitting);
delete procedure;
DBUG_RETURN(error);
}
void JOIN::cleanup_item_list(List<Item> &items) const
{
DBUG_ENTER("JOIN::cleanup_item_list" );
if (!items.is_empty())
{
List_iterator_fast<Item> it(items);
Item *item;
while ((item= it++))
item->cleanup();
}
DBUG_VOID_RETURN;
}
/**
@ brief
Look for provision of the select_handler interface by a foreign engine .
Must not be called directly , use find_single_select_handler ( ) or
find_partial_select_handler ( ) instead .
@ param
thd The thread handler
select_lex SELECT_LEX object , must be passed in the cases of :
- single select pushdown
- partial pushdown ( part of a UNION / EXCEPT / INTERSECT )
Must be NULL in case of entire unit pushdown
select_lex_unit SELECT_LEX_UNIT object , must be passed in the cases of :
- entire unit pushdown
- partial pushdown ( part of a UNION / EXCEPT / INTERSECT )
Must be NULL in case of single select pushdown
@ details
The function checks that this is an upper level select and if so looks
through its tables searching for one whose handlerton owns a
create_select call - back function . If the call of this function returns
a select_handler interface object then the server will push the select
query into this engine .
This function does not check if the select has tables from
different engines . Such a check must be done inside each engine ' s
create_select function .
Also the engine ' s create_select function must perform other checks
to make sure the engine can execute the query .
@ retval the found select_handler if the search is successful
0 otherwise
*/
static
select_handler *find_select_handler_inner(THD *thd,
SELECT_LEX *select_lex,
SELECT_LEX_UNIT *select_lex_unit)
{
// Pushdown is not supported for non-top-level SELECTs
if (select_lex->master_unit()->outer_select())
return 0 ;
TABLE_LIST *tbl= nullptr;
// For SQLCOM_INSERT_SELECT the server takes TABLE_LIST
// from thd->lex->query_tables and skips its first table
// b/c it is the target table for the INSERT..SELECT.
if (thd->lex->sql_command != SQLCOM_INSERT_SELECT)
{
tbl= select_lex->join->tables_list;
}
else if (thd->lex->query_tables &&
thd->lex->query_tables->next_global)
{
tbl= thd->lex->query_tables->next_global;
}
else
return 0 ;
for (;tbl; tbl= tbl->next_global)
{
if (!tbl->table)
continue ;
handlerton *ht= tbl->table->file->partition_ht();
if (!ht->create_select)
continue ;
select_handler *sh= ht->create_select(thd, select_lex, select_lex_unit);
if (sh)
return sh;
}
return 0 ;
}
/**
Wrapper for find_select_handler_inner ( ) for the case of single select
pushdown . See more comments at the description of
find_select_handler_inner ( )
*/
select_handler *find_single_select_handler(THD *thd, SELECT_LEX *select_lex)
{
return find_select_handler_inner(thd, select_lex, nullptr);
}
/**
Wrapper for find_select_handler_inner ( ) for the case of partial select
pushdown . Partial pushdown means that a unit ( i . e . multiple selects combined
with UNION / EXCEPT / INTERSECT operators ) cannot be pushed down to
the storage engine as a whole but some particular selects of this unit can .
For example ,
SELECT a FROM federated . t1 - - can be pushed down to Federated
UNION
SELECT b FROM local . t2 - - cannot be pushed down , executed locally
See more comments at the description of find_select_handler_inner ( )
*/
select_handler *
find_partial_select_handler(THD *thd, SELECT_LEX *select_lex,
SELECT_LEX_UNIT *select_lex_unit)
{
return find_select_handler_inner(thd, select_lex, select_lex_unit);
}
/**
An entry point to single - unit select ( a select without UNION ) .
@ param thd thread handler
@ param rref_pointer_array a reference to ref_pointer_array of
the top - level select_lex for this query
@ param tables list of all tables used in this query .
The tables have been pre - opened .
@ param fields list of items in SELECT list of the top - level
select
e . g . SELECT a , b , c FROM t1 will have Item_field
for a , b and c in this list .
@ param conds top level item of an expression representing
WHERE clause of the top level select
@ param og_num total number of ORDER BY and GROUP BY clauses
arguments
@ param order linked list of ORDER BY arguments
@ param group linked list of GROUP BY arguments
@ param having top level item of HAVING expression
@ param proc_param list of PROCEDUREs
@ param select_options select options ( BIG_RESULT , etc )
@ param result an instance of result set handling class .
This object is responsible for send result
set rows to the client or inserting them
into a table .
@ param select_lex the only SELECT_LEX of this query
@ param unit top - level UNIT of this query
UNIT is an artificial object created by the
parser for every SELECT clause .
e . g .
SELECT * FROM t1 WHERE a1 IN ( SELECT * FROM t2 )
has 2 unions .
@ retval
FALSE success
@ retval
TRUE an error
*/
bool
mysql_select(THD *thd, TABLE_LIST *tables, List<Item> &fields, COND *conds,
uint og_num, ORDER *order, ORDER *group, Item *having,
ORDER *proc_param, ulonglong select_options, select_result *result,
SELECT_LEX_UNIT *unit, SELECT_LEX *select_lex)
{
int err= 0 ;
bool free_join= 1 , exec_error= 0 ;
DBUG_ENTER("mysql_select" );
if (!fields.is_empty())
select_lex->context.resolve_in_select_list= true ;
JOIN *join;
if (select_lex->join != 0 )
{
join= select_lex->join;
/*
is it single SELECT in derived table , called in derived table
creation
*/
if (select_lex->get_linkage() != DERIVED_TABLE_TYPE ||
(select_options & SELECT_DESCRIBE))
{
if (select_lex->get_linkage() != GLOBAL_OPTIONS_TYPE)
{
/*
Original join tabs might be overwritten at first
subselect execution . So we need to restore them .
*/
Item_subselect *subselect= select_lex->master_unit()->item;
if (subselect && subselect->is_uncacheable() && join->reinit())
DBUG_RETURN(TRUE );
}
else
{
if (!join->prepared &&
(err= join->prepare(tables, conds, og_num, order, false , group,
having, proc_param, select_lex, unit)))
{
goto err;
}
}
}
free_join= 0 ;
join->select_options= select_options;
}
else
{
if (thd->lex->describe)
select_options|= SELECT_DESCRIBE;
/*
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)
free_join= 0 ;
if (!(join= new (thd->mem_root) JOIN(thd, fields, select_options, result)))
DBUG_RETURN(TRUE );
THD_STAGE_INFO(thd, stage_init);
thd->lex->used_tables=0 ;
if (!join->prepared &&
(err= join->prepare(tables, conds, og_num, order, false , group, having,
proc_param, select_lex, unit)))
{
goto err;
}
}
thd->get_stmt_da()->reset_current_row_for_warning(1 );
if (thd->lex->prepare_unreferenced_in_with_clauses())
goto err;
/* Look for a table owned by an engine with the select_handler interface */
select_lex->pushdown_select= find_single_select_handler(thd, select_lex);
if ((err= join->optimize()))
{
goto err; // 1
}
if (thd->lex->describe & DESCRIBE_EXTENDED)
{
join->conds_history= join->conds;
join->having_history= (join->having?join->having:join->tmp_having);
}
if (unlikely(thd->is_error()))
goto err;
exec_error= join->exec();
if (thd->lex->describe & DESCRIBE_EXTENDED)
{
select_lex->where= join->conds_history;
select_lex->having= join->having_history;
}
err:
thd->push_final_warnings();
if (select_lex->pushdown_select)
{
delete select_lex->pushdown_select;
select_lex->pushdown_select= NULL;
}
if (free_join)
{
THD_STAGE_INFO(thd, stage_end);
err|= (int )(select_lex->cleanup());
DBUG_RETURN(exec_error || err || thd->is_error());
}
DBUG_RETURN(exec_error || err);
}
/**
Approximate how many records are going to be returned by this table in this
select with this key .
@ param thd Thread handle
@ param select Select to be examined
@ param table The table of interest
@ param keys The keys of interest
@ param limit Maximum number of rows of interest
@ param quick_count Pointer to where we want the estimate written
@ return Status
@ retval false Success
@ retval true Error
*/
static bool get_quick_record_count(THD *thd, SQL_SELECT *select,
TABLE *table,
const key_map *keys,ha_rows limit,
ha_rows *quick_count)
{
quick_select_return error;
DBUG_ENTER("get_quick_record_count" );
uchar buff[STACK_BUFF_ALLOC];
if (unlikely(check_stack_overrun(thd, STACK_MIN_SIZE, buff)))
DBUG_RETURN(false ); // Fatal error flag is set
DEBUG_SYNC(thd, "before_get_quick_record_count" );
if (select)
{
select->head=table;
table->reginfo.impossible_range=0 ;
/*
EQ_FUNC and EQUAL_FUNC already sent unusable key notes ( if any )
during update_ref_and_keys ( ) . Have only other functions raise notes
from can_optimize_scalar_range ( ) .
*/
error= select->test_quick_select(thd, *(key_map *)keys, (table_map) 0 ,
limit, 0 , FALSE ,
TRUE , /* remove_where_parts*/
FALSE ,
Item_func::BITMAP_EXCEPT_ANY_EQUALITY);
if (error == SQL_SELECT::OK)
{
if (select->quick)
{
/*
opt_range_condition_rows was updated in test_quick_select to be
the smallest number of rows in any range .
select - > quick - > records is the number of rows in range with
smallest cost .
*/
DBUG_ASSERT(select->quick->records >=
table->opt_range_condition_rows);
*quick_count= select->quick->records;
}
DBUG_RETURN(false );
}
if (error == SQL_SELECT::IMPOSSIBLE_RANGE)
{
table->reginfo.impossible_range=1 ;
*quick_count= 0 ;
DBUG_RETURN(false );
}
if (unlikely(error == SQL_SELECT::ERROR))
DBUG_RETURN(true );
DBUG_PRINT("warning" ,("Couldn't use record count on const keypart" ));
}
*quick_count= HA_POS_ERROR;
DBUG_RETURN(false ); /* This shouldn't happen */
}
/*
This structure is used to collect info on potentially sargable
predicates in order to check whether they become sargable after
reading const tables .
We form a bitmap of indexes that can be used for sargable predicates .
Only such indexes are involved in range analysis .
*/
struct SARGABLE_PARAM
{
Field *field; /* field against which to check sargability */
Item **arg_value; /* values of potential keys for lookups */
uint num_values; /* number of values in the above array */
};
/*
Mark all tables inside a join nest as constant .
@ detail This is called when there is a local " Impossible WHERE " inside
a multi - table LEFT JOIN .
*/
void mark_join_nest_as_const(JOIN *join,
TABLE_LIST *join_nest,
table_map *found_const_table_map,
uint *const_count)
{
List_iterator<TABLE_LIST> it(join_nest->nested_join->join_list);
TABLE_LIST *tbl;
Json_writer_object emb_obj(join->thd);
Json_writer_object trace_obj(join->thd, "mark_join_nest_as_const" );
Json_writer_array trace_array(join->thd, "members" );
while ((tbl= it++))
{
if (tbl->nested_join)
{
mark_join_nest_as_const(join, tbl, found_const_table_map, const_count);
continue ;
}
JOIN_TAB *tab= tbl->table->reginfo.join_tab;
if (!(join->const_table_map & tab->table->map))
{
tab->type= JT_CONST;
tab->info= ET_IMPOSSIBLE_ON_CONDITION;
tab->table->const_table= 1 ;
join->const_table_map|= tab->table->map;
*found_const_table_map|= tab->table->map;
set_position(join,(*const_count)++,tab,(KEYUSE*) 0 );
mark_as_null_row(tab->table); // All fields are NULL
trace_array.add_table_name(tab->table);
}
}
}
/*
@ brief Get the condition that can be used to do range analysis / partition
pruning / etc
@ detail
Figure out which condition we can use :
- For INNER JOIN , we use the WHERE ,
- " t1 LEFT JOIN t2 ON . . . " uses t2 ' s ON expression
- " t1 LEFT JOIN ( . . . ) ON . . . " uses the join nest ' s ON expression .
*/
static Item **get_sargable_cond(JOIN *join, TABLE *table)
{
Item **retval;
if (table->pos_in_table_list->on_expr)
{
/*
This is an inner table from a single - table LEFT JOIN , " t1 LEFT JOIN
t2 ON cond " . Use the condition cond .
*/
retval= &table->pos_in_table_list->on_expr;
}
else if (table->pos_in_table_list->embedding &&
!table->pos_in_table_list->embedding->sj_on_expr)
{
/*
This is the inner side of a multi - table outer join . Use the
appropriate ON expression .
*/
retval= &(table->pos_in_table_list->embedding->on_expr);
}
else
{
/* The table is not inner wrt some LEFT JOIN. Use the WHERE clause */
retval= &join->conds;
}
return retval;
}
/**
Calculate the best possible join and initialize the join structure .
@ retval
0 ok
@ retval
1 Fatal error
*/
static bool
make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list,
DYNAMIC_ARRAY *keyuse_array)
{
int error= 0 ;
uint i,table_count,const_count,key;
uint sort_space;
table_map found_const_table_map, all_table_map;
key_map const_ref, eq_part;
bool has_expensive_keyparts;
TABLE **table_vector;
JOIN_TAB *stat,*stat_end,*s,**stat_ref, **stat_vector;
KEYUSE *keyuse,*start_keyuse;
table_map outer_join=0 ;
table_map no_rows_const_tables= 0 ;
SARGABLE_PARAM *sargables= 0 ;
List_iterator<TABLE_LIST> ti(tables_list);
TABLE_LIST *tables;
THD *thd= join->thd;
DBUG_ENTER("make_join_statistics" );
table_count=join->table_count;
const uint sj_nests= join->select_lex->sj_nests.elements; // Changed by pull-out
/*
best_extension_by_limited_search need sort space for 2 POSITIION
objects per remaining table , which gives us
2 * ( T + T - 1 + T - 2 + T - 3 . . . 1 POSITIONS ) = 2 * ( T + 1 ) / 2 * T = ( T * T + T )
*/
join->sort_space= sort_space= (table_count*table_count + table_count);
/*
best_positions is ok to allocate with alloc ( ) as we copy things to it with
memcpy ( )
*/
if (!multi_alloc_root(join->thd->mem_root,
&stat, sizeof (JOIN_TAB)*(table_count),
&stat_ref, sizeof (JOIN_TAB*)* MAX_TABLES,
&stat_vector, sizeof (JOIN_TAB*)* (table_count +1 ),
&table_vector, sizeof (TABLE*)*(table_count*2 ),
&join->positions, sizeof (POSITION)*(table_count + 1 ),
&join->sort_positions, sizeof (POSITION)*(sort_space),
&join->best_positions,
sizeof (POSITION)*(table_count + 1 ),
NullS))
DBUG_RETURN(1 );
/* The following should be optimized to only clear critical things */
bzero((void *)stat, sizeof (JOIN_TAB)* table_count);
join->top_join_tab_count= table_count;
/* Initialize POSITION objects */
for (i=0 ; i <= table_count ; i++)
(void ) new ((char *) (join->positions + i)) POSITION;
for (i=0 ; i < sort_space ; i++)
(void ) new ((char *) (join->sort_positions + i)) POSITION;
join->best_ref= stat_vector;
stat_end=stat+table_count;
found_const_table_map= all_table_map=0 ;
const_count=0 ;
for (s= stat, i= 0 ; (tables= ti++); s++, i++)
{
TABLE_LIST *embedding= tables->embedding;
TABLE *table= tables->table;
stat_vector[i]=s;
table_vector[i]= s->table= table;
s->tab_list= tables;
table->pos_in_table_list= tables;
error= tables->fetch_number_of_rows();
/* Calculate table->use_stat_records */
set_statistics_for_table(join->thd, table);
bitmap_clear_all(&table->cond_set);
#ifdef WITH_PARTITION_STORAGE_ENGINE
const bool all_partitions_pruned_away= table->all_partitions_pruned_away;
#else
const bool all_partitions_pruned_away= FALSE ;
#endif
DBUG_EXECUTE_IF("bug11747970_raise_error" ,
{ join->thd->set_killed(KILL_QUERY_HARD); });
if (unlikely(error))
{
table->file->print_error(error, MYF(0 ));
goto error;
}
table->opt_range_keys.clear_all();
table->intersect_keys.clear_all();
table->reginfo.join_tab=s;
table->reginfo.not_exists_optimize=0 ;
bzero(table->const_key_parts, sizeof (key_part_map)*table->s->total_keys);
all_table_map|= table->map;
s->preread_init_done= FALSE ;
s->join=join;
s->dependent= tables->dep_tables;
if (tables->schema_table)
{
/*
Information schema is slow and we don ' t know how many rows we will
find . Be setting a moderate amount of rows we are more likely
to have it materialized if needed .
*/
table->file->stats.records= table->used_stat_records= 100 ;
}
table->opt_range_condition_rows= table->stat_records();
s->on_expr_ref= &tables->on_expr;
if (*s->on_expr_ref)
{
/* s is the only inner table of an outer join */
if (!table->is_filled_at_execution() &&
((!table->file->stats.records &&
(table->file->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT)) ||
all_partitions_pruned_away) && !embedding)
{ // Empty table
s->dependent= 0 ; // Ignore LEFT JOIN depend.
no_rows_const_tables |= table->map;
set_position(join,const_count++,s,(KEYUSE*) 0 );
continue ;
}
outer_join|= table->map;
s->embedding_map= 0 ;
for (;embedding; embedding= embedding->embedding)
s->embedding_map|= embedding->nested_join->nj_map;
continue ;
}
if (embedding)
{
/* s belongs to a nested join, maybe to several embedded joins */
s->embedding_map= 0 ;
bool inside_an_outer_join= FALSE ;
do
{
/*
If this is a semi - join nest , skip it , and proceed upwards . Maybe
we ' re in some outer join nest
*/
if (embedding->sj_on_expr)
{
embedding= embedding->embedding;
continue ;
}
inside_an_outer_join= TRUE ;
NESTED_JOIN *nested_join= embedding->nested_join;
s->embedding_map|=nested_join->nj_map;
s->dependent|= embedding->dep_tables;
embedding= embedding->embedding;
outer_join|= nested_join->used_tables;
}
while (embedding);
if (inside_an_outer_join)
continue ;
}
if (!table->is_filled_at_execution() &&
(table->s->system ||
(table->file->stats.records <= 1 &&
(table->file->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT)) ||
all_partitions_pruned_away) &&
!s->dependent &&
!table->fulltext_searched && !join->no_const_tables)
{
set_position(join,const_count++,s,(KEYUSE*) 0 );
no_rows_const_tables |= table->map;
}
/* SJ-Materialization handling: */
if (table->pos_in_table_list->jtbm_subselect &&
table->pos_in_table_list->jtbm_subselect->is_jtbm_const_tab)
{
set_position(join,const_count++,s,(KEYUSE*) 0 );
no_rows_const_tables |= table->map;
table->file->stats.records= 0 ;
}
}
stat_vector[i]=0 ;
join->outer_join=outer_join;
if (join->propagate_dependencies(stat))
{
// Illegal cross-references found
table_count= 0 ;
my_message(ER_WRONG_OUTER_JOIN, ER_THD(thd, ER_WRONG_OUTER_JOIN), MYF(0 ));
goto error;
}
{
for (JOIN_TAB *s= stat ; s < stat_end ; s++)
{
TABLE_LIST *tl= s->table->pos_in_table_list;
if (tl->embedding && tl->embedding->sj_subq_pred)
{
s->embedded_dependent= tl->embedding->original_subq_pred_used_tables;
}
}
}
if (join->conds || outer_join)
{
if (update_ref_and_keys(thd, keyuse_array, stat, join->table_count,
join->conds, ~outer_join, join->select_lex, &sargables))
goto error;
/*
Keyparts without prefixes may be useful if this JOIN is a subquery , and
if the subquery may be executed via the IN - EXISTS strategy .
*/
bool skip_unprefixed_keyparts=
!(join->is_in_subquery() &&
join->unit->item->get_IN_subquery()->test_strategy(SUBS_IN_TO_EXISTS));
if (keyuse_array->elements &&
sort_and_filter_keyuse(join, keyuse_array,
skip_unprefixed_keyparts))
goto error;
DBUG_EXECUTE("opt" , print_keyuse_array(keyuse_array););
if (unlikely(thd->trace_started()))
print_keyuse_array_for_trace(thd, keyuse_array);
}
join->const_table_map= no_rows_const_tables;
join->const_tables= const_count;
eliminate_tables(join);
join->const_table_map &= ~no_rows_const_tables;
const_count= join->const_tables;
found_const_table_map= join->const_table_map;
/* Read tables with 0 or 1 rows (system tables) */
for (POSITION *p_pos=join->positions, *p_end=p_pos+const_count;
p_pos < p_end ;
p_pos++)
{
s= p_pos->table;
if (! (s->table->map & join->eliminated_tables))
{
int tmp;
s->type=JT_SYSTEM;
join->const_table_map|=s->table->map;
if ((tmp=join_read_const_table(join->thd, s, p_pos)))
{
if (tmp > 0 )
goto error; // Fatal error
}
else
{
found_const_table_map|= s->table->map;
s->table->pos_in_table_list->optimized_away= TRUE ;
}
}
}
/* loop until no more const tables are found */
int ref_changed;
do
{
ref_changed = 0 ;
more_const_tables_found:
/*
We only have to loop from stat_vector + const_count as
set_position ( ) will move all const_tables first in stat_vector
*/
for (JOIN_TAB **pos=stat_vector+const_count ; (s= *pos) ; pos++)
{
TABLE *table= s->table;
if (table->is_filled_at_execution())
continue ;
/*
If equi - join condition by a key is null rejecting and after a
substitution of a const table the key value happens to be null
then we can state that there are no matches for this equi - join .
*/
if ((keyuse= s->keyuse) && *s->on_expr_ref && !s->embedding_map &&
!(table->map & join->eliminated_tables))
{
/*
When performing an outer join operation if there are no matching rows
for the single row of the outer table all the inner tables are to be
null complemented and thus considered as constant tables .
Here we apply this consideration to the case of outer join operations
with a single inner table only because the case with nested tables
would require a more thorough analysis .
TODO . Apply single row substitution to null complemented inner tables
for nested outer join operations .
*/
while (keyuse->table == table)
{
if (!keyuse->is_for_hash_join() &&
!(keyuse->val->used_tables() & ~join->const_table_map) &&
keyuse->val->is_null() && keyuse->null_rejecting)
{
s->type= JT_CONST;
s->table->const_table= 1 ;
mark_as_null_row(table);
found_const_table_map|= table->map;
join->const_table_map|= table->map;
set_position(join,const_count++,s,(KEYUSE*) 0 );
goto more_const_tables_found;
}
keyuse++;
}
}
if (s->dependent) // If dependent on some table
{
// All dep. must be constants
if (s->dependent & ~(found_const_table_map))
continue ;
if (table->file->stats.records <= 1 L &&
(table->file->ha_table_flags() & HA_STATS_RECORDS_IS_EXACT) &&
!table->pos_in_table_list->embedding &&
!((outer_join & table->map) &&
(*s->on_expr_ref)->is_expensive()))
{ // system table
int tmp= 0 ;
s->type= JT_SYSTEM;
join->const_table_map|=table->map;
set_position(join,const_count++,s,(KEYUSE*) 0 );
if ((tmp= join_read_const_table(join->thd, s,
join->positions+const_count-1 )))
{
if (tmp > 0 )
goto error; // Fatal error
}
else
found_const_table_map|= table->map;
continue ;
}
}
/* check if table can be read by key or table only uses const refs */
if ((keyuse=s->keyuse))
{
s->type= JT_REF;
while (keyuse->table == table)
{
if (keyuse->is_for_hash_join())
{
keyuse++;
continue ;
}
start_keyuse=keyuse;
key=keyuse->key;
s->keys.set_bit(key); // TODO: remove this ?
const_ref.clear_all();
eq_part.clear_all();
has_expensive_keyparts= false ;
do
{
if (keyuse->val->type() != Item::NULL_ITEM &&
!keyuse->optimize &&
keyuse->keypart != FT_KEYPART)
{
if (!((~found_const_table_map) & keyuse->used_tables))
{
const_ref.set_bit(keyuse->keypart);
if (keyuse->val->is_expensive())
has_expensive_keyparts= true ;
}
eq_part.set_bit(keyuse->keypart);
}
keyuse++;
} while (keyuse->table == table && keyuse->key == key);
TABLE_LIST *embedding= table->pos_in_table_list->embedding;
/*
TODO ( low priority ) : currently we ignore the const tables that
are within a semi - join nest which is within an outer join nest .
The effect of this is that we don ' t do const substitution for
such tables .
*/
KEY *keyinfo= table->key_info + key;
uint key_parts= table->actual_n_key_parts(keyinfo);
if (eq_part.is_prefix(key_parts) &&
!table->fulltext_searched &&
(!embedding || (embedding->sj_on_expr && !embedding->embedding)))
{
key_map base_part, base_const_ref, base_eq_part;
base_part.set_prefix(keyinfo->user_defined_key_parts);
base_const_ref= const_ref;
base_const_ref.intersect(base_part);
base_eq_part= eq_part;
base_eq_part.intersect(base_part);
/*
We can read the const record if we are using a full unique key and
if the table is not an unopened to be materialized table / view .
*/
if ((table->actual_key_flags(keyinfo) & HA_NOSAME) &&
(!s->table->pos_in_table_list->is_materialized_derived() ||
s->table->pos_in_table_list->fill_me))
{
if (base_const_ref == base_eq_part &&
!has_expensive_keyparts &&
!((outer_join & table->map) &&
(*s->on_expr_ref)->is_expensive()))
{ // Found everything for ref.
int tmp;
ref_changed = 1 ;
s->type= JT_CONST;
join->const_table_map|=table->map;
set_position(join,const_count++,s,start_keyuse);
/* create_ref_for_key will set s->table->const_table */
if (create_ref_for_key(join, s, start_keyuse, FALSE ,
found_const_table_map))
goto error;
if ((tmp=join_read_const_table(join->thd, s,
join->positions+const_count-1 )))
{
if (tmp > 0 )
goto error; // Fatal error
}
else
found_const_table_map|= table->map;
break ;
}
}
else if (base_const_ref == base_eq_part)
s->const_keys.set_bit(key);
}
}
}
}
} while (ref_changed);
join->sort_by_table= get_sort_by_table(join->order, join->group_list,
join->select_lex->leaf_tables,
join->const_table_map);
join->limit_shortcut_applicable= join_limit_shortcut_is_applicable(join);
/*
Update info on indexes that can be used for search lookups as
reading const tables may has added new sargable predicates .
*/
if (const_count && sargables)
{
for ( ; sargables->field ; sargables++)
{
Field *field= sargables->field;
JOIN_TAB *join_tab= field->table->reginfo.join_tab;
key_map possible_keys= field->key_start;
possible_keys.intersect(field->table->keys_in_use_for_query);
bool is_const= 1 ;
for (uint j=0 ; j < sargables->num_values; j++)
is_const&= sargables->arg_value[j]->const_item();
if (is_const)
join_tab[0 ].const_keys.merge(possible_keys);
}
}
join->impossible_where= false ;
if (join->conds && const_count)
{
Item* &conds= join->conds;
COND_EQUAL *orig_cond_equal = join->cond_equal;
conds->update_used_tables();
conds= conds->remove_eq_conds(join->thd, &join->cond_value, true );
if (conds && conds->type() == Item::COND_ITEM &&
((Item_cond*) conds)->functype() == Item_func::COND_AND_FUNC)
join->cond_equal= &((Item_cond_and*) conds)->m_cond_equal;
join->select_lex->where= conds;
if (join->cond_value == Item::COND_FALSE)
{
join->impossible_where= true ;
conds= (Item*) Item_false;
}
join->cond_equal= NULL;
if (conds)
{
if (conds->type() == Item::COND_ITEM &&
((Item_cond*) conds)->functype() == Item_func::COND_AND_FUNC)
join->cond_equal= (&((Item_cond_and *) conds)->m_cond_equal);
else if (conds->type() == Item::FUNC_ITEM &&
((Item_func*) conds)->functype() == Item_func::MULT_EQUAL_FUNC)
{
if (!join->cond_equal)
join->cond_equal= new COND_EQUAL;
join->cond_equal->current_level.empty();
join->cond_equal->current_level.push_back((Item_equal*) conds,
join->thd->mem_root);
}
}
if (orig_cond_equal != join->cond_equal)
{
/*
If join - > cond_equal has changed all references to it from COND_EQUAL
objects associated with ON expressions must be updated .
*/
for (JOIN_TAB **pos=stat_vector+const_count ; (s= *pos) ; pos++)
{
if (*s->on_expr_ref && s->cond_equal &&
s->cond_equal->upper_levels == orig_cond_equal)
s->cond_equal->upper_levels= join->cond_equal;
}
}
}
join->join_tab= stat;
join->make_notnull_conds_for_range_scans();
/* Calc how many (possible) matched records in each table */
/*
Todo : add a function so that we can add these Json_writer_objects
easily .
Another way would be to enclose them in a scope { } ;
*/
{
Json_writer_object rows_estimation_wrapper(thd);
Json_writer_array rows_estimation(thd, "rows_estimation" );
for (s=stat ; s < stat_end ; s++)
{
s->startup_cost= 0 ;
if (s->type == JT_SYSTEM || s->type == JT_CONST)
{
Json_writer_object table_records(thd);
ha_rows records= 1 ;
if (s->type == JT_SYSTEM || s->table->file->stats.records == 0 )
records= s->table->file->stats.records;
/* zero or one matching row */
s->records= s->found_records= records;
s->records_init= s->records_out= rows2double(records);
s->read_time=1 .0 ;
table_records.add_table_name(s).
add("rows" , s->found_records).
add("cost" , s->read_time).
add("table_type" , s->type == JT_CONST ?
"const" : "system" );
continue ;
}
/*
Approximate found rows and time to read them
Update found_records , records , read_time and other scan related
variables
*/
s->estimate_scan_time();
if (s->table->is_splittable())
s->add_keyuses_for_splitting();
/*
Add to stat - > const_keys those indexes for which all group fields or
all select distinct fields participate in one index .
*/
add_group_and_distinct_keys(join, s);
/* This will be updated in calculate_cond_selectivity_for_table() */
s->table->set_cond_selectivity(1 .0 );
DBUG_ASSERT(s->table->used_stat_records == 0 ||
s->table->cond_selectivity <=
s->table->opt_range_condition_rows /
s->table->used_stat_records);
/*
Perform range analysis if we could infer something from it .
( 1 ) There are indexes for which we have range conditions ,
( 2 ) Or there are sargable conditions on the table ' s columns that we
could use for selectivity estimation ,
( 3 ) Or selectivity estimation via sampling is enabled .
( 4 ) Don ' t do range analysis for materialized subqueries .
( 5 ) Don ' t do range analysis for materialized derived tables / views .
*/
if ((!s->const_keys.is_clear_all() || // (1)
!bitmap_is_clear_all(&s->table->cond_set) || // (2)
thd->variables.optimizer_use_condition_selectivity >= 5 ) && // (3)
!s->table->is_filled_at_execution() && // (4)
!(s->table->pos_in_table_list->derived && // (5)
s->table->pos_in_table_list->is_materialized_derived())) // (5)
{
bool impossible_range= FALSE ;
ha_rows records= HA_ROWS_MAX;
SQL_SELECT *select= 0 ;
Item **sargable_cond= NULL;
if (!s->const_keys.is_clear_all())
{
sargable_cond= get_sargable_cond(join, s->table);
bool is_sargable_cond_of_where= sargable_cond == &join->conds;
select= make_select(s->table, found_const_table_map,
found_const_table_map,
*sargable_cond,
(SORT_INFO*) 0 , 1 , &error);
if (!select)
goto error;
if (get_quick_record_count(join->thd, select, s->table,
&s->const_keys, join->row_limit, &records))
{
/* There was an error in test_quick_select */
delete select;
goto error;
}
/*
Range analyzer might have modified the condition . Put it the new
condition to where we got it from .
*/
*sargable_cond= select->cond;
if (is_sargable_cond_of_where &&
join->conds && join->conds->type() == Item::COND_ITEM &&
((Item_cond*) (join->conds))->functype() ==
Item_func::COND_AND_FUNC)
join->cond_equal= &((Item_cond_and*) (join->conds))->m_cond_equal;
s->quick=select->quick;
select->quick=0 ;
s->needed_reg=select->needed_reg;
impossible_range= records == 0 && s->table->reginfo.impossible_range;
s->table->init_cost_info_for_usable_range_rowid_filters(join->thd);
}
if (!impossible_range)
{
if (!sargable_cond)
sargable_cond= get_sargable_cond(join, s->table);
if (join->thd->variables.optimizer_use_condition_selectivity > 1 )
calculate_cond_selectivity_for_table(join->thd, s->table,
sargable_cond);
if (s->table->reginfo.impossible_range)
{
impossible_range= TRUE ;
records= 0 ;
}
}
if (impossible_range)
{
/*
Impossible WHERE or ON expression
In case of ON , we mark that the we match one empty NULL row .
In case of WHERE , don ' t set found_const_table_map to get the
caller to abort with a zero row result .
*/
TABLE_LIST *emb= s->table->pos_in_table_list->embedding;
if (emb && !emb->sj_on_expr && !*s->on_expr_ref)
{
/* Mark all tables in a multi-table join nest as const */
mark_join_nest_as_const(join, emb, &found_const_table_map,
&const_count);
}
else
{
double records= 1 ;
join->const_table_map|= s->table->map;
set_position(join,const_count++,s,(KEYUSE*) 0 );
s->type= JT_CONST;
s->table->const_table= 1 ;
if (*s->on_expr_ref)
{
/* Generate empty row */
s->info= ET_IMPOSSIBLE_ON_CONDITION;
found_const_table_map|= s->table->map;
mark_as_null_row(s->table); // All fields are NULL
records= 0 ;
}
s->records_init= s->records_out= records;
s->found_records= s->records= (ha_rows)records;
}
}
if (records != HA_POS_ERROR)
{
s->found_records=records;
s->read_time= s->quick ? s->quick->read_time : 0 .0 ;
}
if (select)
delete select;
else
{
if (unlikely(thd->trace_started()))
add_table_scan_values_to_trace(thd, s);
}
}
else
{
if (unlikely(thd->trace_started()))
add_table_scan_values_to_trace(thd, s);
}
}
}
if (pull_out_semijoin_tables(join))
DBUG_RETURN(TRUE );
join->join_tab=stat;
join->top_join_tab_count= table_count;
join->map2table=stat_ref;
join->table= table_vector;
join->const_tables=const_count;
join->found_const_table_map=found_const_table_map;
if (join->select_lex->opt_hints_qb)
join->select_lex->opt_hints_qb->apply_join_order_hints(join);
join->update_key_dependencies();
if (unlikely(thd->trace_started()))
trace_table_dependencies(thd, join->join_tab, join->table_count);
if (sj_nests)
join->select_lex->update_available_semijoin_strategies(thd);
if (join->const_tables != join->table_count)
optimize_keyuse(join, keyuse_array);
DBUG_ASSERT(!join->conds || !join->cond_equal ||
!join->cond_equal->current_level.elements ||
(join->conds->type() == Item::COND_ITEM &&
((Item_cond*) (join->conds))->functype() ==
Item_func::COND_AND_FUNC &&
join->cond_equal ==
&((Item_cond_and *) (join->conds))->m_cond_equal) ||
(join->conds->type() == Item::FUNC_ITEM &&
((Item_func*) (join->conds))->functype() ==
Item_func::MULT_EQUAL_FUNC &&
join->cond_equal->current_level.elements == 1 &&
join->cond_equal->current_level.head() == join->conds));
if (optimize_semijoin_nests(join, all_table_map))
DBUG_RETURN(TRUE ); /* purecov: inspected */
{
SELECT_LEX_UNIT *unit= join->select_lex->master_unit();
/* Find an optimal join order of the non-constant tables. */
if (join->const_tables != join->table_count)
{
if (choose_plan(join, all_table_map & ~join->const_table_map, 0 ))
goto error;
#ifdef HAVE_valgrind
// JOIN::positions holds the current query plan. We've already
// made the plan choice, so we should only use JOIN::best_positions
for (uint k=join->const_tables; k < join->table_count; k++)
MEM_UNDEFINED(&join->positions[k], sizeof (join->positions[k]));
#endif
}
else
{
memcpy((uchar*) join->best_positions,(uchar*) join->positions,
sizeof (POSITION)*join->const_tables);
join->join_record_count= 1 .0 ;
/* Const tables are part of optimizer setup and not counted in cost */
join->best_read=0 .0 ;
}
if (!(join->select_options & SELECT_DESCRIBE) &&
unit->derived && unit->derived->is_materialized_derived())
{
/*
Calculate estimated number of rows for materialized derived
table / view .
*/
double records= 1 .0 ;
ha_rows rows;
for (i= 0 ; i < join->table_count ; i++)
if (double rr= join->best_positions[i].records_read)
records= COST_MULT(records, rr);
if (join->group_list)
records= estimate_post_group_cardinality(join, records);
rows= double_to_rows(records);
set_if_smaller(rows, unit->lim.get_select_limit());
join->select_lex->increase_derived_records(rows);
}
}
if (join->choose_subquery_plan(all_table_map & ~join->const_table_map))
goto error;
DEBUG_SYNC(join->thd, "inside_make_join_statistics" );
DBUG_RETURN(0 );
error:
/*
Need to clean up join_tab from TABLEs in case of error .
They won ' t get cleaned up by JOIN : : cleanup ( ) because JOIN : : join_tab
may not be assigned yet by this function ( which is building join_tab ) .
Dangling TABLE : : reginfo . join_tab may cause part_of_refkey to choke .
*/
{
TABLE_LIST *tmp_table;
List_iterator<TABLE_LIST> ti2(tables_list);
while ((tmp_table= ti2++))
tmp_table->table->reginfo.join_tab= NULL;
}
DBUG_RETURN (1 );
}
/*
Propagate dependencies between tables .
@ returns false if success , true if error
Build transitive closure for relation ' to be dependent on ' .
This will speed up the plan search for many cases with outer joins ,
as well as allow us to catch illegal cross references /
Warshall ' s algorithm is used to build the transitive closure .
As we use bitmaps to represent the relation the complexity
of the algorithm is O ( ( number of tables ) ^ 2 ) .
The classic form of the Warshall ' s algorithm would look like :
for ( i = 0 ; i < table_count ; i + + )
{
for ( j = 0 ; j < table_count ; j + + )
{
for ( k = 0 ; k < table_count ; k + + )
{
if ( bitmap_is_set ( stat [ j ] . dependent , i ) & &
bitmap_is_set ( stat [ i ] . dependent , k ) )
bitmap_set_bit ( stat [ j ] . dependent , k ) ;
}
}
}
*/
bool JOIN::propagate_dependencies(JOIN_TAB *stat)
{
for (JOIN_TAB *s= stat; s < stat + table_count; s++)
{
TABLE *table= s->table;
if (outer_join & s->table->map)
s->table->maybe_null= 1 ;
if (!table->reginfo.join_tab->dependent)
continue ;
// Add my dependencies to other tables depending on me
for (JOIN_TAB *t= stat; t < stat + table_count; t++)
{
if (t->dependent & table->map)
t->dependent |= table->reginfo.join_tab->dependent;
}
}
// Catch illegal cross references
for (JOIN_TAB *s= stat; s < stat + table_count; s++)
{
if (s->dependent & s->table->map)
return true ;
}
return false ;
}
void JOIN::update_key_dependencies()
{
for (JOIN_TAB *tab= join_tab; tab < join_tab + table_count; tab++)
tab->key_dependent |= tab->dependent;
}
/*
Export dependencies of the JOIN tables to a newly allocated array of bitmaps
( table_map ' s ) .
This array may be used to restore the original dependencies
( see restore_table_dependencies ( ) )
*/
table_map *JOIN::export_table_dependencies() const
{
table_map *orig_dep_array=
(table_map *)thd->alloc(sizeof (table_map) * table_count);
if (orig_dep_array == nullptr)
return nullptr;
for (uint i= 0 ; i < table_count; i++)
orig_dep_array[i]= join_tab[i].dependent;
return orig_dep_array;
}
/*
Restore dependencies of the JOIN tables from a previously exported array
of bitmaps ( table_map ' s ) ( see export_table_dependencies ( ) ) .
This function overwrites the existing dependencies with those from the array .
*/
void JOIN::restore_table_dependencies(table_map *orig_dep_array)
{
for (uint i = 0 ; i < table_count; i++)
join_tab[i].dependent= orig_dep_array[i];
}
/*****************************************************************************
Check with keys are used and with tables references with tables
Updates in stat :
keys Bitmap of all used keys
const_keys Bitmap of all keys with may be used with quick_select
keyuse Pointer to possible keys
*****************************************************************************/
/**
Merge new key definitions to old ones , remove those not used in both .
This is called for OR between different levels .
That is , the function operates on an array of KEY_FIELD elements which has
two parts :
$ LEFT_PART $ RIGHT_PART
+ - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - +
start new_fields end
$ LEFT_PART and $ RIGHT_PART are arrays that have KEY_FIELD elements for two
parts of the OR condition . Our task is to produce an array of KEY_FIELD
elements that would correspond to " $ LEFT_PART OR $ RIGHT_PART " .
The rules for combining elements are as follows :
( keyfieldA1 AND keyfieldA2 AND . . . ) OR ( keyfieldB1 AND keyfieldB2 AND . . . ) =
= AND_ij ( keyfieldA_i OR keyfieldB_j )
We discard all ( keyfieldA_i OR keyfieldB_j ) that refer to different
fields . For those referring to the same field , the logic is as follows :
t . keycol = expr1 OR t . keycol = expr2 - > ( since expr1 and expr2 are different
we can ' t produce a single equality ,
so produce nothing )
t . keycol = expr1 OR t . keycol = expr1 - > t . keycol = expr1
t . keycol = expr1 OR t . keycol IS NULL - > t . keycol = expr1 , and also set
KEY_OPTIMIZE_REF_OR_NULL flag
The last one is for ref_or_null access . We have handling for this special
because it ' s needed for evaluating IN subqueries that are internally
transformed into
@ code
EXISTS ( SELECT * FROM t1 WHERE t1 . key = outer_ref_field or t1 . key IS NULL )
@ endcode
See add_key_fields ( ) for discussion of what is and_level .
KEY_FIELD : : null_rejecting is processed as follows : @ n
result has null_rejecting = true if it is set for both ORed references .
for example :
- ( t2 . key = t1 . field OR t2 . key = t1 . field ) - > null_rejecting = true
- ( t2 . key = t1 . field OR t2 . key < = > t1 . field ) - > null_rejecting = false
@ todo
The result of this is that we ' re missing some ' ref ' accesses .
OptimizerTeam : Fix this
*/
static KEY_FIELD *
merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end,
uint and_level)
{
if (start == new_fields)
return start; // Impossible or
if (new_fields == end)
return start; // No new fields, skip all
KEY_FIELD *first_free=new_fields;
/* Mark all found fields in old array */
for (; new_fields != end ; new_fields++)
{
for (KEY_FIELD *old=start ; old != first_free ; old++)
{
if (old->field == new_fields->field)
{
/*
NOTE : below const_item ( ) call really works as " ! used_tables ( ) " , i . e .
it can return FALSE where it is feasible to make it return TRUE .
The cause is as follows : Some of the tables are already known to be
const tables ( the detection code is in make_join_statistics ( ) ,
above the update_ref_and_keys ( ) call ) , but we didn ' t propagate
information about this : TABLE : : const_table is not set to TRUE , and
Item : : update_used_tables ( ) hasn ' t been called for each item .
The result of this is that we ' re missing some ' ref ' accesses .
TODO : OptimizerTeam : Fix this
*/
if (!new_fields->val->const_item())
{
/*
If the value matches , we can use the key reference .
If not , we keep it until we have examined all new values
*/
if (old->val->eq(new_fields->val, old->field->binary()))
{
old->level= and_level;
old->optimize= ((old->optimize & new_fields->optimize &
KEY_OPTIMIZE_EXISTS) |
((old->optimize | new_fields->optimize) &
KEY_OPTIMIZE_REF_OR_NULL));
old->null_rejecting= (old->null_rejecting &&
new_fields->null_rejecting);
}
}
else if (old->eq_func && new_fields->eq_func &&
old->val->eq_by_collation(new_fields->val,
old->field->binary(),
old->field->charset()))
{
old->level= and_level;
old->optimize= ((old->optimize & new_fields->optimize &
KEY_OPTIMIZE_EXISTS) |
((old->optimize | new_fields->optimize) &
KEY_OPTIMIZE_REF_OR_NULL));
old->null_rejecting= (old->null_rejecting &&
new_fields->null_rejecting);
}
else if (old->eq_func && new_fields->eq_func &&
((old->val->can_eval_in_optimize() && old->val->is_null()) ||
(!new_fields->val->is_expensive() &&
new_fields->val->is_null())))
{
/* field = expression OR field IS NULL */
old->level= and_level;
if (old->field->maybe_null())
{
old->optimize= KEY_OPTIMIZE_REF_OR_NULL;
/* The referred expression can be NULL: */
old->null_rejecting= 0 ;
}
/*
Remember the NOT NULL value unless the value does not depend
on other tables .
*/
if (!old->val->used_tables() && !old->val->is_expensive() &&
old->val->is_null())
old->val= new_fields->val;
}
else
{
/*
We are comparing two different const . In this case we can ' t
use a key - lookup on this so it ' s better to remove the value
and let the range optimzier handle it
*/
if (old == --first_free) // If last item
break ;
*old= *first_free; // Remove old value
old--; // Retry this value
}
}
}
}
/* Remove all not used items */
for (KEY_FIELD *old=start ; old != first_free ;)
{
if (old->level != and_level)
{ // Not used in all levels
if (old == --first_free)
break ;
*old= *first_free; // Remove old value
continue ;
}
old++;
}
return first_free;
}
/*
Given a field , return its index in semi - join ' s select list , or UINT_MAX
DESCRIPTION
Given a field , we find its table ; then see if the table is within a
semi - join nest and if the field was in select list of the subselect .
If it was , we return field ' s index in the select list . The value is used
by LooseScan strategy .
*/
static uint get_semi_join_select_list_index(Field *field)
{
uint res= UINT_MAX;
TABLE_LIST *emb_sj_nest;
if ((emb_sj_nest= field->table->pos_in_table_list->embedding) &&
emb_sj_nest->sj_on_expr)
{
Item_in_subselect *subq_pred= emb_sj_nest->sj_subq_pred;
st_select_lex *subq_lex= subq_pred->unit->first_select();
uint ncols= subq_pred->left_exp()->cols();
if (ncols == 1 )
{
Item *sel_item= subq_lex->ref_pointer_array[0 ];
if (sel_item->type() == Item::FIELD_ITEM &&
((Item_field*)sel_item)->field->eq(field))
{
res= 0 ;
}
}
else
{
for (uint i= 0 ; i < ncols; i++)
{
Item *sel_item= subq_lex->ref_pointer_array[i];
if (sel_item->type() == Item::FIELD_ITEM &&
((Item_field*)sel_item)->field->eq(field))
{
res= i;
break ;
}
}
}
}
return res;
}
/**
Add a possible key to array of possible keys if it ' s usable as a key
@ param key_fields Pointer to add key , if usable
@ param and_level And level , to be stored in KEY_FIELD
@ param cond Condition predicate
@ param field Field used in comparision
@ param eq_func True if we used = , < = > or IS NULL
@ param value Value used for comparison with field
@ param num_values Number of values [ ] that we are comparing against
@ param usable_tables Tables which can be used for key optimization
@ param sargables IN / OUT Array of found sargable candidates
@ param row_col_no if = n that > 0 then field is compared only
against the n - th component of row values
@ note
If we are doing a NOT NULL comparison on a NOT NULL field in a outer join
table , we store this to be able to do not exists optimization later .
@ returns
* key_fields is incremented if we stored a key in the array
*/
static void
add_key_field(JOIN *join,
KEY_FIELD **key_fields,uint and_level, Item_bool_func *cond,
Field *field, bool eq_func, Item **value, uint num_values,
table_map usable_tables, SARGABLE_PARAM **sargables,
uint row_col_no= 0 )
{
uint optimize= 0 ;
if (eq_func &&
((join->is_allowed_hash_join_access(field->table) &&
field->hash_join_is_possible() &&
!(field->table->pos_in_table_list->is_materialized_derived() &&
field->table->is_created())) ||
(field->table->pos_in_table_list->is_materialized_derived() &&
!field->table->is_created() && !(field->flags & BLOB_FLAG))))
{
optimize= KEY_OPTIMIZE_EQ;
}
else if (!(field->flags & PART_KEY_FLAG))
{
// Don't remove column IS NULL on a LEFT JOIN table
if (eq_func && (*value)->type() == Item::NULL_ITEM &&
field->table->maybe_null && !field->null_ptr)
{
optimize= KEY_OPTIMIZE_EXISTS;
DBUG_ASSERT(num_values == 1 );
}
}
if (optimize != KEY_OPTIMIZE_EXISTS)
{
table_map used_tables=0 ;
bool optimizable=0 ;
for (uint i=0 ; i<num_values; i++)
{
Item *curr_val;
if (row_col_no && value[i]->real_item()->type() == Item::ROW_ITEM)
{
Item_row *value_tuple= (Item_row *) (value[i]->real_item());
curr_val= value_tuple->element_index(row_col_no - 1 );
}
else
curr_val= value[i];
table_map value_used_tables= curr_val->used_tables();
used_tables|= value_used_tables;
if (!(value_used_tables & (field->table->map | RAND_TABLE_BIT)))
optimizable=1 ;
}
if (!optimizable)
return ;
if (!(usable_tables & field->table->map))
{
if (!eq_func || (*value)->type() != Item::NULL_ITEM ||
!field->table->maybe_null || field->null_ptr)
return ; // Can't use left join optimize
optimize= KEY_OPTIMIZE_EXISTS;
}
else
{
JOIN_TAB *stat=field->table->reginfo.join_tab;
key_map possible_keys=field->get_possible_keys();
possible_keys.intersect(field->table->keys_in_use_for_query);
stat[0 ].keys.merge(possible_keys); // Add possible keys
/*
Save the following cases :
Field op constant
Field LIKE constant where constant doesn ' t start with a wildcard
Field = field2 where field2 is in a different table
Field op formula
Field IS NULL
Field IS NOT NULL
Field BETWEEN . . .
Field IN . . .
*/
if (field->flags & PART_KEY_FLAG)
{
stat[0 ].key_dependent|= used_tables;
if (field->key_start.bits_set())
stat[0 ].key_start_dependent= 1 ;
}
bool is_const=1 ;
for (uint i=0 ; i<num_values; i++)
{
Item *curr_val;
if (row_col_no && value[i]->real_item()->type() == Item::ROW_ITEM)
{
Item_row *value_tuple= (Item_row *) (value[i]->real_item());
curr_val= value_tuple->element_index(row_col_no - 1 );
}
else
curr_val= value[i];
if (!(is_const&= curr_val->const_item()))
break ;
}
if (is_const)
{
stat[0 ].const_keys.merge(possible_keys);
bitmap_set_bit(&field->table->cond_set, field->field_index);
}
else if (!eq_func)
{
/*
Save info to be able check whether this predicate can be
considered as sargable for range analysis after reading const tables .
We do not save info about equalities as update_const_equal_items
will take care of updating info on keys from sargable equalities .
*/
(*sargables)--;
(*sargables)->field= field;
(*sargables)->arg_value= value;
(*sargables)->num_values= num_values;
}
if (!eq_func) // eq_func is NEVER true when num_values > 1
return ;
}
}
/*
For the moment eq_func is always true . This slot is reserved for future
extensions where we want to remembers other things than just eq comparisons
*/
DBUG_ASSERT(eq_func);
/* Store possible eq field */
(*key_fields)->field= field;
(*key_fields)->eq_func= eq_func;
(*key_fields)->val= *value;
(*key_fields)->cond= cond;
(*key_fields)->level= and_level;
(*key_fields)->optimize= optimize;
/*
If the condition we are analyzing is NULL - rejecting and at least
one side of the equalities is NULLable , mark the KEY_FIELD object as
null - rejecting . This property is used by :
- add_not_null_conds ( ) to add " column IS NOT NULL " conditions
- best_access_path ( ) to produce better estimates for NULL - able unique keys .
*/
{
if ((cond->functype() == Item_func::EQ_FUNC ||
cond->functype() == Item_func::MULT_EQUAL_FUNC) &&
((*value)->maybe_null() || field->real_maybe_null()))
(*key_fields)->null_rejecting= true ;
else
(*key_fields)->null_rejecting= false ;
}
(*key_fields)->cond_guard= NULL;
(*key_fields)->sj_pred_no= get_semi_join_select_list_index(field);
(*key_fields)++;
}
/**
Add possible keys to array of possible keys originated from a simple
predicate .
@ param key_fields Pointer to add key , if usable
@ param and_level And level , to be stored in KEY_FIELD
@ param cond Condition predicate
@ param field_item Field item used for comparison
@ param eq_func True if we used = , < = > or IS NULL
@ param value Value used for comparison with field_item
@ param num_values Number of values [ ] that we are comparing against
@ param usable_tables Tables which can be used for key optimization
@ param sargables IN / OUT Array of found sargable candidates
@ param row_col_no if = n that > 0 then field is compared only
against the n - th component of row values
@ note
If field items f1 and f2 belong to the same multiple equality and
a key is added for f1 , the same key is added for f2 .
@ returns
* key_fields is incremented if we stored a key in the array
*/
static void
add_key_equal_fields(JOIN *join, KEY_FIELD **key_fields, uint and_level,
Item_bool_func *cond, Item *field_item,
bool eq_func, Item **val,
uint num_values, table_map usable_tables,
SARGABLE_PARAM **sargables, uint row_col_no= 0 )
{
Field *field= ((Item_field *) (field_item->real_item()))->field;
add_key_field(join, key_fields, and_level, cond, field,
eq_func, val, num_values, usable_tables, sargables,
row_col_no);
Item_equal *item_equal= field_item->get_item_equal();
if (item_equal)
{
/*
Add to the set of possible key values every substitution of
the field for an equal field included into item_equal
*/
Item_equal_fields_iterator it(*item_equal);
while (it++)
{
Field *equal_field= it.get_curr_field();
if (!field->eq(equal_field))
{
add_key_field(join, key_fields, and_level, cond, equal_field,
eq_func, val, num_values, usable_tables,
sargables, row_col_no);
}
}
}
}
/**
Check if an expression is a non - outer field .
Checks if an expression is a field and belongs to the current select .
@ param field Item expression to check
@ return boolean
@ retval TRUE the expression is a local field
@ retval FALSE it ' s something else
*/
static bool
is_local_field (Item *field)
{
return field->real_item()->type() == Item::FIELD_ITEM
&& !(field->used_tables() & OUTER_REF_TABLE_BIT)
&& !((Item_field *)field->real_item())->get_depended_from();
}
/*
In this and other functions , and_level is a number that is ever - growing
and is different for the contents of every AND or OR clause . For example ,
when processing clause
( a AND b AND c ) OR ( x AND y )
we ' ll have
* KEY_FIELD elements for ( a AND b AND c ) are assigned and_level = 1
* KEY_FIELD elements for ( x AND y ) are assigned and_level = 2
* OR operation is performed , and whatever elements are left after it are
assigned and_level = 3 .
The primary reason for having and_level attribute is the OR operation which
uses and_level to mark KEY_FIELDs that should get into the result of the OR
operation
*/
void
Item_cond_and::add_key_fields(JOIN *join, KEY_FIELD **key_fields,
uint *and_level, table_map usable_tables,
SARGABLE_PARAM **sargables)
{
List_iterator_fast<Item> li(*argument_list());
KEY_FIELD *org_key_fields= *key_fields;
Item *item;
while ((item=li++))
item->add_key_fields(join, key_fields, and_level, usable_tables,
sargables);
for (; org_key_fields != *key_fields ; org_key_fields++)
org_key_fields->level= *and_level;
}
void
Item_cond::add_key_fields(JOIN *join, KEY_FIELD **key_fields,
uint *and_level, table_map usable_tables,
SARGABLE_PARAM **sargables)
{
List_iterator_fast<Item> li(*argument_list());
KEY_FIELD *org_key_fields= *key_fields;
(*and_level)++;
(li++)->add_key_fields(join, key_fields, and_level, usable_tables,
sargables);
Item *item;
while ((item=li++))
{
KEY_FIELD *start_key_fields= *key_fields;
(*and_level)++;
item->add_key_fields(join, key_fields, and_level, usable_tables,
sargables);
*key_fields= merge_key_fields(org_key_fields,start_key_fields,
*key_fields, ++(*and_level));
}
}
void
Item_func_trig_cond::add_key_fields(JOIN *join, KEY_FIELD **key_fields,
uint *and_level, table_map usable_tables,
SARGABLE_PARAM **sargables)
{
/*
Subquery optimization : Conditions that are pushed down into subqueries
are wrapped into Item_func_trig_cond . We process the wrapped condition
but need to set cond_guard for KEYUSE elements generated from it .
*/
if (!join->group_list && !join->order &&
join->unit->item &&
join->unit->item->substype() == Item_subselect::IN_SUBS &&
!join->unit->is_unit_op())
{
KEY_FIELD *save= *key_fields;
args[0 ]->add_key_fields(join, key_fields, and_level, usable_tables,
sargables);
// Indicate that this ref access candidate is for subquery lookup:
for (; save != *key_fields; save++)
save->cond_guard= get_trig_var();
}
}
void
Item_func_between::add_key_fields(JOIN *join, KEY_FIELD **key_fields,
uint *and_level, table_map usable_tables,
SARGABLE_PARAM **sargables)
{
/*
Build list of possible keys for ' a BETWEEN low AND high ' .
It is handled similar to the equivalent condition
' a > = low AND a < = high ' :
*/
Item_field *field_item;
bool equal_func= false ;
uint num_values= 2 ;
bool binary_cmp= (args[0 ]->real_item()->type() == Item::FIELD_ITEM)
? ((Item_field*) args[0 ]->real_item())->field->binary()
: true ;
/*
Additional optimization : If ' low = high ' :
Handle as if the condition was " t . key = low " .
*/
if (!negated && args[1 ]->eq(args[2 ], binary_cmp))
{
equal_func= true ;
num_values= 1 ;
}
/*
Append keys for ' field < cmp > value [ ] ' if the
condition is of the form : :
' < field > BETWEEN value [ 1 ] AND value [ 2 ] '
*/
if (is_local_field(args[0 ]))
{
field_item= (Item_field *) (args[0 ]->real_item());
add_key_equal_fields(join, key_fields, *and_level, this ,
field_item, equal_func, &args[1 ],
num_values, usable_tables, sargables);
}
/*
Append keys for ' value [ 0 ] < cmp > field ' if the
condition is of the form :
' value [ 0 ] BETWEEN field1 AND field2 '
*/
for (uint i= 1 ; i <= num_values; i++)
{
if (is_local_field(args[i]))
{
field_item= (Item_field *) (args[i]->real_item());
add_key_equal_fields(join, key_fields, *and_level, this ,
field_item, equal_func, args,
1 , usable_tables, sargables);
}
}
}
void
Item_func_in::add_key_fields(JOIN *join, KEY_FIELD **key_fields,
uint *and_level, table_map usable_tables,
SARGABLE_PARAM **sargables)
{
if (is_local_field(args[0 ]) && !(used_tables() & OUTER_REF_TABLE_BIT))
{
DBUG_ASSERT(arg_count != 2 );
add_key_equal_fields(join, key_fields, *and_level, this ,
(Item_field*) (args[0 ]->real_item()), false ,
args + 1 , arg_count - 1 , usable_tables, sargables);
}
else if (key_item()->type() == Item::ROW_ITEM &&
!(used_tables() & OUTER_REF_TABLE_BIT))
{
Item_row *key_row= (Item_row *) key_item();
Item **key_col= key_row->addr(0 );
uint row_cols= key_row->cols();
for (uint i= 0 ; i < row_cols; i++, key_col++)
{
if (is_local_field(*key_col))
{
Item_field *field_item= (Item_field *)((*key_col)->real_item());
add_key_equal_fields(join, key_fields, *and_level, this ,
field_item, false , args + 1 , arg_count - 1 ,
usable_tables, sargables, i + 1 );
}
}
}
}
void
Item_func_ne::add_key_fields(JOIN *join, KEY_FIELD **key_fields,
uint *and_level, table_map usable_tables,
SARGABLE_PARAM **sargables)
{
if (!(used_tables() & OUTER_REF_TABLE_BIT))
{
/*
QQ : perhaps test for ! is_local_field ( args [ 1 ] ) is not really needed here .
Other comparison functions , e . g . Item_func_le , Item_func_gt , etc ,
do not have this test . See Item_bool_func2 : : add_key_fieldoptimize_op ( ) .
Check with the optimizer team .
*/
if (is_local_field(args[0 ]) && !is_local_field(args[1 ]))
add_key_equal_fields(join, key_fields, *and_level, this ,
(Item_field*) (args[0 ]->real_item()), false ,
&args[1 ], 1 , usable_tables, sargables);
/*
QQ : perhaps test for ! is_local_field ( args [ 0 ] ) is not really needed here .
*/
if (is_local_field(args[1 ]) && !is_local_field(args[0 ]))
add_key_equal_fields(join, key_fields, *and_level, this ,
(Item_field*) (args[1 ]->real_item()), false ,
&args[0 ], 1 , usable_tables, sargables);
}
}
void
Item_func_like::add_key_fields(JOIN *join, KEY_FIELD **key_fields,
uint *and_level, table_map usable_tables,
SARGABLE_PARAM **sargables)
{
if (is_local_field(args[0 ]) && with_sargable_pattern())
{
/*
SELECT * FROM t1 WHERE field LIKE const_pattern
const_pattern starts with a non - wildcard character
*/
add_key_equal_fields(join, key_fields, *and_level, this ,
(Item_field*) args[0 ]->real_item(), false ,
args + 1 , 1 , usable_tables, sargables);
}
}
void
Item_bool_func2::add_key_fields_optimize_op(JOIN *join, KEY_FIELD **key_fields,
uint *and_level,
table_map usable_tables,
SARGABLE_PARAM **sargables,
bool equal_func)
{
/* If item is of type 'field op field/constant' add it to key_fields */
if (is_local_field(args[0 ]))
{
add_key_equal_fields(join, key_fields, *and_level, this ,
(Item_field*) args[0 ]->real_item(), equal_func,
args + 1 , 1 , usable_tables, sargables);
}
else
{
Item_field *field= NULL;
int value_idx= -1 ;
/* Handle SUBSTR(key,1,N)='const', 'const'=SUBSTR(key,1,N), etc */
if (with_sargable_substr(&field, &value_idx))
{
add_key_equal_fields(join, key_fields, *and_level, this , field,
false , args + value_idx, 1 , usable_tables, sargables);
}
}
if (is_local_field(args[1 ]))
{
add_key_equal_fields(join, key_fields, *and_level, this ,
(Item_field*) args[1 ]->real_item(), equal_func,
args, 1 , usable_tables, sargables);
}
}
void
Item_func_truth::add_key_fields(JOIN *join,
KEY_FIELD **key_fields,
uint *and_level,
table_map usable_tables,
SARGABLE_PARAM **sargables)
{
if (is_local_field(args[0 ]))
{
Item *tmp= args[0 ]->type_handler()->create_boolean_false_item(join->thd);
if (unlikely(!tmp))
return ;
add_key_equal_fields(join, key_fields, *and_level, this ,
(Item_field*) args[0 ]->real_item(),
false /*equal_func*/,
&tmp, 1 , usable_tables, sargables);
}
}
void
Item_func_null_predicate::add_key_fields(JOIN *join, KEY_FIELD **key_fields,
uint *and_level,
table_map usable_tables,
SARGABLE_PARAM **sargables)
{
/* column_name IS [NOT] NULL */
if (is_local_field(args[0 ]) && !(used_tables() & OUTER_REF_TABLE_BIT))
{
Item *tmp= new (join->thd->mem_root) Item_null(join->thd);
if (unlikely(!tmp)) // Should never be true
return ;
add_key_equal_fields(join, key_fields, *and_level, this ,
(Item_field*) args[0 ]->real_item(),
functype() == Item_func::ISNULL_FUNC,
&tmp, 1 , usable_tables, sargables);
}
}
void
Item_equal::add_key_fields(JOIN *join, KEY_FIELD **key_fields,
uint *and_level, table_map usable_tables,
SARGABLE_PARAM **sargables)
{
Item *const_item2= get_const();
Item_equal_fields_iterator it(*this );
if (const_item2)
{
/*
For each field field1 from item_equal consider the equality
field1 = const_item as a condition allowing an index access of the table
with field1 by the keys value of field1 .
*/
while (it++)
{
Field *equal_field= it.get_curr_field();
add_key_field(join, key_fields, *and_level, this , equal_field,
TRUE , &const_item2, 1 , usable_tables, sargables);
}
}
else
{
/*
Consider all pairs of different fields included into item_equal .
For each of them ( field1 , field1 ) consider the equality
field1 = field2 as a condition allowing an index access of the table
with field1 by the keys value of field2 .
*/
Item_equal_fields_iterator fi(*this );
while (fi++)
{
Field *field= fi.get_curr_field();
Item *item;
while ((item= it++))
{
Field *equal_field= it.get_curr_field();
if (!field->eq(equal_field))
{
add_key_field(join, key_fields, *and_level, this , field,
TRUE , &item, 1 , usable_tables,
sargables);
}
}
it.rewind();
}
}
}
static inline uint
max_part_bit(key_part_map bits)
{
if (bits == 0 )
return 0 ;
/* find first zero bit by reverting all bits and find first bit */
return my_find_first_bit(~(ulonglong) bits);
}
/**
Add a new keuse to the specified array of KEYUSE objects
@ param [ in , out ] keyuse_array array of keyuses to be extended
@ param [ in ] key_field info on the key use occurrence
@ param [ in ] key key number for the keyse to be added
@ param [ in ] part key part for the keyuse to be added
@ note
The function builds a new KEYUSE object for a key use utilizing the info
on the left and right parts of the given key use extracted from the
structure key_field , the key number and key part for this key use .
The built object is added to the dynamic array keyuse_array .
@ retval 0 the built object is successfully added
@ retval 1 otherwise
*/
static bool
add_keyuse(DYNAMIC_ARRAY *keyuse_array, KEY_FIELD *key_field,
uint key, uint part)
{
KEYUSE keyuse;
Field *field= key_field->field;
keyuse.table= field->table;
keyuse.val= key_field->val;
keyuse.key= key;
if (!is_hash_join_key_no(key))
{
keyuse.keypart=part;
keyuse.keypart_map= (key_part_map) 1 << part;
}
else
{
keyuse.keypart= field->field_index;
keyuse.keypart_map= (key_part_map) 0 ;
}
keyuse.used_tables= key_field->val->used_tables();
keyuse.optimize= key_field->optimize & KEY_OPTIMIZE_REF_OR_NULL;
keyuse.ref_table_rows= 0 ;
keyuse.null_rejecting= key_field->null_rejecting;
keyuse.cond_guard= key_field->cond_guard;
keyuse.sj_pred_no= key_field->sj_pred_no;
keyuse.validity_ref= 0 ;
return (insert_dynamic(keyuse_array,(uchar*) &keyuse));
}
/*
Add all keys with uses ' field ' for some keypart
If field - > and_level ! = and_level then only mark key_part as const_part
RETURN
0 - OK
1 - Out of memory .
*/
static LEX_CSTRING equal_str= { STRING_WITH_LEN("=" ) };
static bool add_key_part(DYNAMIC_ARRAY *keyuse_array, KEY_FIELD *key_field)
{
Field *field=key_field->field;
TABLE *form= field->table;
THD *thd= form->in_use;
if (key_field->eq_func && !(key_field->optimize & KEY_OPTIMIZE_EXISTS))
{
for (uint key=0 ; key < form->s->keys ; key++)
{
if (!(form->keys_in_use_for_query.is_set(key)))
continue ;
if (form->key_info[key].algorithm == HA_KEY_ALG_FULLTEXT ||
form->key_info[key].algorithm == HA_KEY_ALG_RTREE ||
form->key_info[key].algorithm == HA_KEY_ALG_VECTOR ||
form->key_info[key].flags & HA_UNIQUE_HASH)
continue ;
KEY *keyinfo= form->key_info+key;
uint key_parts= form->actual_n_key_parts(keyinfo);
for (uint part=0 ; part < key_parts ; part++)
{
if (field->eq(form->key_info[key].key_part[part].field))
{
Data_type_compatibility compat=
field->can_optimize_keypart_ref(key_field->cond, key_field->val);
if (compat == Data_type_compatibility::OK)
{
if (add_keyuse(keyuse_array, key_field, key, part))
return TRUE ;
}
else if (thd->give_notes_for_unusable_keys())
{
field->raise_note_cannot_use_key_part(thd, key, part,
equal_str,
key_field->cond->compare_collation(),
key_field->val,
compat);
}
}
}
}
/*
Compressed field cannot be part of a key . For optimizer temporary table
compressed fields are replaced by uncompressed , see
is_optimizer_tmp_table ( ) and Field_ * _ compressed : : make_new_field ( ) .
*/
if (!field->compression_method() &&
field->hash_join_is_possible() &&
(key_field->optimize & KEY_OPTIMIZE_EQ) &&
key_field->val->used_tables())
{
if (field->can_optimize_hash_join(key_field->cond, key_field->val) !=
Data_type_compatibility::OK)
return false ;
/*
MDEV - 24931 : For materialized derived tables , don ' t add hash - join
KEYUSE entries beyond max_key_parts ( ) . Excess entries would cause
generate_derived_keys_for_table ( ) to build a key with more parts
than key_part_map ( 64 bits ) or Bitmap < 64 > can represent .
*/
if (form->pos_in_table_list &&
form->pos_in_table_list->is_materialized_derived())
{
uint existing= 0 ;
for (uint k= 0 ; k < keyuse_array->elements; k++)
{
KEYUSE *ku= dynamic_element(keyuse_array, k, KEYUSE*);
if (ku->table == form && is_hash_join_key_no(ku->key))
existing++;
}
if (existing >= form->file->max_key_parts())
return FALSE ;
}
if (form->is_splittable())
form->add_splitting_info_for_key_field(key_field);
/*
If a key use is extracted from an equi - join predicate then it is
added not only as a key use for every index whose component can
be evaluated utilizing this key use , but also as a key use for
hash join . Such key uses are marked with a special key number .
*/
if (add_keyuse(keyuse_array, key_field, get_hash_join_key_no(), 0 ))
return TRUE ;
}
}
return FALSE ;
}
static bool
add_ft_keys(DYNAMIC_ARRAY *keyuse_array,
JOIN_TAB *stat,COND *cond,table_map usable_tables)
{
Item_func_match *cond_func=NULL;
if (!cond)
return FALSE ;
if (cond->type() == Item::FUNC_ITEM)
{
Item_func *func=(Item_func *)cond;
Item_func::Functype functype= func->functype();
if (functype == Item_func::FT_FUNC)
cond_func=(Item_func_match *)cond;
else if (func->argument_count() == 2 )
{
Item *arg0=(Item *)(func->arguments()[0 ]),
*arg1=(Item *)(func->arguments()[1 ]);
if (arg1->const_item() && arg1->cols() == 1 &&
arg0->type() == Item::FUNC_ITEM &&
((Item_func *) arg0)->functype() == Item_func::FT_FUNC &&
((functype == Item_func::GE_FUNC && arg1->val_real() > 0 ) ||
(functype == Item_func::GT_FUNC && arg1->val_real() >=0 )))
cond_func= (Item_func_match *) arg0;
else if (arg0->const_item() && arg0->cols() == 1 &&
arg1->type() == Item::FUNC_ITEM &&
((Item_func *) arg1)->functype() == Item_func::FT_FUNC &&
((functype == Item_func::LE_FUNC && arg0->val_real() > 0 ) ||
(functype == Item_func::LT_FUNC && arg0->val_real() >=0 )))
cond_func= (Item_func_match *) arg1;
}
}
else if (cond->type() == Item::COND_ITEM)
{
List_iterator_fast<Item> li(*((Item_cond*) cond)->argument_list());
if (((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
{
Item *item;
while ((item=li++))
{
if (add_ft_keys(keyuse_array,stat,item,usable_tables))
return TRUE ;
}
}
}
if (!cond_func || cond_func->key == NO_SUCH_KEY ||
!(usable_tables & cond_func->table->map))
return FALSE ;
KEYUSE keyuse;
keyuse.table= cond_func->table;
keyuse.val = cond_func;
keyuse.key = cond_func->key;
keyuse.keypart= FT_KEYPART;
keyuse.used_tables=cond_func->key_item()->used_tables();
keyuse.optimize= 0 ;
keyuse.ref_table_rows= 0 ;
keyuse.keypart_map= 0 ;
keyuse.sj_pred_no= UINT_MAX;
keyuse.validity_ref= 0 ;
keyuse.null_rejecting= FALSE ;
return insert_dynamic(keyuse_array,(uchar*) &keyuse);
}
static int
sort_keyuse(const void *a_, const void *b_)
{
const KEYUSE *a= static_cast <const KEYUSE *>(a_);
const KEYUSE *b= static_cast <const KEYUSE *>(b_);
int res;
if (a->table->tablenr != b->table->tablenr)
return (int ) (a->table->tablenr - b->table->tablenr);
if (a->key != b->key)
return (int ) (a->key - b->key);
if (a->key == MAX_KEY && b->key == MAX_KEY &&
a->used_tables != b->used_tables)
return (int ) ((ulong) a->used_tables - (ulong) b->used_tables);
if (a->keypart != b->keypart)
return (int ) (a->keypart - b->keypart);
// Place const values before other ones
if ((res= MY_TEST((a->used_tables & ~OUTER_REF_TABLE_BIT)) -
MY_TEST((b->used_tables & ~OUTER_REF_TABLE_BIT))))
return res;
/* Place rows that are not 'OPTIMIZE_REF_OR_NULL' first */
return (int ) ((a->optimize & KEY_OPTIMIZE_REF_OR_NULL) -
(b->optimize & KEY_OPTIMIZE_REF_OR_NULL));
}
/*
Add to KEY_FIELD array all ' ref ' access candidates within nested join .
This function populates KEY_FIELD array with entries generated from the
ON condition of the given nested join , and does the same for nested joins
contained within this nested join .
@ param [ in ] nested_join_table Nested join pseudo - table to process
@ param [ in , out ] end End of the key field array
@ param [ in , out ] and_level And - level
@ param [ in , out ] sargables Array of found sargable candidates
@ note
We can add accesses to the tables that are direct children of this nested
join ( 1 ) , and are not inner tables w . r . t their neighbours ( 2 ) .
Example for # 1 ( outer brackets pair denotes nested join this function is
invoked for ) :
@ code
. . . LEFT JOIN ( t1 LEFT JOIN ( t2 . . . ) ) ON cond
@ endcode
Example for # 2 :
@ code
. . . LEFT JOIN ( t1 LEFT JOIN t2 ) ON cond
@ endcode
In examples 1 - 2 for condition cond , we can add ' ref ' access candidates to
t1 only .
Example # 3 :
@ code
. . . LEFT JOIN ( t1 , t2 LEFT JOIN t3 ON inner_cond ) ON cond
@ endcode
Here we can add ' ref ' access candidates for t1 and t2 , but not for t3 .
*/
static void add_key_fields_for_nj(JOIN *join, TABLE_LIST *nested_join_table,
KEY_FIELD **end, uint *and_level,
SARGABLE_PARAM **sargables)
{
List_iterator<TABLE_LIST> li(nested_join_table->nested_join->join_list);
List_iterator<TABLE_LIST> li2(nested_join_table->nested_join->join_list);
bool have_another = FALSE ;
table_map tables= 0 ;
TABLE_LIST *table;
DBUG_ASSERT(nested_join_table->nested_join);
while ((table= li++) || (have_another && (li=li2, have_another=FALSE ,
(table= li++))))
{
if (table->nested_join)
{
if (!table->on_expr)
{
/* It's a semi-join nest. Walk into it as if it wasn't a nest */
have_another= TRUE ;
li2= li;
li= List_iterator<TABLE_LIST>(table->nested_join->join_list);
}
else
add_key_fields_for_nj(join, table, end, and_level, sargables);
}
else
if (!table->on_expr)
tables |= table->table->map;
}
if (nested_join_table->on_expr)
nested_join_table->on_expr->add_key_fields(join, end, and_level, tables,
sargables);
}
void count_cond_for_nj(SELECT_LEX *sel, TABLE_LIST *nested_join_table)
{
List_iterator<TABLE_LIST> li(nested_join_table->nested_join->join_list);
List_iterator<TABLE_LIST> li2(nested_join_table->nested_join->join_list);
bool have_another = FALSE ;
TABLE_LIST *table;
while ((table= li++) || (have_another && (li=li2, have_another=FALSE ,
(table= li++))))
if (table->nested_join)
{
if (!table->on_expr)
{
/* It's a semi-join nest. Walk into it as if it wasn't a nest */
have_another= TRUE ;
li2= li;
li= List_iterator<TABLE_LIST>(table->nested_join->join_list);
}
else
count_cond_for_nj(sel, table);
}
if (nested_join_table->on_expr)
nested_join_table->on_expr->walk(&Item::count_sargable_conds, sel, 0 );
}
/**
Update keyuse array with all possible keys we can use to fetch rows .
@ param thd
@ param [ out ] keyuse Put here ordered array of KEYUSE structures
@ param join_tab Array in tablenr_order
@ param tables Number of tables in join
@ param cond WHERE condition ( note that the function analyzes
join_tab [ i ] - > on_expr too )
@ param normal_tables Tables not inner w . r . t some outer join ( ones
for which we can make ref access based the WHERE
clause )
@ param select_lex current SELECT
@ param [ out ] sargables Array of found sargable candidates
@ retval
0 OK
@ retval
1 Out of memory .
*/
static bool
update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab,
uint tables, COND *cond, table_map normal_tables,
SELECT_LEX *select_lex, SARGABLE_PARAM **sargables)
{
uint and_level,i;
KEY_FIELD *key_fields, *end, *field;
size_t sz;
uint m= MY_MAX(select_lex->max_equal_elems,1 );
DBUG_ENTER("update_ref_and_keys" );
DBUG_PRINT("enter" , ("normal_tables: %llx" , normal_tables));
SELECT_LEX *sel=thd->lex->current_select;
sel->cond_count= 0 ;
sel->between_count= 0 ;
if (cond)
cond->walk(&Item::count_sargable_conds, sel, 0 );
for (i=0 ; i < tables ; i++)
{
if (*join_tab[i].on_expr_ref)
(*join_tab[i].on_expr_ref)->walk(&Item::count_sargable_conds, sel, 0 );
}
{
List_iterator<TABLE_LIST> li(*join_tab->join->join_list);
TABLE_LIST *table;
while ((table= li++))
{
if (table->nested_join)
count_cond_for_nj(sel, table);
}
}
/*
We use the same piece of memory to store both KEY_FIELD
and SARGABLE_PARAM structure .
KEY_FIELD values are placed at the beginning this memory
while SARGABLE_PARAM values are put at the end .
All predicates that are used to fill arrays of KEY_FIELD
and SARGABLE_PARAM structures have at most 2 arguments
except BETWEEN predicates that have 3 arguments and
IN predicates .
This any predicate if it ' s not BETWEEN / IN can be used
directly to fill at most 2 array elements , either of KEY_FIELD
or SARGABLE_PARAM type . For a BETWEEN predicate 3 elements
can be filled as this predicate is considered as
sargable with respect to each of its argument .
An IN predicate can require at most 1 element as currently
it is considered as sargable only for its first argument .
Multiple equality can add elements that are filled after
substitution of field arguments by equal fields . There
can be not more than select_lex - > max_equal_elems such
substitutions .
*/
sz= MY_MAX(sizeof (KEY_FIELD),sizeof (SARGABLE_PARAM))*
((sel->cond_count*2 + sel->between_count)*m+1 );
if (!(key_fields=(KEY_FIELD*) thd->alloc(sz)))
DBUG_RETURN(TRUE ); /* purecov: inspected */
and_level= 0 ;
field= end= key_fields;
*sargables= (SARGABLE_PARAM *) key_fields +
(sz - sizeof ((*sargables)[0 ].field))/sizeof (SARGABLE_PARAM);
/* set a barrier for the array of SARGABLE_PARAM */
(*sargables)[0 ].field= 0 ;
if (my_init_dynamic_array2(thd->mem_root->psi_key, keyuse, sizeof (KEYUSE),
thd->alloc<KEYUSE>(20 ), 20 , 64 ,
MYF(MY_THREAD_SPECIFIC)))
DBUG_RETURN(TRUE );
if (cond)
{
KEY_FIELD *saved_field= field;
cond->add_key_fields(join_tab->join, &end, &and_level, normal_tables,
sargables);
for (; field != end ; field++)
{
/* Mark that we can optimize LEFT JOIN */
if (field->val->type() == Item::NULL_ITEM &&
!field->field->real_maybe_null())
field->field->table->reginfo.not_exists_optimize=1 ;
}
field= saved_field;
}
for (i=0 ; i < tables ; i++)
{
/*
Block the creation of keys for inner tables of outer joins .
Here only the outer joins that can not be converted to
inner joins are left and all nests that can be eliminated
are flattened .
In the future when we introduce conditional accesses
for inner tables in outer joins these keys will be taken
into account as well .
*/
if (*join_tab[i].on_expr_ref)
(*join_tab[i].on_expr_ref)->add_key_fields(join_tab->join, &end,
&and_level,
join_tab[i].table->map,
sargables);
}
/* Process ON conditions for the nested joins */
{
List_iterator<TABLE_LIST> li(*join_tab->join->join_list);
TABLE_LIST *table;
while ((table= li++))
{
if (table->nested_join)
add_key_fields_for_nj(join_tab->join, table, &end, &and_level,
sargables);
}
}
/* fill keyuse with found key parts */
for ( ; field != end ; field++)
{
if (add_key_part(keyuse,field))
DBUG_RETURN(TRUE );
}
if (select_lex->ftfunc_list->elements)
{
if (add_ft_keys(keyuse,join_tab,cond,normal_tables))
DBUG_RETURN(TRUE );
}
DBUG_RETURN(FALSE );
}
/*
check if key could be used with eq_ref
The assumption is that all previous key parts where used
*/
static void remember_if_eq_ref_key(JOIN *join, KEYUSE *use)
{
DBUG_ASSERT(use->keypart != FT_KEYPART && use->key != MAX_KEY);
TABLE *table= use->table;
KEY *key= table->key_info+use->key;
ulong key_flags= table->actual_key_flags(key);
/*
Check if possible eq_ref key
This may include keys that does not have HA_NULL_PART_KEY
set , but this is ok as best_access_path will resolve this .
*/
if ((key_flags & (HA_NOSAME | HA_EXT_NOSAME)))
{
uint key_parts= table->actual_n_key_parts(key);
if (use->keypart+1 == key_parts)
join->eq_ref_tables|= table->map;
}
}
/**
Sort the array of possible keys and remove the following key parts :
- ref if there is a keypart which is a ref and a const .
( e . g . if there is a key ( a , b ) and the clause is a = 3 and b = 7 and b = t2 . d ,
then we skip the key part corresponding to b = t2 . d )
- keyparts without previous keyparts
( e . g . if there is a key ( a , b , c ) but only b < 5 ( or a = 2 and c < 3 ) is
used in the query , we drop the partial key parts from consideration ) .
Special treatment for ft - keys .
Update join - > eq_ref_tables with a bitmap of all tables that can possible
have a EQ_REF key .
Note that the keys are generated to be used by best_access_path ( ) during
the optimization stage . Unused keys will later be deleted by
JOIN : : drop_unused_derived_keys ( ) .
*/
bool sort_and_filter_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse,
bool skip_unprefixed_keyparts,
bool is_splitting)
{
THD *thd= join->thd;
KEYUSE key_end, *prev, *save_pos, *use;
uint found_eq_constant, i;
bool found_unprefixed_key_part= 0 ;
join->eq_ref_tables= 0 ;
DBUG_ASSERT(keyuse->elements);
my_qsort(keyuse->buffer, keyuse->elements, sizeof (KEYUSE),
(qsort_cmp) sort_keyuse);
bzero((char *) &key_end, sizeof (key_end)); /* Add for easy testing */
if (insert_dynamic(keyuse, (uchar*) &key_end))
return TRUE ;
if (optimizer_flag(thd, OPTIMIZER_SWITCH_DERIVED_WITH_KEYS))
generate_derived_keys(keyuse);
use= save_pos= dynamic_element(keyuse,0 ,KEYUSE*);
prev= &key_end;
found_eq_constant= 0 ;
/* Loop over all elements except the last 'key_end' */
for (i=0 ; i < keyuse->elements-1 ; i++,use++)
{
if (!use->is_for_hash_join())
{
if (!is_splitting &&
!(use->used_tables & ~OUTER_REF_TABLE_BIT) &&
use->optimize != KEY_OPTIMIZE_REF_OR_NULL)
use->table->const_key_parts[use->key]|= use->keypart_map;
if (use->keypart != FT_KEYPART)
{
if (use->key == prev->key && use->table == prev->table)
{
if (prev->keypart == use->keypart && found_eq_constant)
continue ;
if (prev->keypart+1 < use->keypart)
{
found_unprefixed_key_part= 1 ;
if (skip_unprefixed_keyparts)
continue ; /* remove */
}
}
else
{
/*
Key changed , check if previous key was a primary / unique key lookup
*/
if (prev != &key_end && !found_unprefixed_key_part)
remember_if_eq_ref_key(join, prev);
found_unprefixed_key_part= 0 ;
if (use->keypart != 0 )
{
found_unprefixed_key_part= 1 ;
if (skip_unprefixed_keyparts)
continue ; /* remove - first found key part must be 0 */
}
}
}
else /* FT_KEY_PART */
{
if (prev != &key_end && !found_unprefixed_key_part)
remember_if_eq_ref_key(join, prev);
found_unprefixed_key_part= 1 ; // This key cannot be EQ_REF
}
prev= use;
found_eq_constant= !use->used_tables;
use->table->reginfo.join_tab->checked_keys.set_bit(use->key);
}
else
{
if (prev != &key_end && !found_unprefixed_key_part)
remember_if_eq_ref_key(join, prev);
prev= &key_end;
}
/*
Old gcc used a memcpy ( ) , which is undefined if save_pos = = use :
http : //gcc.gnu.org/bugzilla/show_bug.cgi?id=19410
http : //gcc.gnu.org/bugzilla/show_bug.cgi?id=39480
This also disables a valgrind warning , so better to have the test .
*/
if (save_pos != use)
*save_pos= *use;
/* Save ptr to first use */
if (!use->table->reginfo.join_tab->keyuse)
use->table->reginfo.join_tab->keyuse= save_pos;
save_pos++;
}
if (prev != &key_end && !found_unprefixed_key_part)
remember_if_eq_ref_key(join, prev);
i= (uint) (save_pos-(KEYUSE*) keyuse->buffer);
(void ) set_dynamic(keyuse,(uchar*) &key_end,i);
keyuse->elements= i;
return FALSE ;
}
/**
Update some values in keyuse for faster choose_plan ( ) loop .
*/
void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array)
{
KEYUSE *end,*keyuse= dynamic_element(keyuse_array, 0 , KEYUSE*);
for (end= keyuse+ keyuse_array->elements ; keyuse < end ; keyuse++)
{
table_map map;
/*
If we find a ref , assume this table matches a proportional
part of this table .
For example 100 records matching a table with 5000 records
gives 5000 / 100 = 50 records per key
Constant tables are ignored .
To avoid bad matches , we don ' t make ref_table_rows less than 100 .
*/
keyuse->ref_table_rows= ~(ha_rows) 0 ; // If no ref
if (keyuse->used_tables &
(map= (keyuse->used_tables & ~join->const_table_map &
~OUTER_REF_TABLE_BIT)))
{
uint n_tables= my_count_bits(map);
if (n_tables == 1 ) // Only one table
{
DBUG_ASSERT(!(map & PSEUDO_TABLE_BITS)); // Must be a real table
Table_map_iterator it(map);
int tablenr= it.next_bit();
DBUG_ASSERT(tablenr != Table_map_iterator::BITMAP_END);
TABLE *tmp_table=join->table[tablenr];
if (tmp_table) // already created
keyuse->ref_table_rows= MY_MAX(tmp_table->file->stats.records, 100 );
}
}
/*
Outer reference ( external field ) is constant for single executing
of subquery
*/
if (keyuse->used_tables == OUTER_REF_TABLE_BIT)
keyuse->ref_table_rows= 1 ;
}
}
/**
Check for the presence of AGGFN ( DISTINCT a ) queries that may be subject
to loose index scan .
Check if the query is a subject to AGGFN ( DISTINCT ) using loose index scan
( QUICK_GROUP_MIN_MAX_SELECT ) .
Optionally ( if out_args is supplied ) will push the arguments of
AGGFN ( DISTINCT ) to the list
Check for every COUNT ( DISTINCT ) , AVG ( DISTINCT ) or
SUM ( DISTINCT ) . These can be resolved by Loose Index Scan as long
as all the aggregate distinct functions refer to the same
fields . Thus :
SELECT AGGFN ( DISTINCT a , b ) , AGGFN ( DISTINCT b , a ) . . . = > can use LIS
SELECT AGGFN ( DISTINCT a ) , AGGFN ( DISTINCT a ) . . . = > can use LIS
SELECT AGGFN ( DISTINCT a , b ) , AGGFN ( DISTINCT a ) . . . = > cannot use LIS
SELECT AGGFN ( DISTINCT a ) , AGGFN ( DISTINCT b ) . . . = > cannot use LIS
etc .
@ param join the join to check
@ param [ out ] out_args Collect the arguments of the aggregate functions
to a list . We don ' t worry about duplicates as
these will be sorted out later in
get_best_group_min_max .
@ return does the query qualify for indexed AGGFN ( DISTINCT )
@ retval true it does
@ retval false AGGFN ( DISTINCT ) must apply distinct in it .
*/
bool
is_indexed_agg_distinct(JOIN *join, List<Item_field> *out_args)
{
Item_sum **sum_item_ptr;
bool result= false ;
if (join->table_count != 1 || /* reference more than 1 table */
join->select_distinct || /* or a DISTINCT */
join->select_lex->olap == ROLLUP_TYPE) /* Check (B3) for ROLLUP */
return false ;
Bitmap<MAX_FIELDS> first_aggdistinct_fields;
bool first_aggdistinct_fields_initialized= false ;
for (sum_item_ptr= join->sum_funcs; *sum_item_ptr; sum_item_ptr++)
{
Item_sum *sum_item= *sum_item_ptr;
Item *expr;
/* aggregate is not AGGFN(DISTINCT) or more than 1 argument to it */
switch (sum_item->sum_func())
{
case Item_sum::MIN_FUNC:
case Item_sum::MAX_FUNC:
continue ;
case Item_sum::COUNT_DISTINCT_FUNC:
break ;
case Item_sum::AVG_DISTINCT_FUNC:
case Item_sum::SUM_DISTINCT_FUNC:
if (sum_item->get_arg_count() == 1 )
break ;
/* fall through */
default : return false ;
}
/*
We arrive here for every COUNT ( DISTINCT ) , AVG ( DISTINCT ) or SUM ( DISTINCT ) .
Collect the arguments of the aggregate functions to a list .
We don ' t worry about duplicates as these will be sorted out later in
get_best_group_min_max
*/
Bitmap<MAX_FIELDS> cur_aggdistinct_fields;
cur_aggdistinct_fields.clear_all();
for (uint i= 0 ; i < sum_item->get_arg_count(); i++)
{
expr= sum_item->get_arg(i);
/* The AGGFN(DISTINCT) arg is not an attribute? */
if (expr->real_item()->type() != Item::FIELD_ITEM)
return false ;
Item_field* item= static_cast <Item_field*>(expr->real_item());
if (out_args)
out_args->push_back(item, join->thd->mem_root);
cur_aggdistinct_fields.set_bit(item->field->field_index);
result= true ;
}
/*
If there are multiple aggregate functions , make sure that they all
refer to exactly the same set of columns .
*/
if (!first_aggdistinct_fields_initialized)
{
first_aggdistinct_fields= cur_aggdistinct_fields;
first_aggdistinct_fields_initialized=true ;
}
else if (first_aggdistinct_fields != cur_aggdistinct_fields)
return false ;
}
return result;
}
/**
Discover the indexes that can be used for GROUP BY or DISTINCT queries .
If the query has a GROUP BY clause , find all indexes that contain all
GROUP BY fields , and add those indexes to join - > const_keys .
If the query has a DISTINCT clause , find all indexes that contain all
SELECT fields , and add those indexes to join - > const_keys .
This allows later on such queries to be processed by a
QUICK_GROUP_MIN_MAX_SELECT .
@ param join
@ param join_tab
@ return
None
*/
static void
add_group_and_distinct_keys(JOIN *join, JOIN_TAB *join_tab)
{
List<Item_field> indexed_fields;
List_iterator<Item_field> indexed_fields_it(indexed_fields);
ORDER *cur_group;
Item_field *cur_item;
key_map possible_keys(0 );
if (join->group_list)
{ /* Collect all query fields referenced in the GROUP clause. */
for (cur_group= join->group_list; cur_group; cur_group= cur_group->next)
(*cur_group->item)->walk(&Item::collect_item_field_processor,
&indexed_fields, 0 );
}
else if (join->select_distinct)
{ /* Collect all query fields referenced in the SELECT clause. */
List<Item> &select_items= join->fields_list;
List_iterator<Item> select_items_it(select_items);
Item *item;
while ((item= select_items_it++))
item->walk(&Item::collect_item_field_processor, &indexed_fields, 0 );
}
else if (!join->tmp_table_param.sum_func_count ||
!is_indexed_agg_distinct(join, &indexed_fields))
{
/*
There where no GROUP BY fields and also either no aggregate
functions or not all aggregate functions where used with the
same DISTINCT ( or MIN ( ) / MAX ( ) that works similarly ) .
Nothing to do there .
*/
return ;
}
if (indexed_fields.elements == 0 )
{
/* There where no index we could use to satisfy the GROUP BY */
return ;
}
/* Intersect the keys of all group fields. */
cur_item= indexed_fields_it++;
possible_keys.merge(cur_item->field->part_of_key);
while ((cur_item= indexed_fields_it++))
{
possible_keys.intersect(cur_item->field->part_of_key);
}
if (!possible_keys.is_clear_all())
join_tab->const_keys.merge(possible_keys);
}
/*****************************************************************************
Go through all combinations of not marked tables and find the one
which uses least records
*****************************************************************************/
/** Save const tables first as used tables. */
void set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key)
{
join->positions[idx].table= table;
join->positions[idx].key=key;
join->positions[idx].records_read=1 .0 ; /* This is a const table */
join->positions[idx].records_out=1 .0 ; /* This is a const table */
join->positions[idx].records_init=1 .0 ; /* This is a const table */
join->positions[idx].cond_selectivity= 1 .0 ;
join->positions[idx].ref_depend_map= 0 ;
join->positions[idx].partial_join_cardinality= 1 ;
// join->positions[idx].loosescan_key= MAX_KEY; /* Not a LooseScan */
join->positions[idx].sj_strategy= SJ_OPT_NONE;
join->positions[idx].use_join_buffer= FALSE ;
join->positions[idx].range_rowid_filter_info= 0 ;
/* Move the const table as down as possible in best_ref */
JOIN_TAB **pos=join->best_ref+idx+1 ;
JOIN_TAB *next=join->best_ref[idx];
for (;next != table ; pos++)
{
JOIN_TAB *tmp=pos[0 ];
pos[0 ]=next;
next=tmp;
}
join->best_ref[idx]=table;
join->positions[idx].spl_plan= 0 ;
join->positions[idx].spl_pd_boundary= 0 ;
}
/*
Estimate how many records we will get if we read just this table and apply
a part of WHERE that can be checked using only the current table and
const tables .
@ param s Current JOIN_TAB
@ param use_cond_selectivity Value of optimizer_use_condition_selectivity .
If > 1 then use table - > cond_selecitivity .
@ return 0 . 0 No matching rows
@ return > = 1 . 0 Number of expected matching rows
Estimate how many records we will get if we
- read the given table with its " independent " access method ( either quick
select or full table / index scan ) ,
- apply the part of WHERE that refers only to this table and const tables .
- The result cannot be bigger than table records
@ see also
table_after_join_selectivity ( ) produces selectivity of condition that is
checked after joining rows from this table to rows from preceding tables .
*/
static double apply_selectivity_for_table(JOIN_TAB *s,
uint use_cond_selectivity)
{
double dbl_records;
if (use_cond_selectivity > 1 )
{
TABLE *table= s->table;
double sel= table->cond_selectivity;
double table_records= rows2double(s->records);
DBUG_ASSERT(sel >= 0 && sel <= 1 .0 );
/*
table - > cond_selectivity will include data from opt_range .
Here we check that this is indeed the case .
Note that if table_records = = 0 , then ' sel ' is probably 1
*/
DBUG_ASSERT(table_records == 0 ||
sel <= s->table->opt_range_condition_rows /
table_records);
dbl_records= table_records * sel;
}
else
{
/*
This is only taking into considering constant key parts used with
this table !
If no such conditions existed the following should normally hold :
s - > table - > opt_range_condition_rows = = s - > found_rows = =
s - > records .
The case when this does not hold is when using ' best splitting '
in which case s - > records may be less than s - > found_rows ;
*/
DBUG_ASSERT(s->table->opt_range_condition_rows <= s->found_records);
dbl_records= rows2double(MY_MIN(s->table->opt_range_condition_rows,
s->records));
}
DBUG_ASSERT(dbl_records <= s->records);
/*
Ensure we return at least one row if there is any possibility to have
a matching row . Having rows > = 1 . 0 helps ensure that when we calculate
total rows of joins , the number of resulting rows will not be less
after the join . In other words , we assume there is at least one matching
row when joining a row with the next table .
0 . 0 is returned only if it is guaranteed there are no matching rows
( for example if the table is empty ) .
*/
return dbl_records ? MY_MAX(dbl_records, MIN_ROWS_AFTER_FILTERING) : 0 .0 ;
}
/*
Take into account that the table ' s WHERE clause has conditions on earlier
tables that can reduce the number of accepted rows .
@ param records Number of original rows ( after selectivity )
If there is a filtering condition on the table ( i . e . ref analyzer found
at least one " table . keyXpartY = exprZ " , where exprZ refers only to tables
preceding this table in the join order we ' re now considering ) , then
assume that 25 % of the rows will be filtered out by this condition .
This heuristic is supposed to force tables used in exprZ to be before
this table in join order .
*/
inline double use_found_constraint(double records)
{
records-= records/4 ;
return records ? MY_MAX(records, MIN_ROWS_AFTER_FILTERING) : 0 .0 ;
}
/*
Calculate the cost of reading a set of rows trough an index
@ param eq_ref True if there is only one matching key ( EQ_REF )
Logically this is identical to the code in multi_range_read_info_const ( )
excepts the function also takes into account io_blocks and multiple
ranges .
One main difference between the functions is that
multi_range_read_info_const ( ) adds a very small cost per range
MULTI_RANGE_READ_SETUP_COST , to ensure that ' ref ' is preferred
over ranges .
Note that this function assumes that index_only_cost is only to be
used with filtering ( as cost . read_cost takes into account both
clustering and covered keys ) . index_only_cost does not include
KEY_COPY_COST as for filtering there is no copying of not accepted
keys .
If eq_ref is not set , it means that we have to do one extra ' read_next '
on the index to verify that there is not more keys with the same value .
WHERE_COST cost is not added to any result .
*/
static ALL_READ_COST cost_for_index_read(THD *thd, const TABLE *table,
uint key, ha_rows records,
bool eq_ref)
{
ALL_READ_COST cost;
handler *file= table->file;
ha_rows max_seeks;
ha_rows extra_reads= eq_ref ? 0 : 1 ;
DBUG_ENTER("cost_for_index_read" );
max_seeks= (ha_rows) thd->variables.max_seeks_for_key;
set_if_bigger(records, 1 );
if (thd->opt_ctx_replay &&
!thd->opt_ctx_replay->infuse_cost_for_index_read(table, key, records,
eq_ref, &cost))
{
/* Ok, Optimizer_context_replay has provided the cost numbers */
}
else if (file->is_clustering_key(key))
{
cost.index_cost=
file->ha_keyread_clustered_time(key, 1 , records+extra_reads, 0 );
cost.copy_cost= rows2double(records) * file->ROW_COPY_COST;
/* There is no 'index_only_read' with a clustered index */
cost.row_cost= {0 ,0 };
/* Caping of index_blocks will happen in handler::cost() */
cost.max_index_blocks= MY_MIN(file->row_blocks(), max_seeks);
cost.max_row_blocks= 0 ;
}
else if (table->covering_keys.is_set(key) && !table->no_keyread)
{
cost.index_cost= file->ha_keyread_time(key, 1 , records + extra_reads, 0 );
cost.row_cost= {0 ,0 };
cost.copy_cost= rows2double(records) * file->KEY_COPY_COST;
cost.max_index_blocks= MY_MIN(file->index_blocks(key), max_seeks);
cost.max_row_blocks= 0 ;
}
else
{
cost.index_cost= file->ha_keyread_time(key, 1 , records + extra_reads, 0 );
/* ha_rnd_pos_time() includes time for copying the row */
cost.row_cost= file->ha_rnd_pos_time(records);
cost.max_index_blocks= MY_MIN(file->index_blocks(key), max_seeks);
cost.max_row_blocks= MY_MIN(file->row_blocks(), max_seeks);
cost.copy_cost= 0 ;
}
if (Optimizer_context_recorder *recorder= thd->opt_ctx_recorder)
{
recorder->record_cost_for_index_read(table, key, records, eq_ref, &cost);
}
DBUG_PRINT("statistics" , ("index_cost: %.3f row_cost: %.3f" ,
file->cost(cost.index_cost),
file->cost(cost.row_cost)));
DBUG_RETURN(cost);
}
/**
Apply filter if the filter is better than the current cost or
if it forced by ROWID_FILTER hint
@ param thd Thread handler
@ param table Table
@ param cost Pointer to cost for current cost , which does not
include WHERE_COST cost . Will be updated to
new cost if filter is chosen .
Will be updated to new cost if filter is used .
@ param records_arg Pointer to number of records for the current key .
Will be updated to records after filter , if filter is
used .
@ param startup_cost Startup cost . Will be updated if filter is used .
@ param fetch_cost Cost of finding the row , without where compare cost
@ param index_only_cost Cost if fetching ' * records_arg ' key values
@ param prev_records Number of record combinations in previous tables
@ return ' this ' Filter is used ( and variables are updated )
@ return 0 Filter is worse than old plan
*/
Range_rowid_filter_cost_info* Range_rowid_filter_cost_info::
apply_filter(THD *thd, TABLE *table, ALL_READ_COST *cost,
double *records_arg,
double *startup_cost,
uint ranges, double prev_records)
{
handler *file= table->file;
bool use_filter;
double new_cost, org_cost, records= *records_arg, new_records;
double filter_startup_cost= get_setup_cost();
double filter_lookup_cost= records * lookup_cost();
double tmp;
ALL_READ_COST adjusted_cost;
/*
Calculate number of resulting rows after filtering
Here we trust selectivity and do not adjust rows up even if
the end result is low . This means that new_records is allowed to be
be < 1 . 0
*/
new_records= records * selectivity;
/*
Calculate the cost of the filter based on that we had originally
' records ' rows and after the filter only ' new_records ' accepted
rows .
Note that the rejected rows , we have only done a key read . We only
fetch the row and compare the where if the filter accepts the
row id .
In case of index only read , fetch_cost = = index_only_cost . Even in this
the filter can give a better plan as we have to do less comparisons
with the WHERE clause .
The io_cost is used to take into account that we have to do 1 key
lookup to find the first matching key in each range .
*/
adjusted_cost= *cost;
/* We are going to read 'selectivity' fewer rows */
adjusted_cost.row_cost.io*= selectivity;
adjusted_cost.row_cost.cpu*= selectivity;
adjusted_cost.copy_cost*= selectivity; // Cost of copying row or key
adjusted_cost.index_cost.cpu+= filter_lookup_cost;
tmp= prev_records * WHERE_COST_THD(thd);
org_cost= (file->cost_for_reading_multiple_times(prev_records,
cost) +
records * tmp);
new_cost= (file->cost_for_reading_multiple_times(prev_records,
&adjusted_cost) +
new_records * tmp + filter_startup_cost);
DBUG_ASSERT(new_cost >= 0 && new_records >= 0 );
use_filter= new_cost < org_cost || is_forced_by_hint;
if (unlikely(thd->trace_started()))
{
Json_writer_object trace_filter(thd, "filter" );
trace_filter.add("rowid_filter_index" ,
table->key_info[get_key_no()].name).
add("index_only_cost" , file->cost(cost->index_cost)).
add("filter_startup_cost" , filter_startup_cost).
add("find_key_and_filter_lookup_cost" , filter_lookup_cost).
add("filter_selectivity" , selectivity).
add("original_rows" , records).
add("new_rows" , new_records).
add("original_access_cost" , file->cost(cost)).
add("with_filter_access_cost" , file->cost(&adjusted_cost)).
add("original_found_rows_cost" , file->cost(cost->row_cost)).
add("with_filter_found_rows_cost" , file->cost(adjusted_cost.row_cost)).
add("org_cost" , org_cost).
add("filter_cost" , new_cost).
add("filter_used" , use_filter);
}
if (use_filter)
{
cost->row_cost= adjusted_cost.row_cost;
cost->index_cost= adjusted_cost.index_cost;
cost->copy_cost= adjusted_cost.copy_cost;
*records_arg= new_records;
(*startup_cost)+= filter_startup_cost;
return this ;
}
return 0 ;
}
/*
@ brief
Compute the fanout of hash join operation using EITS data
@ param join JOIN structure
@ param tab JOIN_TAB for the current table
@ param remaining_tables Map of tables not yet accessable
@ param rnd_records Number of accepted rows in the table , after taking
selectivity into account .
@ param hj_start_key Pointer to hash key
@ param stats_found Is set to 1 if we found any usable hash key part
with statistics from analyze .
*/
double hash_join_fanout(JOIN *join, JOIN_TAB *tab, table_map remaining_tables,
double rnd_records, KEYUSE *hj_start_key,
bool *stats_found)
{
THD *thd= join->thd;
/*
Before doing the hash join , we will scan the table and apply the local part
of the WHERE condition . This will produce rnd_records .
The EITS statistics describes the entire table . Calling
table - > field [ N ] - > get_avg_frequency ( )
produces average # rows in the table with some value .
What happens if we filter out rows so that rnd_records rows are left ?
Something between the two outcomes :
A . filtering removes a fraction of rows for each value :
avg_frequency = avg_frequency * condition_selectivity
B . filtering removes entire groups of rows with the same value , but
the remaining groups remain of the same size .
We make pessimistic assumption and assume B .
We also handle an edge case : if rnd_records is less than avg_frequency ,
assume we ' ll get rnd_records rows with the same value , and return
rnd_records as the fanout estimate .
*/
double min_freq= (double ) tab->table->stat_records();
bool found_not_usable_field= 0 ;
bool found_usable_field __attribute__((unused))= 0 ;
DBUG_ENTER("hash_join_cardinality" );
DBUG_ASSERT(rnd_records > 0 && min_freq > 0 );
Json_writer_object trace_obj(thd, "hash_join_cardinality" );
/*
There can be multiple KEYUSE referring to same or different columns
KEYUSE ( tbl . col1 = . . . )
KEYUSE ( tbl . col1 = . . . )
KEYUSE ( tbl . col2 = . . . )
Hash join code can use multiple columns : ( col1 , col2 ) for joining .
We need n_distinct ( { col1 , col2 } ) .
EITS only has statistics on individual columns : n_distinct ( col1 ) ,
n_distinct ( col2 ) .
Our current solution is to be very conservative and use selectivity
of one column with the lowest avg_frequency .
In the future , we should an approach that cautiosly takes into account
multiple KEYUSEs either multiply by number of equalities or by sqrt
of the second most selective equality .
*/
Json_writer_array trace_arr(thd, "hash_join_columns" );
for (KEYUSE *keyuse= hj_start_key;
keyuse->table == tab->table && is_hash_join_key_no(keyuse->key);
keyuse++)
{
if (!(remaining_tables & keyuse->used_tables) &&
(!keyuse->validity_ref || *keyuse->validity_ref) &&
tab->access_from_tables_is_allowed(keyuse->used_tables,
join->sjm_lookup_tables))
{
Field *field= tab->table->field[keyuse->keypart];
found_usable_field= 1 ;
if (is_eits_usable(field))
{
double freq= field->read_stats->get_avg_frequency();
Json_writer_object trace_field(thd);
trace_field.add("field" ,field->field_name.str).
add("avg_frequency" , freq);
if (freq < min_freq)
min_freq= freq;
*stats_found= 1 ;
continue ;
}
}
if (!keyuse->validity_ref || *keyuse->validity_ref)
found_not_usable_field= 1 ;
}
/* Ensure that some part of hash_key is usable */
DBUG_ASSERT(found_usable_field);
trace_arr.end();
if (found_not_usable_field)
{
/*
We did not ' t have data for all key fields . Assume that the hash
will at least limit the number of matched rows to HASH_FANOUT .
This makes the cost same as when ' hash_join_cardinality = off '
in the case when no analyze of the tables have been made .
However , it may cause problems when min_freq is higher than
HASH_FANOUT as the optimizer will then assume it is better to
put the table earlier in the plan when all key parts are not
usable .
Note that min_freq can become less than 1 . 0 . This is intentional
as it matches what happens if OPTIMIZER_SWITCH_HASH_JOIN_CARDINALITY
is not used .
*/
double max_expected_records= rnd_records * HASH_FANOUT;
set_if_smaller(min_freq, max_expected_records);
trace_obj.add("using_default_hash_fanout" , HASH_FANOUT);
}
else
{
/*
Before joining the table with the contents of join buffer , we will
use the quick select and / or apply the table condition .
This will reduce the number of rows joined to rnd_records .
How will this affect n_distinct ?
Depending on which rows are removed , this can either leave n_distinct as
is ( for some value X , some rows are removed but some are left , leaving the
number of distinct values the same ) , or reduce n_distinct in proportion
with the fraction of rows removed ( for some values of X , either all or
none of the rows with that value are removed ) .
We assume the latter : n_distinct is reduced in proportion the condition
and quick select ' s selectivity .
This is in effect same as applying apply_selectivity_for_table ( ) on
min_freq as we have already done on rnd_records
*/
min_freq*= rnd_records / tab->table->stat_records();
set_if_bigger(min_freq, HASH_FANOUT);
}
trace_obj.add("rows" , min_freq);
DBUG_RETURN(min_freq);
}
#ifndef DBUG_OFF
static char dbug_join_prefix_buf[256 ];
const char * dbug_print_join_prefix(const POSITION *join_positions,
uint idx,
JOIN_TAB *s)
{
char *buf= dbug_join_prefix_buf;
String str(buf, sizeof (dbug_join_prefix_buf), &my_charset_bin);
str.length(0 );
for (uint i=0 ; i!=idx; i++)
{
str.append(join_positions[i].table->table->alias);
str.append(',' );
}
str.append(s->table->alias);
if (str.c_ptr_safe() == buf)
return buf;
else
return "Couldn't fit into buffer" ;
}
#endif
/**
Find the best access path for an extension of a partial execution
plan and add this path to the plan .
The function finds the best access path to table ' s ' from the passed
partial plan where an access path is the general term for any means to
access the data in ' s ' . An access path may use either an index or a scan ,
whichever is cheaper . The input partial plan is passed via the array
' join - > positions ' of length ' idx ' . The chosen access method for ' s ' and its
cost are stored in ' join - > positions [ idx ] ' .
@ param join pointer to the structure providing all context info
for the query
@ param s the table to be joined by the function
@ param thd thread for the connection that submitted the query
@ param remaining_tables set of tables not included into the partial plan yet
@ param idx the length of the partial plan
@ param disable_jbuf TRUE < = > Don ' t use join buffering
@ param record_count estimate for the number of records returned by the
partial plan
@ param pos OUT Table access plan
@ param loose_scan_pos OUT Table plan that uses loosescan , or set cost to
DBL_MAX if not possible .
@ detail
Use this to print the current join prefix :
dbug_print_join_prefix ( join_positions , idx , s )
Use this as breakpoint condition to stop at join prefix " t1 , t2 , t3 " :
$ _ streq ( dbug_print_join_prefix ( join_positions , idx , s ) , " t1 , t2 , t3 " )
@ return
None
*/
struct best_plan
{
double cost; // Smallest cost found
double records; // Old 'Records'
double records_read; // Records accessed
double records_out; // Smallest record count seen
double identical_keys; // Save value from prev_record_reads
Range_rowid_filter_cost_info *filter; // Best filter
KEYUSE *key; // Best key
SplM_plan_info *spl_plan;
table_map ref_depends_map;
ulonglong refills; // Join cache refills
enum join_type type;
uint forced_index;
uint max_key_part;
table_map found_ref;
bool use_join_buffer;
};
void
best_access_path(JOIN *join,
JOIN_TAB *s,
table_map remaining_tables,
const POSITION *join_positions,
uint idx,
bool disable_jbuf,
double record_count,
POSITION *pos,
POSITION *loose_scan_pos)
{
THD *thd= join->thd;
uint use_cond_selectivity=
thd->variables.optimizer_use_condition_selectivity;
TABLE *table= s->table;
handler *file= table->file;
my_bool found_constraint= 0 ;
/*
key_dependent is 0 if all key parts could be used or if there was an
EQ_REF table found ( which uses all key parts ) . In other words , we cannot
find a better key for the table even if remaining_tables is reduced .
Otherwise it ' s a bitmap of tables that could improve key usage .
*/
table_map key_dependent= 0 ;
ALL_READ_COST tmp;
ha_rows rec;
MY_BITMAP *eq_join_set= &s->table->eq_join_set;
KEYUSE *hj_start_key= 0 ;
table_map spl_pd_boundary= 0 ;
Loose_scan_opt loose_scan_opt;
struct best_plan best;
Json_writer_object trace_wrapper(thd, "best_access_path" );
DBUG_ENTER("best_access_path" );
/*
Assume that there is at least one accepted row from previous table
combinations .
This fixes a problem when the selectivity for the preceding table
combinations becomes so high that record_count becomes < < 1 . 0 ,
which makes the cost for the current table so low that it does not
matter when calculating the best plans .
*/
set_if_bigger(record_count, 1 .0 );
best.cost= DBL_MAX;
best.records= DBL_MAX;
best.records_read= DBL_MAX;
best.records_out= MY_MIN(table->stat_records() * table->cond_selectivity,
table->opt_range_condition_rows);
best.identical_keys= 0 ;
best.filter= 0 ;
best.key= 0 ;
best.max_key_part= 0 ;
best.type= JT_UNKNOWN;
best.forced_index= MAX_KEY;
best.found_ref= 0 ;
best.ref_depends_map= 0 ;
best.refills= 0 ;
best.use_join_buffer= FALSE ;
best.spl_plan= 0 ;
disable_jbuf= disable_jbuf || idx == join->const_tables;
trace_wrapper.add_table_name(s);
bitmap_clear_all(eq_join_set);
loose_scan_opt.init(join, s, remaining_tables);
if (table->is_splittable())
best.spl_plan= s->choose_best_splitting(idx,
remaining_tables,
join_positions,
&spl_pd_boundary);
if (unlikely(thd->trace_started()))
{
Json_writer_object info(thd, "plan_details" );
info.add("record_count" , record_count);
}
Json_writer_array trace_paths(thd, "considered_access_paths" );
if (s->keyuse)
{ /* Use key if possible */
KEYUSE *keyuse, *start_key= 0 ;
const char *cause= NULL;
uint max_key_part=0 ;
enum join_type type= JT_UNKNOWN;
double cur_cost, copy_cost, cached_prev_record_reads= 0 .0 ;
table_map cached_prev_ref= ~(table_map) 0 ;
/* Test how we can use keys */
rec= s->records/MATCHING_ROWS_IN_OTHER_TABLE; // Assumed records/key
for (keyuse=s->keyuse ; keyuse->table == table ;)
{
KEY *keyinfo;
ulong key_flags;
uint key_parts;
key_part_map found_part= 0 ;
/*
Bitmap indicating which key parts are used with NULL - rejecting
conditions .
A bit is set to 1 for a key part if it ' s used with a
NULL - rejecting condition ( i . e . , the condition will never be
satisfied when the indexed column contains NULL ) . A bit is 0 if
the key part is used with a non - NULL - rejecting condition ( i . e . ,
the condition can be satisfied even when the indexed column
contains NULL , e . g . , is NULL or < = > ) .
Example : for condition
t1 . keypart1 = t2 . col1 AND t1 . keypart2 < = > t2 . col2 AND
t1 . keypart3 = t2 . col3
the notnull_part bitmap will be 101 ( binary ) , because :
- keypart1 : ' = ' is NULL - rejecting ( bit 1 )
- keypart2 : ' < = > ' is NOT NULL - rejecting ( bit 0 )
- keypart3 : ' = ' is NULL - rejecting ( bit 1 )
*/
key_part_map notnull_part=0 ;
table_map found_ref= 0 ;
uint key= keyuse->key;
uint max_const_parts;
bool ft_key= (keyuse->keypart == FT_KEYPART);
/* Bitmap of keyparts where the ref access is over 'keypart=const': */
key_part_map const_part= 0 ;
/* The or-null keypart in ref-or-null access: */
key_part_map ref_or_null_part= 0 ;
key_part_map all_parts= 0 ;
double startup_cost= s->startup_cost;
double records_after_filter, records_best_filter, records;
Range_rowid_filter_cost_info *filter= 0 ;
double prev_record_count= record_count;
double identical_keys= 0 ;
if (is_hash_join_key_no(key))
{
/*
Hash join as any join employing join buffer can be used to join
only those tables that are joined after the first non const table
*/
if (!(remaining_tables & keyuse->used_tables) &&
idx > join->const_tables)
{
if (!hj_start_key)
hj_start_key= keyuse;
bitmap_set_bit(eq_join_set, keyuse->keypart);
}
keyuse++;
continue ;
}
keyinfo= table->key_info+key;
key_parts= table->actual_n_key_parts(keyinfo);
key_flags= table->actual_key_flags(keyinfo);
/* Calculate how many key segments of the current key we can use */
start_key= keyuse;
loose_scan_opt.next_ref_key();
DBUG_PRINT("info" , ("Considering ref access on key %s" ,
keyuse->table->key_info[keyuse->key].name.str));
do /* For each keypart */
{
uint keypart= keyuse->keypart;
table_map best_part_found_ref= 0 , key_parts_dependent= 0 ;
double best_prev_record_reads= DBL_MAX;
do /* For each way to access the keypart */
{
/*
If 1 . expression does not refer to forward tables
2 . we won ' t get two ref - or - null ' s
*/
double ignore;
all_parts|= keyuse->keypart_map;
if (!(remaining_tables & keyuse->used_tables) &&
(!keyuse->validity_ref || *keyuse->validity_ref) &&
s->access_from_tables_is_allowed(keyuse->used_tables,
join->sjm_lookup_tables) &&
!(ref_or_null_part && (keyuse->optimize &
KEY_OPTIMIZE_REF_OR_NULL)))
{
found_part|= keyuse->keypart_map;
key_parts_dependent= 0 ;
if (!(keyuse->used_tables & ~join->const_table_map))
const_part|= keyuse->keypart_map;
if (!keyuse->val->maybe_null() || keyuse->null_rejecting)
notnull_part|=keyuse->keypart_map;
if ((found_ref | keyuse->used_tables) != cached_prev_ref)
{
cached_prev_ref= (found_ref | keyuse->used_tables);
cached_prev_record_reads=
prev_record_reads(join_positions, idx,
cached_prev_ref, record_count,
&ignore);
}
if (cached_prev_record_reads < best_prev_record_reads)
{
best_prev_record_reads= cached_prev_record_reads;
best_part_found_ref= (keyuse->used_tables &
~join->const_table_map);
}
if (rec > keyuse->ref_table_rows)
rec= keyuse->ref_table_rows;
/*
If there is one ' key_column IS NULL ' expression , we can
use this ref_or_null optimisation of this field
*/
if (keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL)
ref_or_null_part |= keyuse->keypart_map;
/*
Remember if there is a WHERE condition that contains
' key_part = expression_with_only_accessible_tables '
We ignore const tables as these are handled by selectivity
code ( const table fields are treated as constants ) .
*/
found_constraint|= (keyuse->used_tables &
~(remaining_tables |
join->const_table_map));
}
else if (!(found_part & keyuse->keypart_map))
key_parts_dependent|= keyuse->used_tables;
loose_scan_opt.add_keyuse(remaining_tables, keyuse);
keyuse++;
} while (keyuse->table == table && keyuse->key == key &&
keyuse->keypart == keypart);
/* If we found a usable key, remember the dependent tables */
if (all_parts & 1 )
key_dependent|= key_parts_dependent;
found_ref|= best_part_found_ref;
/* Remember if the key expression used previous non const tables */
} while (keyuse->table == table && keyuse->key == key);
/*
Assume that each key matches a proportional part of table .
*/
if (!found_part && !ft_key && !loose_scan_opt.have_a_case())
continue ; // Nothing usable found
if (rec < MATCHING_ROWS_IN_OTHER_TABLE)
rec= MATCHING_ROWS_IN_OTHER_TABLE; // Fix for small tables
Json_writer_object trace_access_idx(thd);
max_const_parts= max_part_bit(const_part);
/*
full text keys require special treatment
*/
if (ft_key)
{
/*
Fulltext indexes are preformed the following way :
- In the prepare step it performs the search , collects all positions
in an array , sorts it .
- If optimizer decides to use the ft index access method it simply '
returns positions from the array one by one
- If optimizer decides to use something else ( another index , table
scan ) , then it ' ll use binary search in the array to find the
position .
The following code puts the cost down to very small as the prep
step will always be done and the cost to fetch the row from memory
is very small .
Alternatively we could use the cost of an EQ_REF here .
*/
tmp.reset();
tmp.row_cost.cpu= file->ROW_COPY_COST;
/*
We don ' t know how many records will match . However , we want to have
the fulltext search done early , so we put the number of records
to be very low .
*/
records= 1 .0 ;
type= JT_FT;
if (unlikely(trace_access_idx.trace_started()))
trace_access_idx.
add("access_type" , join_type_str[type]).
add("full-text index" , keyinfo->name);
}
else
{
loose_scan_opt.check_ref_access_part1(s, key, start_key, found_part);
/* Check if we found full key */
const key_part_map all_key_parts= PREV_BITS(key_part_map, key_parts);
if (found_part == all_key_parts && !ref_or_null_part)
{ /* use eq key */
max_key_part= (uint) ~0 ;
/*
If the index is a unique index ( 1 ) , and
- all its columns are not null ( 2 ) , or
- equalities we are using reject NULLs ( 3 )
then the estimate is rows = 1 .
*/
if ((key_flags & (HA_NOSAME | HA_EXT_NOSAME)) && // (1)
(!(key_flags & HA_NULL_PART_KEY) || // (2)
all_key_parts == notnull_part)) // (3)
{
/* Check that eq_ref_tables are correctly updated */
DBUG_ASSERT(join->eq_ref_tables & table->map);
type= JT_EQ_REF;
if (unlikely(trace_access_idx.trace_started()))
trace_access_idx.
add("access_type" , join_type_str[type]).
add("index" , keyinfo->name);
if (!found_ref && table->opt_range_keys.is_set(key))
{
/* Ensure that the cost is identical to the range cost */
table->opt_range[key].get_costs(&tmp);
}
else
{
tmp= cost_for_index_read(thd, table, key, 1 , 1 );
}
/*
Calculate how many record read calls will be made taking
into account that we will cache the last read row .
*/
prev_record_count= prev_record_reads(join_positions, idx,
found_ref, record_count,
&identical_keys);
records= 1 .0 ;
}
else
{
type= JT_REF;
if (unlikely(trace_access_idx.trace_started()))
trace_access_idx.
add("access_type" , join_type_str[type]).
add("index" , keyinfo->name);
if (!found_ref)
{ /* We found a const key */
/*
ReuseRangeEstimateForRef - 1 :
We get here if we ' ve found a ref ( const ) ( c_i are constants ) :
" ( keypart1 = c1 ) AND . . . AND ( keypartN = cN ) " [ ref_const_cond ]
If range optimizer was able to construct a " range "
access on this index , then its condition " quick_cond " was
equivalent to ref_const_cond ( * ) , and we can re - use E ( # rows )
from the range optimizer .
Proof of ( * ) : By properties of range and ref optimizers
quick_cond will be equal or tighter than ref_const_cond .
ref_const_cond already covers " smallest " possible interval -
a singlepoint interval over all keyparts . Therefore ,
quick_cond is equivalent to ref_const_cond ( if it was an
empty interval we wouldn ' t have got here ) .
*/
if (table->opt_range_keys.is_set(key))
{
/* Ensure that the cost is identical to the range cost */
records= (double ) table->opt_range[key].rows;
trace_access_idx.add("used_range_estimates" , true );
table->opt_range[key].get_costs(&tmp);
goto got_cost2;
}
/* quick_range couldn't use key! */
records= (double ) s->records/rec;
if (unlikely(trace_access_idx.trace_started()))
trace_access_idx.
add("used_range_estimates" , false ).
add("reason" , "not available" );
}
else
{
if (!(records=
keyinfo->rec_per_key_null_aware(key_parts-1 , notnull_part)))
{ /* Prefer longer keys */
trace_access_idx.add("rec_per_key_stats_missing" , true );
records=
((double ) s->records / (double ) rec *
(1 .0 +
((double ) (table->s->max_key_length-keyinfo->key_length) /
(double ) table->s->max_key_length)));
set_if_smaller(records, (double )s->records);
if (records < 1 .0 )
records= 1 .0 ; /* Can't be as good as a unique */
}
/*
ReuseRangeEstimateForRef - 2 : We get here if we could not reuse
E ( # rows ) from range optimizer . Make another try :
If range optimizer produced E ( # rows ) for a prefix of the ref
access we ' re considering , and that E ( # rows ) is lower then our
current estimate , make an adjustment . The criteria of when we
can make an adjustment is a special case of the criteria used
in ReuseRangeEstimateForRef - 3 .
*/
if (table->opt_range_keys.is_set(key) &&
table->opt_range[key].key_parts <= max_const_parts &&
table->opt_range[key].ranges == 1 &&
records > (double ) table->opt_range[key].rows)
{
records= (double ) table->opt_range[key].rows;
trace_access_idx.add("used_range_estimates" , "clipped down" );
}
else if (unlikely(trace_access_idx.trace_started()))
{
if (table->opt_range_keys.is_set(key))
{
trace_access_idx.
add("used_range_estimates" ,false ).
add("reason" , "not better than ref estimates" );
}
else
{
trace_access_idx.
add("used_range_estimates" , false ).
add("reason" , "not available" );
}
}
}
/* Calculate the cost of the index access */
tmp= cost_for_index_read(thd, table, key,
(ha_rows) records, 0 );
}
}
else
{
type = ref_or_null_part ? JT_REF_OR_NULL : JT_REF;
if (unlikely(trace_access_idx.trace_started()))
trace_access_idx.
add("access_type" , join_type_str[type]).
add("index" , keyinfo->name);
/*
Use as much key - parts as possible and a uniq key is better
than a not unique key
Set tmp to the cost of the accessing the expected number of
records .
*/
if ((found_part & 1 ) &&
(!(table->key_info[key].index_flags & HA_ONLY_WHOLE_INDEX) ||
found_part == PREV_BITS(key_part_map,
keyinfo->user_defined_key_parts)))
{
double extra_cost= 0 ;
max_key_part= max_part_bit(found_part);
bool all_used_equalities_are_const= (max_key_part ==
max_const_parts);
/*
ReuseRangeEstimateForRef - 3 :
We ' re now considering a ref [ or_null ] access via
( t . keypart1 = e1 AND . . . AND t . keypartK = eK ) [ OR
( same - as - above but with one cond replaced
with " t . keypart_i IS NULL " ) ] ( * * )
Try re - using E ( # rows ) from " range " optimizer :
We can do so if " range " optimizer used the same intervals as
in ( * * ) . The intervals used by range optimizer may be not
available at this point ( as " range " access might have chosen to
create quick select over another index ) , so we can ' t compare
them to ( * * ) . We ' ll make indirect judgements instead .
The sufficient conditions for re - use are :
( C1 ) All e_i in ( * * ) are constants ( if
this is not satisfied we have no way to know which ranges
will be actually scanned by ' ref ' until we execute the
join )
( C2 ) max # key parts in ' range ' access = = K = = max_key_part ( this
is apparently a necessary requirement )
We also have a property that " range optimizer produces equal or
tighter set of scan intervals than ref ( const ) optimizer " . Each
of the intervals in ( * * ) are " tightest possible " intervals when
one limits itself to using keyparts 1 . . K ( which we do in # 2 ) .
From here it follows that range access used either one , or
both of the ( I1 ) and ( I2 ) intervals :
( t . keypart1 = c1 AND . . . AND t . keypartK = eK ) ( I1 )
( same - as - above but with one cond replaced
with " t . keypart_i IS NULL " ) ( I2 )
The remaining part is to exclude the situation where range
optimizer used one interval while we ' re considering
ref - or - null and looking for estimate for two intervals . This
is done by last limitation :
( C3 ) " range optimizer used ( have ref_or_null ? 2 : 1 ) intervals "
*/
if (table->opt_range_keys.is_set(key) &&
all_used_equalities_are_const && // (C1)
table->opt_range[key].key_parts == max_key_part && //(C2)
(table->opt_range[key].ranges ==
1 + MY_TEST(ref_or_null_part))) //(C3)
{
records= (double ) table->opt_range[key].rows;
table->opt_range[key].get_costs(&tmp);
/*
TODO : Disable opt_range testing below for this range as we can
always use this ref instead .
*/
trace_access_idx.add("used_range_estimates" , true );
goto got_cost2;
}
else
{
/* Check if we have statistic about the distribution */
if ((records=
keyinfo->rec_per_key_null_aware(max_key_part-1 ,
notnull_part)))
{
/*
Fix for the case where the index statistics is too
optimistic : If
( 1 ) We ' re considering ref ( const ) and there is quick select
on the same index ,
( 2 ) and that quick select uses more keyparts ( i . e . it will
scan equal / smaller interval then this ref ( const ) )
( 3 ) and E ( # rows ) for quick select is higher then our
estimate ,
Then
We ' ll use E ( # rows ) from quick select .
Q : Why do we choose to use ' ref ' ? Won ' t quick select be
cheaper in some cases ?
TODO : figure this out and adjust the plan choice if needed .
*/
if (table->opt_range_keys.is_set(key))
{
double rows;
if (table->opt_range[key].key_parts >= max_key_part) // (2)
{
/*
Choose range over REF in the case range will always be
as good or better than REF .
This is the case when we have only one const range
and it consist of more parts than what we used for REF .
*/
if (all_used_equalities_are_const &&
table->opt_range[key].key_parts > max_key_part &&
table->opt_range[key].ranges <=
(uint) (1 + MY_TEST(ref_or_null_part)))
{
trace_access_idx.
add("chosen" , false ).
add("cause" , "range is simple and more selective" );
continue ; // continue with next key
}
}
rows= (double ) table->opt_range[key].rows;
if (all_used_equalities_are_const && // (1)
records < rows) // (3)
{
trace_access_idx.add("used_range_estimates" ,
"clipped up" );
records= rows;
}
}
}
else
{
trace_access_idx.add("rec_per_key_stats_missing" , true );
/*
Assume that the first key part matches 1 % of the file
and that the whole key matches 10 ( duplicates ) or 1
( unique ) records .
Assume also that more key matches proportionally more
records
This gives the formula :
records = ( x * ( b - a ) + a * c - b ) / ( c - 1 )
b = records matched by whole key
a = records matched by first key part ( 1 % of all records ? )
c = number of key parts in key
x = used key parts ( 1 < = x < = c )
*/
double rec_per_key;
if (!(rec_per_key=(double )
keyinfo->rec_per_key[keyinfo->user_defined_key_parts-1 ]))
rec_per_key=(double ) s->records/rec+1 ;
if (!s->records)
records= 0 ;
else if (rec_per_key/(double ) s->records >= 0 .01 )
records= rec_per_key;
else
{
double a=s->records*0 .01 ;
if (keyinfo->user_defined_key_parts > 1 )
records= (max_key_part * (rec_per_key - a) +
a*keyinfo->user_defined_key_parts - rec_per_key)/
(keyinfo->user_defined_key_parts-1 );
else
records= rows2double(s->records);
set_if_bigger(records, MIN_ROWS_AFTER_FILTERING);
}
}
if (ref_or_null_part)
{
/* We need to do two key searches to find row */
records *= 2 .0 ;
extra_cost= s->table->file->KEY_LOOKUP_COST;
}
/*
ReuseRangeEstimateForRef - 4 : We get here if we could not reuse
E ( # rows ) from range optimizer . Make another try :
If range optimizer produced E ( # rows ) for a prefix of the ref
access we ' re considering , and that E ( # rows ) is lower then our
current estimate , make the adjustment .
The decision whether we can re - use the estimate from the range
optimizer is the same as in ReuseRangeEstimateForRef - 3 ,
applied to first table - > quick_key_parts [ key ] key parts .
*/
if (table->opt_range_keys.is_set(key) &&
table->opt_range[key].key_parts <= max_const_parts &&
table->opt_range[key].ranges == (1 +
MY_TEST(ref_or_null_part &
const_part)) &&
records > (double ) table->opt_range[key].rows)
{
// psergey-merge-sept: remove: if (table->opt_range[key].key_parts <= max_const_parts)
{
trace_access_idx.add("used_range_estimates" , true );
records= (double ) table->opt_range[key].rows;
}
}
}
set_if_smaller(records, (double ) s->records);
tmp= cost_for_index_read(thd, table, key, (ha_rows)records, 0 );
tmp.copy_cost+= extra_cost;
}
else
{
if (!(found_part & 1 ))
cause= "no predicate for first keypart" ;
else
cause= "No full key found" ;
trace_access_idx.add("chosen" , false ).add("cause" , cause);
continue ;
}
}
got_cost2:
loose_scan_opt.check_ref_access_part2(key, start_key, records,
file->cost(&tmp) + startup_cost,
found_ref);
} /* not ft_key */
if (records == DBL_MAX) // Key not usable
continue ;
records_best_filter= records_after_filter= records;
/*
Check if we can use a filter .
Records can be 0 in case of empty tables .
*/
if ((found_part & 1 ) && records &&
table->rowid_filter_can_be_applied_to_key(start_key->key))
{
/*
If we use filter F with selectivity s the cost of fetching data
by key using this filter will be
cost_of_fetching_1_row * rows * s +
cost_of_fetching_1_key_tuple * rows * ( 1 - s ) +
cost_of_1_lookup_into_filter * rows
Without using any filter the cost would be just
cost_of_fetching_1_row * rows
So the gain in access cost per row will be
cost_of_fetching_1_row * ( 1 - s ) -
cost_of_fetching_1_key_tuple * ( 1 - s ) -
cost_of_1_lookup_into_filter
=
( cost_of_fetching_1_row - cost_of_fetching_1_key_tuple ) * ( 1 - s )
- cost_of_1_lookup_into_filter
Here we have :
cost_of_fetching_1_row = tmp / rows
cost_of_fetching_1_key_tuple = keyread_tmp / rows
Here ' s a more detailed explanation that uses the formulas behind
the function the call filter - > get_adjusted_gain ( ) . The function
takes as a parameter the number of probes / look - ups into the filter
that is equal to the number of fetched key entries that is equal to
the number of row fetches when no filter is used ( assuming no
index condition pushdown is employed for the used key access ) .
Let this number be N . Then the total gain from using the filter is
N * a_adj - b where b is the cost of building the filter and
a_adj is calculated as follows :
a - ( 1 - access_cost_factor ) * ( 1 - s ) =
( 1 + 1 _ cond_eval_cost ) * ( 1 - s ) - 1 _ probe_cost - ( 1 - access_cost_factor ) * ( 1 - s )
= ( 1 - s ) * ( 1 _ cond_eval_cost + access_cost_factor ) - 1 _ probe_cost .
Here ( ( 1 - s ) * ( 1 _ cond_eval_cost ) * N is the gain from checking less
conditions pushed into the table , 1 _ probe_cost * N is the cost of the
probes and ( 1 * s ) * access_cost_factor * N must be the gain from
accessing less rows .
It does not matter how we calculate the cost of N full row fetches
cost_of_fetching_N_rows or
how we calculate the cost of fetching N key entries
cost_of_fetching_N_key_entries
the gain from less row fetches will be
( cost_of_fetching_N_rows - cost_of_fetching_N_key_entries ) * ( 1 - s )
and this should be equal to ( 1 * s ) * access_cost_factor * N .
Thus access_cost_factor must be calculated as
( cost_of_fetching_N_rows - cost_of_fetching_N_key_entries ) / N .
For safety we clip cost_of_fetching_N_key_entries by the value
of cost_of_fetching_N_row though formally it ' s not necessary .
We cannot use filter with JT_EQ_REF as in this case ' tmp ' is
number of rows from prev_record_read ( ) and keyread_tmp is 0 . These
numbers are not usable with rowid filter code .
*/
filter= table->best_range_rowid_filter(start_key->key,
records,
file->cost(&tmp),
file->cost(tmp.index_cost),
prev_record_count,
&records_best_filter);
set_if_smaller(best.records_out, records_best_filter);
if (filter)
filter= filter->apply_filter(thd, table, &tmp,
&records_after_filter,
&startup_cost,
1 , prev_record_count);
}
/*
Take into account WHERE and setup cost .
We have to check the WHERE for all previous row combinations
( record_count ) .
' prev_record_count ' is either ' record_count ' , or in case of
EQ_REF the estimated number of index_read ( ) calls to the
engine when taking the one row read cache into account .
*/
copy_cost= (record_count * records_after_filter * WHERE_COST_THD(thd) +
startup_cost);
cur_cost= (file->cost_for_reading_multiple_times(prev_record_count,
&tmp) +
copy_cost);
if (unlikely(trace_access_idx.trace_started()))
{
if (prev_record_count != record_count)
trace_access_idx.add("prev_record_count" , prev_record_count);
trace_access_idx.
add("rows" , records_after_filter).
add("cost" , cur_cost);
}
/*
The COST_EPS is here to ensure we use the first key if there are
two ' identical keys ' that could be used .
*/
if (cur_cost + COST_EPS < best.cost)
{
trace_access_idx.add("chosen" , true );
best.cost= cur_cost;
/*
We use ' records ' instead of ' records_after_filter ' here as we want
to have EXPLAIN print the number of rows found by the key access .
*/
best.records= records; // Records before filter!
best.records_read= records;
best.identical_keys= identical_keys;
best.key= start_key;
best.found_ref= found_ref;
best.max_key_part= max_key_part;
best.ref_depends_map= found_ref;
best.filter= filter;
best.type= type;
}
else if (unlikely(thd->trace_started()))
{
trace_access_idx.
add("chosen" , false ).
add("cause" , cause ? cause : "cost" );
}
set_if_smaller(best.records_out, records);
} /* for each key */
}
else
{
/*
No usable keys found . However , there may still be an option to use
" Range checked for each record " when all depending tables has
been read . s - > key_dependent tells us which tables these could be and
s - > key_start_dependent tells us if a first key part was used .
s - > key_dependent may include more tables than could be used ,
but this is ok as not having any usable keys is a rare thing and
the performance penalty for extra table bits is that
best_extension_by_limited_search ( ) would not be able to prune tables
earlier .
Example query :
SELECT * FROM t1 , t2 where t1 . key1 = t2 . key1 OR t2 . key2 < 1
*/
if (s->key_start_dependent)
key_dependent= s->key_dependent;
/* Add dependency for sub queries */
key_dependent|= s->embedded_dependent;
} /* if (s->keyuse) */
/* Check that s->key_dependent contains all used_tables found in s->keyuse */
key_dependent&= ~PSEUDO_TABLE_BITS;
DBUG_ASSERT((key_dependent & (s->key_dependent | s->embedded_dependent)) ==
key_dependent);
/*
If there is no key to access the table , but there is an equi - join
predicate connecting the table with the previous tables then we
consider the possibility of using hash join .
We need also to check that :
( 1 ) s is inner table of semi - join - > join cache is allowed for semijoins
( 2 ) s is inner table of outer join - > join cache is allowed for outer joins
*/
if (idx > join->const_tables && best.key == 0 &&
join->is_allowed_hash_join_access(table) &&
!bitmap_is_clear_all(eq_join_set) && !disable_jbuf &&
(!s->emb_sj_nest ||
join->allowed_semijoin_with_cache) && // (1)
(!(table->map & join->outer_join) ||
join->allowed_outer_join_with_cache)) // (2)
{
Json_writer_object trace_access_hash(thd);
double refills, row_copy_cost, copy_cost, cur_cost, where_cost;
double matching_combinations, fanout= 0 .0 , join_sel;
trace_access_hash.add("type" , "hash" );
trace_access_hash.add("index" , "hj-key" );
/* Estimate the cost of the hash join access to the table */
double rnd_records;
bool stats_found= 0 ;
rnd_records= apply_selectivity_for_table(s, use_cond_selectivity);
DBUG_ASSERT(rnd_records <= rows2double(s->found_records) + 0 .5 );
DBUG_ASSERT(hj_start_key);
fanout= rnd_records;
if (optimizer_flag(thd, OPTIMIZER_SWITCH_HASH_JOIN_CARDINALITY) &&
rnd_records > 0 )
{
/*
Starting from this point , rnd_records should not be used anymore .
Use " fanout " for an estimate of # matching records .
*/
fanout= hash_join_fanout(join, s, remaining_tables, rnd_records,
hj_start_key, &stats_found);
set_if_smaller(best.records_out, fanout);
join_sel= 1 .0 ;
}
if (!stats_found)
{
/*
No OPTIMIZER_SWITCH_HASH_JOIN_CARDINALITY or no field statistics
found .
Take into account if there is non constant constraints used with
earlier tables in the where expression .
If yes , this will set fanout to rnd_records / 4 .
We estimate that there will be HASH_FANOUT ( 10 % )
hash matches / row .
*/
fanout= ((found_constraint) ?
use_found_constraint(rnd_records) :
rnd_records);
set_if_smaller(best.records_out, fanout * HASH_FANOUT);
join_sel= HASH_FANOUT;
}
/*
The following cost calculation is identical to the cost calculation for
the join cache later on , except for the HASH_FANOUT
*/
if (s->quick)
{
/*
Cost of reading rows through opt_range including comparing the rows
with the attached WHERE clause .
*/
cur_cost= s->quick->read_time;
}
else
cur_cost= s->cached_scan_and_compare_time;
/* We read the table as many times as join buffer becomes full. */
refills= (1 .0 + floor((double ) cache_record_length(join,idx) *
record_count /
(double ) thd->variables.join_buff_size));
cur_cost= COST_MULT(cur_cost, refills);
/*
Cost of doing the hash lookup and check all matching rows with the
WHERE clause .
We assume here that , thanks to the hash , we don ' t have to compare all
row combinations , only a fanout or HASH_FANOUT ( 10 % ) rows in the cache .
*/
row_copy_cost= (ROW_COPY_COST_THD(thd) *
JOIN_CACHE_ROW_COPY_COST_FACTOR(thd));
matching_combinations= fanout * join_sel * record_count;
copy_cost= (record_count * row_copy_cost +
matching_combinations *
((idx - join->const_tables) * row_copy_cost));
where_cost= matching_combinations * WHERE_COST_THD(thd);
cur_cost= COST_ADD(cur_cost, copy_cost + where_cost);
best.cost= cur_cost;
best.records_read= rows2double(s->records);
best.records= rnd_records; // Records after where (Legacy value)
best.key= hj_start_key;
best.ref_depends_map= 0 ;
best.use_join_buffer= TRUE ;
best.filter= 0 ;
best.type= JT_HASH;
best.refills= double_to_ulonglong(ceil(refills));
if (unlikely(trace_access_hash.trace_started()))
trace_access_hash.
add("rows" , rnd_records).
add("rows_after_hash" , fanout * join_sel).
add("refills" , refills).
add("jbuf_use_cost" , copy_cost).
add("extra_cond_check_cost" , where_cost).
add("total_cost" , best.cost).
add("chosen" , true );
}
/*
Don ' t test table scan if it can ' t be better .
Prefer key lookup if we would use the same key for scanning .
Don ' t do a table scan on InnoDB tables , if we can read the used
parts of the row from any of the used index .
This is because table scans uses index and we would not win
anything by using a table scan .
A word for word translation of the below if - statement in sergefp ' s
understanding : we check if we should use table scan if :
( 1 ) The found ' ref ' access produces more records than a table scan
( or index scan , or quick select ) , or ' ref ' is more expensive than
any of them .
( 2 ) This doesn ' t hold : the best way to perform table scan is to perform
' range ' access using index IDX , and the best way to perform ' ref '
access is to use the same index IDX , with the same or more key parts .
( note : it is not clear how this rule is / should be extended to
index_merge quick selects ) . Also if we have a hash join we prefer that
over a table scan . This heuristic doesn ' t apply if the quick select
uses the group - by min - max optimization .
( 3 ) See above note about InnoDB .
( 4 ) NOT ( " FORCE INDEX ( . . . ) " is used for table and there is ' ref ' access
path , but there is no quick select )
If the condition in the above brackets holds , then the only possible
" table scan " access method is ALL / index ( there is no quick select ) .
Since we have a ' ref ' access path , and FORCE INDEX instructs us to
choose it over ALL / index , there is no need to consider a full table
scan .
( 5 ) Non - flattenable semi - joins : don ' t consider doing a scan of temporary
table if we had an option to make lookups into it . In real - world cases ,
lookups are cheaper than full scans , but when the table is small , they
can be [ considered to be ] more expensive , which causes lookups not to
be used for cases with small datasets , which is annoying .
OR
( 10 ) The quick select is an index_merge quick select prescribed by the
INDEX_MERGE hint . In this case we try to follow the hint and ignore
all other considerations .
*/
Json_writer_object trace_access_scan(thd);
if (((best.records_read >= s->found_records ||
best.cost > s->read_time) && // (1)
!(best.key && best.key->key == MAX_KEY) && // (2)
!(s->quick &&
s->quick->get_type() != QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX && // (2)
best.key && s->quick->index == best.key->key && // (2)
table->opt_range_keys.is_set(best.key->key) && // (2)
best.max_key_part >= table->opt_range[best.key->key].key_parts) &&// (2)
!((file->ha_table_flags() & HA_TABLE_SCAN_ON_INDEX) && // (3)
!table->covering_keys.is_clear_all() && best.key && !s->quick) &&// (3)
!(table->force_index_join && best.key && !s->quick) && // (4)
!(best.key && table->pos_in_table_list->jtbm_subselect)) // (5)
||
(s->quick && s->quick->force_index_merge)) // (10)
{ // Check full join
double records_after_filter, org_records;
double records_best_filter, cur_cost;
Range_rowid_filter_cost_info *filter= 0 ;
double startup_cost= s->startup_cost;
const char *scan_type= "" ;
enum join_type type;
uint forced_index= MAX_KEY;
bool force_plan= 0 , use_join_buffer= 0 ;
ulonglong refills= 1 ;
ALL_READ_COST cost;
/*
Range optimizer never proposes a RANGE if it isn ' t better
than FULL : so if RANGE is present , it ' s always preferred to FULL .
Here we estimate its cost .
*/
if (s->quick)
{
/*
For each record we :
- read record range through ' quick '
- skip rows which does not satisfy WHERE constraints
*/
/*
Use record count from range optimizer .
This is done to make records found comparable to what we get with
' ref ' access .
*/
org_records= records_after_filter= rows2double(s->found_records);
records_best_filter= org_records;
set_if_smaller(best.records_out, records_best_filter);
if (s->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE)
{
uint key_no= s->quick->index;
TABLE::OPT_RANGE *range= &table->opt_range[key_no];
/*
Ensure that ' range ' and ' s ' are coming from the same source
The complex ' double ' comparison is there because floating point
registers complications when costs are calculated .
*/
DBUG_ASSERT(range->rows >= s->found_records);
DBUG_ASSERT((range->cost.total_cost() == 0 .0 &&
s->quick->read_time == 0 .0 ) ||
compare_cost(range->cost.total_cost(),
s->quick->read_time));
DBUG_ASSERT(compare_cost(range->cost.comp_cost,
range->rows * file->WHERE_COST));
/* Get range cost. This does not include cost of the WHERE */
range->get_costs(&cost);
/* Ensure that cost from opt_range are correct */
DBUG_ASSERT(compare_cost(file->cost_no_capping(&cost) +
range->cost.comp_cost +
range->cost.setup_cost,
s->quick->read_time));
if (table->rowid_filter_can_be_applied_to_key(key_no))
{
filter= table->best_range_rowid_filter(key_no,
rows2double(range->rows),
file->cost(&cost),
file->cost(cost.index_cost),
record_count,
&records_best_filter);
set_if_smaller(best.records_out, records_best_filter);
if (filter)
{
filter= filter->apply_filter(thd, table, &cost,
&records_after_filter,
&startup_cost,
range->ranges,
record_count);
if (filter)
{
set_if_smaller(best.records_out, records_after_filter);
table->opt_range[key_no].selectivity= filter->selectivity;
}
}
}
if (best.key && key_no == best.key->key &&
!best.found_ref &&
best.max_key_part < table->opt_range[best.key->key].key_parts &&
table->opt_range[best.key->key].ranges == 1 )
{
/*
Force to use range as it is using the ' best key ' and using more
key parts ( and thus will read less rows )
*/
force_plan= 1 ;
}
type= JT_RANGE;
/*
We cannot use range - > cost . cmp_cost here as records_after_filter
is be different if filter is used .
*/
cost.copy_cost+= (records_after_filter * file->WHERE_COST +
range->cost.setup_cost);
}
else
{
type= JT_INDEX_MERGE;
/*
We don ' t know exactly from where the costs comes from .
Let ' s store it in copy_cost .
Note that s - > quick - > read_time includes the cost of comparing
the row with the where clause ( WHERE_COST )
*/
cost.reset();
cost.copy_cost= s->quick->read_time;
force_plan= s->quick->force_index_merge;
}
loose_scan_opt.check_range_access(join, idx, s->quick);
}
else
{
double records_table_filter;
/* We will now calculate cost of scan, with or without join buffer */
records_best_filter= records_after_filter=
apply_selectivity_for_table(s, use_cond_selectivity);
records_table_filter= ((found_constraint) ?
use_found_constraint(records_after_filter) :
records_after_filter);
DBUG_ASSERT(records_after_filter <= s->records);
DBUG_ASSERT(records_after_filter <= s->found_records);
set_if_smaller(best.records_out, records_table_filter);
org_records= rows2double(s->records);
/* Estimate cost of reading table. */
if (s->cached_forced_index_type)
{
type= s->cached_forced_index_type;
cost= s->cached_forced_index_cost;
forced_index= s->cached_forced_index;
}
else
{
if (table->force_index_join && !best.key)
{
/*
The query is using ' forced_index ' and we did not find a usable key .
Calculate cost of a table scan with the forced index .
*/
type= JT_NEXT;
if (s->cached_covering_key != MAX_KEY)
{
/* Use value from estimate_scan_time */
forced_index= s->cached_covering_key;
cost= s->cached_scan_and_compare_cost;
}
else
{
#ifdef FORCE_INDEX_SHOULD_FORCE_INDEX_SCAN
/* No cached key, use shortest allowed key */
key_map keys= *file->keys_to_use_for_scanning();
keys.intersect(table->keys_in_use_for_query);
if ((forced_index= find_shortest_key(table, &keys)) < MAX_KEY)
{
cost= cost_for_index_read(thd, table,
forced_index,
s->records, 0 );
/* Calculate cost of checking the attached WHERE */
cost.copy_cost+= s->records * file->WHERE_COST;
}
else
#endif
{
/* No usable key, use table scan */
cost= s->cached_scan_and_compare_cost;
type= JT_ALL;
}
}
}
else // table scan
{
cost= s->cached_scan_and_compare_cost;
type= JT_ALL;
}
/* Cache result for other calls */
s->cached_forced_index_type= type;
s->cached_forced_index_cost= cost;
s->cached_forced_index= forced_index;
}
}
/*
Note : the condition checked here is very out of date and incorrect .
Below , we use a more accurate check when assigning the value of
best . use_join_buffer .
*/
if ((s->table->map & join->outer_join) || disable_jbuf)
{
/*
Simple scan
We estimate we have to read org_records rows .
records_after_filter rows will survive the where check of constants .
' best . records_out ' rows will survive after the check against columns
from previous tables .
*/
scan_type= "scan" ;
/*
We have to compare each row set against all previous row combinations
*/
cur_cost= file->cost_for_reading_multiple_times(record_count,
&cost);
}
else
{
/* Scan trough join cache */
double cmp_time, row_copy_cost, tmp_refills;
/*
Note that the cost of checking all rows against the table specific
WHERE is already included in cur_cost .
*/
scan_type= "scan_with_join_cache" ;
/* Calculate cost of refills */
tmp_refills= (1 .0 + floor((double ) cache_record_length(join,idx) *
(record_count /
(double ) thd->variables.join_buff_size)));
cur_cost= file->cost_for_reading_multiple_times(tmp_refills,
&cost);
refills= double_to_ulonglong(ceil(tmp_refills));
/* We come here only if there are already rows in the join cache */
DBUG_ASSERT(idx != join->const_tables);
/*
records_after_filter is the number of rows that have survived
the table specific WHERE check that only involves constants .
Calculate cost of :
- Copying all previous record combinations to the join cache
- Copying the tables from the join cache to table records
- Checking the WHERE against the final row combination
*/
row_copy_cost= (ROW_COPY_COST_THD(thd) *
JOIN_CACHE_ROW_COPY_COST_FACTOR(thd));
cmp_time= (record_count * row_copy_cost +
records_after_filter * record_count *
((idx - join->const_tables) * row_copy_cost +
WHERE_COST_THD(thd)));
cur_cost= COST_ADD(cur_cost, cmp_time);
use_join_buffer= 1 ;
}
/* Splitting technique cannot be used with join cache */
if (table->is_splittable())
startup_cost+= table->get_materialization_cost();
cur_cost+= startup_cost;
if (unlikely(trace_access_scan.trace_started()))
{
trace_access_scan.
add("access_type" ,
type == JT_ALL ? scan_type : join_type_str[type]);
if (type == JT_RANGE)
trace_access_scan.
add("range_index" , table->key_info[s->quick->index].name);
trace_access_scan.
add("rows" , org_records).
add("rows_after_filter" , records_after_filter).
add("rows_out" , best.records_out).
add("cost" , cur_cost);
if (use_join_buffer)
trace_access_scan.
add("cost_without_join_buffer" ,
file->cost_for_reading_multiple_times(record_count, &cost));
if (type == JT_ALL)
{
trace_access_scan.add("index_only" ,
(s->cached_covering_key != MAX_KEY));
}
}
if (cur_cost + COST_EPS < best.cost || force_plan)
{
/*
If the table has a range ( s - > quick is set ) make_join_select ( )
will ensure that this will be used
*/
best.cost= cur_cost;
best.records_read= org_records; // Records accessed
best.records= records_after_filter; // Records to be checked against
// previous row combinations
/*
If we are using ' use_cond_selectivity > 1 ' then
table_after_join_selectivity may take into account other
filters that what is currently used so we have to use
records_after_filter . If ' use_cond_selectivity < = 1 then we
can use information from the best filter .
*/
best.key= 0 ;
best.forced_index= forced_index;
/*
filter is only set if
s - > quick - > get_type ( ) = = QUICK_SELECT_I : : QS_TYPE_RANGE
*/
best.filter= filter;
/* range/index_merge/ALL/index access method are "independent", so: */
best.ref_depends_map= 0 ;
best.use_join_buffer= use_join_buffer ||
MY_TEST(!disable_jbuf &&
(join->allowed_outer_join_with_cache ||
!(s->table->map & join->outer_join)));
best.refills= refills;
best.spl_plan= 0 ;
best.type= type;
trace_access_scan.add("chosen" , true );
}
else
trace_access_scan.add("chosen" , false );
}
else
{
if (unlikely(trace_access_scan.trace_started()))
trace_access_scan.
add("type" , "scan" ).
add("chosen" , false ).
add("cause" , "cost" );
}
crash_if_first_double_is_bigger(best.records_out, best.records);
crash_if_first_double_is_bigger(best.records_out, best.records_read);
/* Update the cost information for the current partial plan */
pos->loops= record_count;
pos->records_init= best.records_read;
pos->records_read= best.records;
pos->records_out= best.records_out;
pos->identical_keys= best.identical_keys;
pos->read_time= best.cost;
pos->key= best.key;
pos->forced_index= best.forced_index;
pos->type= best.type;
pos->table= s;
pos->ref_depend_map= best.ref_depends_map;
pos->loosescan_picker.loosescan_key= MAX_KEY;
pos->use_join_buffer= best.use_join_buffer;
pos->firstmatch_with_join_buf= 0 ;
pos->spl_plan= best.spl_plan;
pos->spl_pd_boundary= best.spl_plan ? spl_pd_boundary: 0 ;
pos->range_rowid_filter_info= best.filter;
pos->key_dependent= (best.type == JT_EQ_REF ? (table_map) 0 :
key_dependent & remaining_tables);
pos->refills= best.refills;
loose_scan_opt.save_to_position(s, record_count, pos->records_out,
loose_scan_pos);
if (!best.key &&
idx == join->const_tables && // First table
table == join->sort_by_table &&
join->unit->lim.get_select_limit() >= best.records) // QQQ Why?
{
trace_access_scan.add("use_tmp_table" , true );
join->sort_by_table= (TABLE*) 1 ; // Must use temporary table
}
trace_access_scan.end();
trace_paths.end();
if (unlikely(thd->trace_started()))
print_best_access_for_table(thd, pos);
DBUG_VOID_RETURN;
}
/*
Find JOIN_TAB ' s embedding ( i . e , parent ) subquery .
- For merged semi - joins , tables inside the semi - join nest have their
semi - join nest as parent . We intentionally ignore results of table
pullout action here .
- For non - merged semi - joins ( JTBM tabs ) , the embedding subquery is the
JTBM join tab itself .
*/
static TABLE_LIST* get_emb_subq(JOIN_TAB *tab)
{
TABLE_LIST *tlist= tab->table->pos_in_table_list;
if (tlist->jtbm_subselect)
return tlist;
TABLE_LIST *embedding= tlist->embedding;
if (!embedding || !embedding->sj_subq_pred)
return NULL;
return embedding;
}
/*
Choose initial table order that " helps " semi - join optimizations .
The idea is that we should start with the order that is the same as the one
we would have had if we had semijoin = off :
- Top - level tables go first
- subquery tables are grouped together by the subquery they are in ,
- subquery tables are attached where the subquery predicate would have been
attached if we had semi - join off .
This function relies on join_tab_cmp ( ) / join_tab_cmp_straight ( ) to produce
certain pre - liminary ordering , see compare_embedding_subqueries ( ) for its
description .
*/
static void choose_initial_table_order(JOIN *join)
{
TABLE_LIST *emb_subq;
JOIN_TAB **tab= join->best_ref + join->const_tables;
JOIN_TAB **tabs_end= tab + join->table_count - join->const_tables;
DBUG_ENTER("choose_initial_table_order" );
/* Find where the top-level JOIN_TABs end and subquery JOIN_TABs start */
for (; tab != tabs_end; tab++)
{
if ((emb_subq= get_emb_subq(*tab)))
break ;
}
uint n_subquery_tabs= (uint)(tabs_end - tab);
if (!n_subquery_tabs)
DBUG_VOID_RETURN;
/* Copy the subquery JOIN_TABs to a separate array */
JOIN_TAB *subquery_tabs[MAX_TABLES];
memcpy(subquery_tabs, tab, sizeof (JOIN_TAB*) * n_subquery_tabs);
JOIN_TAB **last_top_level_tab= tab;
JOIN_TAB **subq_tab= subquery_tabs;
JOIN_TAB **subq_tabs_end= subquery_tabs + n_subquery_tabs;
TABLE_LIST *cur_subq_nest= NULL;
for (; subq_tab < subq_tabs_end; subq_tab++)
{
if (get_emb_subq(*subq_tab)!= cur_subq_nest)
{
/*
Reached the part of subquery_tabs that covers tables in some subquery .
*/
cur_subq_nest= get_emb_subq(*subq_tab);
/* Determine how many tables the subquery has */
JOIN_TAB **last_tab_for_subq;
for (last_tab_for_subq= subq_tab;
last_tab_for_subq < subq_tabs_end &&
get_emb_subq(*last_tab_for_subq) == cur_subq_nest;
last_tab_for_subq++) {}
uint n_subquery_tables= (uint)(last_tab_for_subq - subq_tab);
/*
Walk the original array and find where this subquery would have been
attached to
*/
table_map need_tables= cur_subq_nest->original_subq_pred_used_tables;
need_tables &= ~(join->const_table_map | PSEUDO_TABLE_BITS);
for (JOIN_TAB **top_level_tab= join->best_ref + join->const_tables;
top_level_tab < last_top_level_tab;
//top_level_tab < join->best_ref + join->table_count;
top_level_tab++)
{
need_tables &= ~(*top_level_tab)->table->map;
/* Check if this is the place where subquery should be attached */
if (!need_tables)
{
/* Move away the top-level tables that are after top_level_tab */
size_t top_tail_len= last_top_level_tab - top_level_tab - 1 ;
memmove(top_level_tab + 1 + n_subquery_tables, top_level_tab + 1 ,
sizeof (JOIN_TAB*)*top_tail_len);
last_top_level_tab += n_subquery_tables;
memcpy(top_level_tab + 1 , subq_tab, sizeof (JOIN_TAB*)*n_subquery_tables);
break ;
}
}
DBUG_ASSERT(!need_tables);
subq_tab += n_subquery_tables - 1 ;
}
}
DBUG_VOID_RETURN;
}
/**
Selects and invokes a search strategy for an optimal query plan .
The function checks user - configurable parameters that control the search
strategy for an optimal plan , selects the search method and then invokes
it . Each specific optimization procedure stores the final optimal plan in
the array ' join - > best_positions ' , and the cost of the plan in
' join - > best_read ' .
@ param join pointer to the structure providing all context info for
the query
@ param join_tables set of the tables in the query
@ param emb_sjm_nest List of tables in case of materialized semi - join nest
@ retval
FALSE ok
@ retval
TRUE Fatal error
*/
bool
choose_plan(JOIN *join, table_map join_tables, TABLE_LIST *emb_sjm_nest)
{
uint search_depth= join->thd->variables.optimizer_search_depth;
uint use_cond_selectivity=
join->thd->variables.optimizer_use_condition_selectivity;
bool straight_join= MY_TEST(join->select_options & SELECT_STRAIGHT_JOIN);
THD *thd= join->thd;
qsort_cmp2 jtab_sort_func;
DBUG_ENTER("choose_plan" );
join->limit_optimization_mode= false ;
join->extra_heuristic_pruning= false ;
join->prune_level= join->thd->variables.optimizer_prune_level;
if ((join->emb_sjm_nest= emb_sjm_nest))
{
/* We're optimizing semi-join materialization nest, so put the
tables from this semi - join as first
*/
jtab_sort_func= join_tab_cmp_embedded_first;
/*
If we are searching for the execution plan of a materialized semi - join
nest then allowed_tables contains bits only for the tables from this
nest .
*/
join->allowed_tables= (emb_sjm_nest->sj_inner_tables &
~join->const_table_map);
}
else
{
/*
if ( SELECT_STRAIGHT_JOIN option is set )
reorder tables so dependent tables come after tables they depend
on , otherwise keep tables in the order they were specified in the query
else
Apply heuristic : pre - sort all access plans with respect to the number
of records accessed .
*/
jtab_sort_func= straight_join ? join_tab_cmp_straight : join_tab_cmp;
join->allowed_tables= ~join->const_table_map;
}
/*
psergey - todo : if we ' re not optimizing an SJM nest ,
- sort that outer tables are first , and each sjm nest follows
- then , put each [ sjm_table1 , . . . sjm_tableN ] sub - array right where
WHERE clause pushdown would have put it .
*/
my_qsort2(join->best_ref + join->const_tables,
join->table_count - join->const_tables, sizeof (JOIN_TAB*),
jtab_sort_func, (void *) emb_sjm_nest);
Json_writer_object wrapper(thd);
Json_writer_array trace_plan(thd,"considered_execution_plans" );
if (!emb_sjm_nest)
choose_initial_table_order(join);
if (straight_join)
{
optimize_straight_join(join, join_tables);
}
else
{
DBUG_ASSERT(search_depth <= MAX_TABLES + 1 );
if (search_depth == 0 )
/* Automatically determine a reasonable value for 'search_depth' */
search_depth= determine_search_depth(join);
if (join->prune_level >= 1 &&
search_depth >= thd->variables.optimizer_extra_pruning_depth)
{
join->extra_heuristic_pruning= true ;
}
double limit_cost= DBL_MAX;
double limit_record_count;
POSITION *limit_plan= NULL;
/*
First , build a join plan that can short - cut ORDER BY . . . LIMIT .
Do it if
( 1 ) The SELECT in query makes it possible to do short - cutting for
some table TBL .
( 2 ) We are optimizing the whole JOIN , not a semi - join nest
( 3 ) The table TBL has not been marked as constant ( in this case ,
ORDER BY LIMIT will be optimized away )
*/
if (join->limit_shortcut_applicable && // (1)
!join->emb_sjm_nest && // (2)
!(join->sort_by_table->map & join->const_table_map)) //(3)
{
bool res;
Json_writer_object wrapper(join->thd);
Json_writer_array trace(join->thd, "join_limit_shortcut_plan_search" );
join->limit_optimization_mode= true ;
res= greedy_search(join, join_tables, search_depth,
use_cond_selectivity);
join->limit_optimization_mode= false ;
if (res)
DBUG_RETURN(TRUE );
DBUG_ASSERT(join->best_read != DBL_MAX);
/*
We ' ve built a join order . Adjust its cost based on ORDER BY . . . LIMIT
short - cutting .
*/
limit_plan= join_limit_shortcut_finalize_plan(join, &limit_cost);
limit_record_count= join->join_record_count;
}
/* The main call to search for the query plan: */
if (greedy_search(join, join_tables, search_depth, use_cond_selectivity))
DBUG_RETURN(TRUE );
DBUG_ASSERT(join->best_read != DBL_MAX);
if (limit_plan && limit_cost < join->best_read)
{
/* Plan that uses ORDER BY ... LIMIT shortcutting is better. */
memcpy((uchar*)join->best_positions, (uchar*)limit_plan,
sizeof (POSITION)*join->table_count);
join->best_read= limit_cost;
join->join_record_count= limit_record_count;
}
}
join->emb_sjm_nest= 0 ;
DBUG_RETURN(FALSE );
}
/*
Compare two join tabs based on the subqueries they are from .
- top - level join tabs go first
- then subqueries are ordered by their select_id ( we ' re using this
criteria because we need a cross - platform , deterministic ordering )
@ return
0 - equal
- 1 - jt1 < jt2
1 - jt1 > jt2
*/
static int compare_embedding_subqueries(const JOIN_TAB *jt1, const JOIN_TAB *jt2)
{
/* Determine if the first table is originally from a subquery */
TABLE_LIST *tbl1= jt1->table->pos_in_table_list;
uint tbl1_select_no;
if (tbl1->jtbm_subselect)
{
tbl1_select_no=
tbl1->jtbm_subselect->unit->first_select()->select_number;
}
else if (tbl1->embedding && tbl1->embedding->sj_subq_pred)
{
tbl1_select_no=
tbl1->embedding->sj_subq_pred->unit->first_select()->select_number;
}
else
tbl1_select_no= 1 ; /* Top-level */
/* Same for the second table */
TABLE_LIST *tbl2= jt2->table->pos_in_table_list;
uint tbl2_select_no;
if (tbl2->jtbm_subselect)
{
tbl2_select_no=
tbl2->jtbm_subselect->unit->first_select()->select_number;
}
else if (tbl2->embedding && tbl2->embedding->sj_subq_pred)
{
tbl2_select_no=
tbl2->embedding->sj_subq_pred->unit->first_select()->select_number;
}
else
tbl2_select_no= 1 ; /* Top-level */
/*
Put top - level tables in front . Tables from within subqueries must follow ,
grouped by their owner subquery . We don ' t care about the order that
subquery groups are in , because choose_initial_table_order ( ) will re - order
the groups .
*/
if (tbl1_select_no != tbl2_select_no)
return tbl1_select_no > tbl2_select_no ? 1 : -1 ;
return 0 ;
}
/**
Compare two JOIN_TAB objects based on the number of accessed records .
@ param ptr1 pointer to first JOIN_TAB object
@ param ptr2 pointer to second JOIN_TAB object
NOTES
The order relation implemented by join_tab_cmp ( ) is not transitive ,
i . e . it is possible to choose such a , b and c that ( a < b ) & & ( b < c )
but ( c < a ) . This implies that result of a sort using the relation
implemented by join_tab_cmp ( ) depends on the order in which
elements are compared , i . e . the result is implementation - specific .
Example :
a : dependent = 0 x0 table - > map = 0 x1 found_records = 3 ptr = 0 x907e6b0
b : dependent = 0 x0 table - > map = 0 x2 found_records = 3 ptr = 0 x907e838
c : dependent = 0 x6 table - > map = 0 x10 found_records = 2 ptr = 0 x907ecd0
As for subqueries , this function must produce order that can be fed to
choose_initial_table_order ( ) .
@ retval
1 if first is bigger
@ retval
- 1 if second is bigger
@ retval
0 if equal
*/
static int
join_tab_cmp(void *, const void * ptr1, const void * ptr2)
{
auto jt1= *(static_cast <const JOIN_TAB *const *>(ptr1));
auto jt2= *(static_cast <const JOIN_TAB *const *>(ptr2));
int cmp;
if ((cmp= compare_embedding_subqueries(jt1, jt2)) != 0 )
return cmp;
/*
After that do ordering according to numbers of
records in the table .
*/
if (jt1->found_records > jt2->found_records)
return 1 ;
if (jt1->found_records < jt2->found_records)
return -1 ;
return jt1 > jt2 ? 1 : (jt1 < jt2 ? -1 : 0 );
}
/**
Same as join_tab_cmp , but for use with SELECT_STRAIGHT_JOIN .
*/
static int
join_tab_cmp_straight(void *, const void * ptr1, const void * ptr2)
{
auto jt1= *(static_cast <const JOIN_TAB *const *>(ptr1));
auto jt2= *(static_cast <const JOIN_TAB *const *>(ptr2));
/*
We don ' t do subquery flattening if the parent or child select has
STRAIGHT_JOIN modifier . It is complicated to implement and the semantics
is hardly useful .
*/
DBUG_ASSERT(!jt1->emb_sj_nest);
DBUG_ASSERT(!jt2->emb_sj_nest);
int cmp;
if ((cmp= compare_embedding_subqueries(jt1, jt2)) != 0 )
return cmp;
/*
We have to check dependency with straight_join as we don ' t reorder
later as we do for other plans in best_extension_by_limited_search ( ) .
*/
if (jt1->dependent & jt2->table->map)
return 1 ;
if (jt2->dependent & jt1->table->map)
return -1 ;
return jt1 > jt2 ? 1 : (jt1 < jt2 ? -1 : 0 );
}
/*
Same as join_tab_cmp but tables from within the given semi - join nest go
first . Used when the optimizing semi - join materialization nests .
*/
static int
join_tab_cmp_embedded_first(void *emb, const void * ptr1, const void * ptr2)
{
TABLE_LIST *emb_nest= static_cast <TABLE_LIST *>(emb);
auto jt1= *(static_cast <const JOIN_TAB *const *>(ptr1));
auto jt2= *(static_cast <const JOIN_TAB *const *>(ptr2));
if (jt1->emb_sj_nest == emb_nest && jt2->emb_sj_nest != emb_nest)
return -1 ;
if (jt1->emb_sj_nest != emb_nest && jt2->emb_sj_nest == emb_nest)
return 1 ;
if (jt1->found_records > jt2->found_records)
return 1 ;
if (jt1->found_records < jt2->found_records)
return -1 ;
return jt1 > jt2 ? 1 : (jt1 < jt2 ? -1 : 0 );
}
/**
Heuristic procedure to automatically guess a reasonable degree of
exhaustiveness for the greedy search procedure .
The procedure estimates the optimization time and selects a search depth
big enough to result in a near - optimal QEP , that doesn ' t take too long to
find . If the number of tables in the query exceeds some constant , then
search_depth is set to this constant .
@ param join pointer to the structure providing all context info for
the query
@ note
This is an extremely simplistic implementation that serves as a stub for a
more advanced analysis of the join . Ideally the search depth should be
determined by learning from previous query optimizations , because it will
depend on the CPU power ( and other factors ) .
@ todo
this value should be determined dynamically , based on statistics :
uint max_tables_for_exhaustive_opt = 7 ;
@ todo
this value could be determined by some mapping of the form :
depth : table_count - > [ max_tables_for_exhaustive_opt . . MAX_EXHAUSTIVE ]
@ return
A positive integer that specifies the search depth ( and thus the
exhaustiveness ) of the depth - first search algorithm used by
' greedy_search ' .
*/
static uint
determine_search_depth(JOIN *join)
{
uint table_count= join->table_count - join->const_tables;
uint search_depth;
/* TODO: this value should be determined dynamically, based on statistics: */
uint max_tables_for_exhaustive_opt= 7 ;
if (table_count <= max_tables_for_exhaustive_opt)
search_depth= table_count+1 ; // use exhaustive for small number of tables
else
/*
TODO : this value could be determined by some mapping of the form :
depth : table_count - > [ max_tables_for_exhaustive_opt . . MAX_EXHAUSTIVE ]
*/
search_depth= max_tables_for_exhaustive_opt; // use greedy search
return search_depth;
}
/**
Select the best ways to access the tables in a query without reordering them .
Find the best access paths for each query table and compute their costs
according to their order in the array ' join - > best_ref ' ( thus without
reordering the join tables ) . The function calls sequentially
' best_access_path ' for each table in the query to select the best table
access method . The final optimal plan is stored in the array
' join - > best_positions ' , and the corresponding cost in ' join - > best_read ' .
@ param join pointer to the structure providing all context info
for the query
@ param remaining_tables set of the tables in the query
@ note
This function can be applied to :
- queries with STRAIGHT_JOIN
- internally to compute the cost of an arbitrary QEP
@ par
Thus ' optimize_straight_join ' can be used at any stage of the query
optimization process to finalize a QEP as it is .
*/
static void
optimize_straight_join(JOIN *join, table_map remaining_tables)
{
JOIN_TAB *s;
uint idx= join->const_tables;
bool disable_jbuf= join->thd->variables.join_cache_level == 0 ;
double record_count= 1 .0 ;
double read_time= 0 .0 ;
uint use_cond_selectivity=
join->thd->variables.optimizer_use_condition_selectivity;
POSITION loose_scan_pos;
THD *thd= join->thd;
init_join_plan_search_state(join);
for (JOIN_TAB **pos= join->best_ref + idx ; (s= *pos) ; pos++)
{
POSITION *position= join->positions + idx;
Json_writer_object trace_one_table(thd);
double original_record_count, current_record_count;
if (unlikely(thd->trace_started()))
trace_plan_prefix(&trace_one_table, join, idx, remaining_tables);
/* Find the best access method from 's' to the current partial plan */
best_access_path(join, s, remaining_tables, join->positions, idx,
disable_jbuf, record_count,
position, &loose_scan_pos);
/* Compute the cost of the new plan extended with 's' */
current_record_count= COST_MULT(record_count, position->records_out);
read_time= COST_ADD(read_time, position->read_time);
original_record_count= current_record_count;
optimize_semi_joins(join, remaining_tables, idx, ¤t_record_count,
&read_time, &loose_scan_pos);
if (position->sj_strategy != SJ_OPT_NONE && original_record_count)
{
/* Adjust records_out to contain the final number of rows */
double ratio= current_record_count / original_record_count;
if (ratio < 1 )
{
position->records_out*= ratio;
}
if (unlikely(trace_one_table.trace_started()))
{
trace_one_table.
add("sj_rows_out" , position->records_out).
add("sj_rows_for_plan" , current_record_count).
add("sj_filtered" , safe_filtered(position->records_out,
position->records_init));
}
}
remaining_tables&= ~(s->table->map);
if (use_cond_selectivity > 1 && position->sj_strategy == SJ_OPT_NONE)
{
double pushdown_cond_selectivity, records_out;
pushdown_cond_selectivity= table_after_join_selectivity(join, idx, s,
remaining_tables,
&records_out);
if (unlikely(thd->trace_started()) &&
pushdown_cond_selectivity != 1 .0 )
{
trace_one_table.
add("rows_out" , records_out).
add("pushdown_cond_selectivity" , pushdown_cond_selectivity).
add("filtered" , safe_filtered(position->records_out,
position->records_init));
}
position->cond_selectivity= pushdown_cond_selectivity;
position->records_out= records_out;
current_record_count= COST_MULT(record_count, records_out);
}
else
position->cond_selectivity= 1 .0 ;
position->partial_join_cardinality= current_record_count;
++idx;
record_count= current_record_count;
}
if (join->sort_by_table &&
join->sort_by_table != join->positions[join->const_tables].table->table)
{
/*
We may have to make a temp table , note that this is only a
heuristic since we cannot know for sure at this point if we
we are going to use addon fields or to have flush sorting to
disk . We also don ' t know the temporary table will be in memory
or disk .
The following calculation takes a middle ground where assume
we can sort the keys in memory but have to use a disk based
temporary table to retrieve the rows .
This cost is probably much bigger than it has to be . . .
*/
double sort_cost;
sort_cost= (get_qsort_sort_cost((ha_rows)record_count, 0 ) +
record_count *
DISK_TEMPTABLE_LOOKUP_COST(thd));
{
if (unlikely(thd->trace_started()))
{
Json_writer_object trace_one_table(thd);
trace_one_table.add("estimated_cost_for_sorting" , sort_cost);
}
}
read_time= COST_ADD(read_time, sort_cost);
}
memcpy((uchar*) join->best_positions, (uchar*) join->positions,
sizeof (POSITION)*idx);
join->join_record_count= record_count;
join->best_read= read_time;
}
/**
Find a good , possibly optimal , query execution plan ( QEP ) by a greedy search .
The search procedure uses a hybrid greedy / exhaustive search with controlled
exhaustiveness . The search is performed in N = card ( remaining_tables )
steps . Each step evaluates how promising is each of the unoptimized tables ,
selects the most promising table , and extends the current partial QEP with
that table . Currenly the most ' promising ' table is the one with least
expensive extension . \
There are two extreme cases :
- # When ( card ( remaining_tables ) < search_depth ) , the estimate finds the
best complete continuation of the partial QEP . This continuation can be
used directly as a result of the search .
- # When ( search_depth = = 1 ) the ' best_extension_by_limited_search '
considers the extension of the current QEP with each of the remaining
unoptimized tables .
All other cases are in - between these two extremes . Thus the parameter
' search_depth ' controlls the exhaustiveness of the search . The higher the
value , the longer the optimization time and possibly the better the
resulting plan . The lower the value , the fewer alternative plans are
estimated , but the more likely to get a bad QEP .
All intermediate and final results of the procedure are stored in ' join ' :
- join - > positions : modified for every partial QEP that is explored
- join - > best_positions : modified for the current best complete QEP
- join - > best_read : modified for the current best complete QEP
- join - > best_ref : might be partially reordered
The final optimal plan is stored in ' join - > best_positions ' , and its
corresponding cost in ' join - > best_read ' .
@ note
The following pseudocode describes the algorithm of ' greedy_search ' :
@ code
procedure greedy_search
input : remaining_tables
output : pplan ;
{
pplan = < > ;
do {
( t , a ) = best_extension ( pplan , remaining_tables ) ;
pplan = concat ( pplan , ( t , a ) ) ;
remaining_tables = remaining_tables - t ;
} while ( remaining_tables ! = { } )
return pplan ;
}
@ endcode
where ' best_extension ' is a placeholder for a procedure that selects the
most " promising " of all tables in ' remaining_tables ' .
Currently this estimate is performed by calling
' best_extension_by_limited_search ' to evaluate all extensions of the
current QEP of size ' search_depth ' , thus the complexity of ' greedy_search '
mainly depends on that of ' best_extension_by_limited_search ' .
@ par
If ' best_extension ( ) ' = = ' best_extension_by_limited_search ( ) ' , then the
worst - case complexity of this algorithm is < =
O ( N * N ^ search_depth / search_depth ) . When serch_depth > = N , then the
complexity of greedy_search is O ( N ! ) .
@ par
In the future , ' greedy_search ' might be extended to support other
implementations of ' best_extension ' , e . g . some simpler quadratic procedure .
@ param join pointer to the structure providing all context info
for the query
@ param remaining_tables set of tables not included into the partial plan yet
@ param search_depth controlls the exhaustiveness of the search
@ param use_cond_selectivity specifies how the selectivity of the conditions
pushed to a table should be taken into account
@ retval
FALSE ok
@ retval
TRUE Fatal error
*/
static bool
greedy_search(JOIN *join,
table_map remaining_tables,
uint search_depth,
uint use_cond_selectivity)
{
double record_count= 1 .0 ;
double read_time= 0 .0 ;
uint idx= join->const_tables; // index into 'join->best_ref'
uint best_idx;
uint size_remain; // cardinality of remaining_tables
table_map usable_tables, eq_ref_tables;
POSITION best_pos;
JOIN_TAB *best_table; // the next plan node to be added to the curr QEP
// ==join->tables or # tables in the sj-mat nest we're optimizing
uint n_tables __attribute__((unused));
DBUG_ENTER("greedy_search" );
DBUG_ASSERT(!(remaining_tables & join->const_table_map));
init_join_plan_search_state(join);
/* number of tables that remain to be optimized */
usable_tables= (join->emb_sjm_nest ?
(join->emb_sjm_nest->sj_inner_tables &
~join->const_table_map & remaining_tables):
remaining_tables);
n_tables= size_remain= my_count_bits(usable_tables);
join->next_sort_position= join->sort_positions;
do {
/*
Find the extension of the current QEP with the lowest cost
We are using remaining_table instead of usable tables here as
in case of an emb_sjm_nest , we want to be able to check if
an embedded table is depending on an outer table .
*/
join->best_read= DBL_MAX;
if ((int ) best_extension_by_limited_search(join, remaining_tables, idx,
record_count,
read_time, search_depth,
use_cond_selectivity,
&eq_ref_tables) <
(int ) SEARCH_OK)
DBUG_RETURN(TRUE );
/*
' best_read < DBL_MAX ' means that optimizer managed to find
some plan and updated ' best_positions ' array accordingly .
*/
DBUG_ASSERT(join->best_read < DBL_MAX);
if (size_remain <= search_depth)
{
/*
' join - > best_positions ' contains a complete optimal extension of the
current partial QEP .
*/
DBUG_EXECUTE("opt" , print_plan(join, n_tables,
record_count, read_time, read_time,
"optimal" ););
DBUG_RETURN(FALSE );
}
/* select the first table in the optimal extension as most promising */
best_pos= join->best_positions[idx];
best_table= best_pos.table;
/*
Each subsequent loop of ' best_extension_by_limited_search ' uses
' join - > positions ' for cost estimates , therefore we have to update its
value .
*/
join->positions[idx]= best_pos;
/*
Update the interleaving state after extending the current partial plan
with a new table .
We are doing this here because best_extension_by_limited_search reverts
the interleaving state to the one of the non - extended partial plan
on exit .
*/
bool is_interleave_error __attribute__((unused))=
check_interleaving_with_nj (best_table);
/* This has been already checked by best_extension_by_limited_search */
DBUG_ASSERT(!is_interleave_error);
/*
Also , update the semi - join optimization state . Information about the
picked semi - join operation is in best_pos - > . . . picker , but we need to
update the global state in the JOIN object , too .
*/
if (!join->emb_sjm_nest)
update_sj_state(join, best_table, idx, remaining_tables);
/* find the position of 'best_table' in 'join->best_ref' */
best_idx= idx;
JOIN_TAB *pos= join->best_ref[best_idx];
while (pos && best_table != pos)
pos= join->best_ref[++best_idx];
DBUG_ASSERT((pos != NULL)); // should always find 'best_table'
/*
Move ' best_table ' at the first free position in the array of joins
We don ' t need to keep the array sorted as
best_extension_by_limited_search ( ) will sort them .
*/
swap_variables(JOIN_TAB*, join->best_ref[idx], join->best_ref[best_idx]);
/* compute the cost of the new plan extended with 'best_table' */
record_count= COST_MULT(record_count, join->positions[idx].records_read);
read_time= COST_ADD(read_time, join->positions[idx].read_time);
remaining_tables&= ~(best_table->table->map);
--size_remain;
++idx;
DBUG_EXECUTE("opt" , print_plan(join, idx,
record_count, read_time, read_time,
"extended" ););
} while (TRUE );
}
/**
Get cost of execution and fanout produced by selected tables in the join
prefix ( where prefix is defined as prefix in depth - first traversal )
@ param end_tab_idx The number of last tab to be taken into
account ( in depth - first traversal prefix )
@ param filter_map Bitmap of tables whose cost / fanout are to
be taken into account .
@ param read_time_arg [ out ] store read time here
@ param record_count_arg [ out ] store record count here
@ note
@ returns
read_time_arg and record_count_arg contain the computed cost and fanout
*/
void JOIN::get_partial_cost_and_fanout(int end_tab_idx,
table_map filter_map,
double *read_time_arg,
double *record_count_arg)
{
double record_count= 1 ;
double read_time= 0 .0 ;
double sj_inner_fanout= 1 .0 ;
JOIN_TAB *end_tab= NULL;
JOIN_TAB *tab;
int i;
int last_sj_table= MAX_TABLES;
/*
Handle a special case where the join is degenerate , and produces no
records
*/
if (table_count == const_tables)
{
*read_time_arg= 0 .0 ;
/*
We return 1 , because
- it is the pessimistic estimate ( there might be grouping )
- it ' s safer , as we ' re less likely to hit the edge cases in
calculations .
*/
*record_count_arg=1 .0 ;
return ;
}
for (tab= first_depth_first_tab(this ), i= const_tables;
tab;
tab= next_depth_first_tab(this , tab), i++)
{
end_tab= tab;
if (i == end_tab_idx)
break ;
}
for (tab= first_depth_first_tab(this ), i= const_tables;
;
tab= next_depth_first_tab(this , tab), i++)
{
if (end_tab->bush_root_tab && end_tab->bush_root_tab == tab)
{
/*
We ' ve entered the SJM nest that contains the end_tab . The caller is
- interested in fanout inside the nest ( because that ' s how many times
we ' ll invoke the attached WHERE conditions )
- not interested in cost
*/
record_count= 1 .0 ;
read_time= 0 .0 ;
}
/*
Ignore fanout ( but not cost ) from sj - inner tables , as long as
the range that processes them finishes before the end_tab
*/
if (tab->sj_strategy != SJ_OPT_NONE)
{
sj_inner_fanout= 1 .0 ;
last_sj_table= i + tab->n_sj_tables;
}
table_map cur_table_map;
if (tab->table)
cur_table_map= tab->table->map;
else
{
/* This is a SJ-Materialization nest. Check all of its tables */
TABLE *first_child= tab->bush_children->start->table;
TABLE_LIST *sjm_nest= first_child->pos_in_table_list->embedding;
cur_table_map= sjm_nest->nested_join->used_tables;
}
if (tab->records_read && (cur_table_map & filter_map))
{
record_count= COST_MULT(record_count, tab->records_read);
read_time= COST_ADD(read_time, tab->read_time);
if (tab->emb_sj_nest)
sj_inner_fanout= COST_MULT(sj_inner_fanout, tab->records_read);
}
if (i == last_sj_table)
{
record_count /= sj_inner_fanout;
sj_inner_fanout= 1 .0 ;
last_sj_table= MAX_TABLES;
}
if (tab == end_tab)
break ;
}
*read_time_arg= read_time;
*record_count_arg= record_count;
}
/*
Get prefix cost and fanout . This function is different from
get_partial_cost_and_fanout :
- it operates on a JOIN that haven ' t yet finished its optimization phase ( in
particular , fix_semijoin_strategies_for_picked_join_order ( ) and
get_best_combination ( ) haven ' t been called )
- it assumes the join prefix doesn ' t have any semi - join plans
These assumptions are met by the caller of the function .
*/
void JOIN::get_prefix_cost_and_fanout(uint n_tables,
double *read_time_arg,
double *record_count_arg)
{
double record_count= 1 ;
double read_time= 0 .0 ;
for (uint i= const_tables; i < n_tables + const_tables ; i++)
{
if (best_positions[i].records_read)
{
record_count= COST_MULT(record_count, best_positions[i].records_read);
read_time= COST_ADD(read_time, best_positions[i].read_time);
}
}
*read_time_arg= read_time;
*record_count_arg= record_count;
}
/**
Estimate the number of rows that query execution will read .
@ todo This is a very pessimistic upper bound . Use join selectivity
when available to produce a more realistic number .
*/
double JOIN::get_examined_rows()
{
double examined_rows;
double prev_fanout= 1 ;
double records;
JOIN_TAB *tab= first_breadth_first_tab();
JOIN_TAB *prev_tab= tab;
records= (double )tab->get_examined_rows();
while ((tab= next_breadth_first_tab(first_breadth_first_tab(),
top_join_tab_count, tab)))
{
prev_fanout= COST_MULT(prev_fanout, prev_tab->records_read);
records=
COST_ADD(records,
COST_MULT((double ) (tab->get_examined_rows()), prev_fanout));
prev_tab= tab;
}
examined_rows= records;
return examined_rows;
}
/**
@ brief
Get the selectivity of equalities between columns when joining a table
@ param join The optimized join
@ param idx The number of tables in the evaluated partual join
@ param s The table to be joined for evaluation
@ param rem_tables The bitmap of tables to be joined later
@ param keyparts The number of key parts to used when joining s
@ param ref_keyuse_steps Array of references to keyuses employed to join s
*/
static
double table_multi_eq_cond_selectivity(JOIN *join, uint idx, JOIN_TAB *s,
table_map rem_tables, uint keyparts,
uint16 *ref_keyuse_steps)
{
double sel= 1 .0 ;
COND_EQUAL *cond_equal= join->cond_equal;
if (!cond_equal || !cond_equal->current_level.elements || !s->keyuse)
return sel;
Item_equal *item_equal;
List_iterator_fast<Item_equal> it(cond_equal->current_level);
TABLE *table= s->table;
table_map table_bit= table->map;
POSITION *pos= &join->positions[idx];
while ((item_equal= it++))
{
/*
Check whether we need to take into account the selectivity of
multiple equality item_equal . If this is the case multiply
the current value of sel by this selectivity
*/
table_map used_tables= item_equal->used_tables();
if (!(used_tables & table_bit))
continue ;
if (item_equal->get_const())
continue ;
bool adjust_sel= FALSE ;
Item_equal_fields_iterator fi(*item_equal);
while ((fi++) && !adjust_sel)
{
Field *fld= fi.get_curr_field();
if (fld->table->map != table_bit)
continue ;
if (pos->key == 0 )
adjust_sel= TRUE ;
else
{
uint i;
KEYUSE *keyuse= pos->key;
uint key= keyuse->key;
for (i= 0 ; i < keyparts; i++)
{
if (i > 0 )
keyuse+= ref_keyuse_steps[i-1 ];
uint fldno;
if (is_hash_join_key_no(key))
fldno= keyuse->keypart;
else
fldno= table->key_info[key].key_part[i].fieldnr - 1 ;
if (fld->field_index == fldno)
break ;
}
keyuse= pos->key;
if (i == keyparts)
{
/*
Field fld is included in multiple equality item_equal
and is not a part of the ref key .
The selectivity of the multiple equality must be taken
into account unless one of the ref arguments is
equal to fld .
*/
adjust_sel= TRUE ;
for (uint j= 0 ; j < keyparts && adjust_sel; j++)
{
if (j > 0 )
keyuse+= ref_keyuse_steps[j-1 ];
Item *ref_item= keyuse->val;
if (ref_item->real_item()->type() == Item::FIELD_ITEM)
{
Item_field *field_item= (Item_field *) (ref_item->real_item());
if (item_equal->contains(field_item->field))
adjust_sel= FALSE ;
}
}
}
}
}
if (adjust_sel)
{
/*
If ref = = 0 and there are no fields in the multiple equality
item_equal that belong to the tables joined prior to s
then the selectivity of multiple equality will be set to 1 . 0 .
*/
double eq_fld_sel= 1 .0 ;
fi.rewind();
while ((fi++))
{
double curr_eq_fld_sel;
Field *fld= fi.get_curr_field();
if (!(fld->table->map & ~(table_bit | rem_tables)))
continue ;
curr_eq_fld_sel= get_column_avg_frequency(fld) /
fld->table->stat_records();
if (curr_eq_fld_sel < 1 .0 )
set_if_bigger(eq_fld_sel, curr_eq_fld_sel);
}
sel*= eq_fld_sel;
}
}
return sel;
}
/**
@ brief
Get the selectivity of conditions when joining a table
@ param join The optimized join
@ param s The table to be joined for evaluation
@ param rem_tables The bitmap of tables to be joined later
@ param new_records_out OUT Set to number of rows accepted
@ detail
Get selectivity of conditions that can be applied when joining this table
with previous tables .
For quick selects and full table scans , selectivity of COND ( this_table )
is accounted for in apply_selectivity_for_table ( ) . Here , we only count
selectivity of COND ( this_table , previous_tables ) .
For other access methods , we need to calculate selectivity of the whole
condition , " COND ( this_table ) AND COND ( this_table , previous_tables ) " .
@ retval
selectivity of the conditions imposed on the rows of s related to
the rows that we are expected to read ( position - > records_init ) .
*/
static
double table_after_join_selectivity(JOIN *join, uint idx, JOIN_TAB *s,
table_map rem_tables,
double *new_records_out)
{
uint16 ref_keyuse_steps_buf[MAX_REF_PARTS];
uint ref_keyuse_size= MAX_REF_PARTS;
uint16 *ref_keyuse_steps= ref_keyuse_steps_buf;
Field *field;
TABLE *table= s->table;
MY_BITMAP *read_set= table->read_set;
POSITION *pos= &join->positions[idx];
double sel, records_out= pos->records_out;
uint keyparts= 0 ;
uint found_part_ref_or_null= 0 ;
if (pos->key != 0 )
{
sel= table->cond_selectivity;
/*
A ref access or hash join is used for this table . ref access is created
from
tbl . keypart1 = expr1 AND tbl . keypart2 = expr2 AND . . .
and it will only return rows for which this condition is satisfied .
Suppose , certain expr { i } is a constant . Since ref access only returns
rows that satisfy
tbl . keypart { i } = const ( * )
then selectivity of this equality should not be counted in return value
of this function . This function uses the value of
table - > cond_selectivity = selectivity ( COND ( tbl ) ) ( * * )
as a starting point . This value includes selectivity of equality ( * ) . We
should somehow discount it .
Looking at calculate_cond_selectivity_for_table ( ) , one can see that
the value is not necessarily a direct multiplicand in
table - > cond_selectivity
There are three possible ways to discount
1 . There is a potential range access on t . keypart { i } = const .
( an important special case : the used ref access has a const prefix for
which a range estimate is available )
2 . The field has a histogram . field [ x ] - > cond_selectivity has the data .
3 . Use index stats on this index :
rec_per_key [ key_part + 1 ] / rec_per_key [ key_part ]
( TODO : more details about the " t . key = othertable . col " case )
*/
KEYUSE *keyuse= pos->key;
KEYUSE *prev_ref_keyuse= keyuse;
uint key= keyuse->key;
bool used_range_selectivity= false ;
/*
Check if we have a prefix of key = const that matches a quick select .
*/
if (!is_hash_join_key_no(key) && table->opt_range_keys.is_set(key))
{
key_part_map quick_key_map= (key_part_map(1 ) <<
table->opt_range[key].key_parts) - 1 ;
if (s->type == JT_RANGE ||
(table->opt_range[key].rows && (table->const_key_parts[key] & 1 )))
{
/*
We are either using a range or we are using a REF which the
same key as an active range and the first key part is a constant .
In both cases we have to discount the selectivity for the range
as otherwise we are using the selectivity twice .
*/
for (; quick_key_map & 1 ; quick_key_map>>= 1 )
{
while (keyuse->table == table && keyuse->key == key &&
keyuse->keypart == keyparts)
{
keyuse++;
}
keyparts++;
}
/*
Here we discount selectivity of the constant range CR . To calculate
this selectivity we use elements from the quick_rows [ ] array .
If we have indexes i1 , . . . , ik with the same prefix compatible
with CR any of the estimate quick_rows [ i1 ] , . . . quick_rows [ ik ] could
be used for this calculation but here we don ' t know which one was
actually used . So sel could be greater than 1 and we have to cap it .
However if sel becomes greater than 2 then with high probability
something went wrong .
*/
DBUG_ASSERT(sel <= 1 .0 );
DBUG_ASSERT(table->opt_range[key].rows <=
(double ) table->stat_records());
sel /= ((double ) table->opt_range[key].rows /
(double ) table->stat_records());
set_if_smaller(sel, 1 .0 );
used_range_selectivity= true ;
}
}
/*
Go through the " keypart { N } = . . . " equalities and find those that were
already taken into account in table - > cond_selectivity .
*/
keyuse= pos->key;
keyparts=0 ;
while (keyuse->table == table && keyuse->key == key)
{
if (!(keyuse->used_tables & (rem_tables | table->map)))
{
if (are_tables_local(s, keyuse->val->used_tables()))
{
if (is_hash_join_key_no(key))
{
if (keyparts == keyuse->keypart)
keyparts++;
}
else
{
if (keyparts == keyuse->keypart &&
!((keyuse->val->used_tables()) & ~pos->ref_depend_map) &&
!(found_part_ref_or_null & keyuse->optimize))
{
/* Found a KEYUSE object that will be used by ref access */
keyparts++;
found_part_ref_or_null|= keyuse->optimize & ~KEY_OPTIMIZE_EQ;
}
}
if (keyparts > keyuse->keypart)
{
/* Ok this is the keyuse that will be used for ref access */
if (!used_range_selectivity && keyuse->val->const_item())
{
uint fldno;
if (is_hash_join_key_no(key))
fldno= keyuse->keypart;
else
fldno= table->key_info[key].key_part[keyparts-1 ].fieldnr - 1 ;
if (table->field[fldno]->cond_selectivity > 0 )
{
sel /= table->field[fldno]->cond_selectivity;
set_if_smaller(sel, 1 .0 );
}
/*
TODO : we could do better here :
1 . cond_selectivity might be = 1 ( the default ) because quick
select on some index prevented us from analyzing
histogram for this column .
2 . we could get an estimate through this ?
rec_per_key [ key_part - 1 ] / rec_per_key [ key_part ]
*/
}
if (keyparts > 1 )
{
/*
Prepare to set ref_keyuse_steps [ keyparts - 2 ] : resize the array
if it is not large enough
*/
if (keyparts - 2 >= ref_keyuse_size)
{
uint new_size= MY_MAX(ref_keyuse_size*2 , keyparts);
void *new_buf;
if (!(new_buf= my_malloc(PSI_INSTRUMENT_ME,
sizeof (*ref_keyuse_steps)*new_size,
MYF(0 ))))
{
sel= 1 .0 ; // As if no selectivity was computed
goto exit ;
}
memcpy(new_buf, ref_keyuse_steps,
sizeof (*ref_keyuse_steps)*ref_keyuse_size);
if (ref_keyuse_steps != ref_keyuse_steps_buf)
my_free(ref_keyuse_steps);
ref_keyuse_steps= (uint16*)new_buf;
ref_keyuse_size= new_size;
}
ref_keyuse_steps[keyparts-2 ]= (uint16)(keyuse - prev_ref_keyuse);
prev_ref_keyuse= keyuse;
}
}
}
}
keyuse++;
}
/*
If the field f from the table is equal to a field from one the
earlier joined tables then the selectivity of the range conditions
over the field f must be discounted .
We need to discount selectivity only if we ' re using ref - based
access method ( and have sel ! = 1 ) .
If we use ALL / range / index_merge , then sel = = 1 , and no need to discount .
*/
for (Field **f_ptr=table->field ; (field= *f_ptr) ; f_ptr++)
{
if (!bitmap_is_set(read_set, field->field_index) ||
!field->next_equal_field)
continue ;
for (Field *next_field= field->next_equal_field;
next_field != field;
next_field= next_field->next_equal_field)
{
if (!(next_field->table->map & rem_tables) &&
next_field->table != table)
{
if (field->cond_selectivity > 0 )
{
sel/= field->cond_selectivity;
set_if_smaller(sel, 1 .0 );
}
break ;
}
}
}
/*
We have now calculated a more exact ' records_out ' taking more index
costs into account .
pos - > records_out previously contained the smallest record count for
all range or ref access , which should not be smaller than what we
calculated above .
*/
records_out= pos->records_init * sel;
set_if_smaller(records_out, pos->records_out);
}
sel= table_multi_eq_cond_selectivity(join, idx, s, rem_tables,
keyparts, ref_keyuse_steps);
records_out*= sel;
/*
Update sel to be relative pos - > records_read as that is what some old
code expects . Newer code should just use ' position - > records_out ' instead .
*/
if (pos->records_read == 0 )
sel= 1 .0 ;
else
{
sel= records_out / pos->records_read;
DBUG_ASSERT(sel >= 0 .0 && sel <= 1 .00001 );
if (sel > 1 .0 )
sel= 1 .0 ;
}
exit :
*new_records_out= records_out;
if (ref_keyuse_steps != ref_keyuse_steps_buf)
my_free(ref_keyuse_steps);
return sel;
}
/*
Check if the table is an EQ_REF or similar table and there is no cost
to gain by moving it to a later stage .
We call such a table a edge table ( or hanging leaf ) as it will read at
most one row and will not add to the number of row combinations in the join .
*/
static inline enum_best_search
check_if_edge_table(POSITION *pos,
double pushdown_cond_selectivity)
{
if ((pos->type == JT_EQ_REF ||
(pos->type == JT_REF &&
pos->records_init == 1 &&
!pos->range_rowid_filter_info)) &&
pushdown_cond_selectivity >= 0 .999 )
return SEARCH_FOUND_EDGE;
return SEARCH_OK;
}
struct SORT_POSITION
{
JOIN_TAB **join_tab;
POSITION *position;
};
/*
Sort SORT_POSITIONS according to expected number of rows found
If number of combinations are the same sort according to join_tab order
( same table order as used in the original SQL query )
*/
static int sort_positions(const void *a_, const void *b_)
{
const SORT_POSITION *a= static_cast <const SORT_POSITION*>(a_);
const SORT_POSITION *b= static_cast <const SORT_POSITION*>(b_);
int cmp;
if ((cmp= compare_embedding_subqueries(*a->join_tab, *b->join_tab)) != 0 )
return cmp;
if (a->position->records_read > b->position->records_read)
return 1 ;
if (a->position->records_read < b->position->records_read)
return -1 ;
return CMP_NUM(*a->join_tab, *b->join_tab);
}
/*
Call best_access_path ( ) for a set of tables and collect results
@ param join JOIN object
@ param trace_one_table Current optimizer_trace
@ param pos Pointer to remaining tables
@ param allowed_tables bitmap of allowed tables . On return set to
the collected tables .
@ param store_poisition Points to where to store next found SORT_POSITION .
Will be updated to next free position .
@ param stop_on_eq_ref Stop searching for more tables if we found an EQ_REF
table .
@ return
0 Normal
1 Eq_ref table found ( only if stop_on_eq_ref is used )
join - > next_sort_position will be update to next free position .
*/
static bool
get_costs_for_tables(JOIN *join, table_map remaining_tables, uint idx,
double record_count,
Json_writer_object *trace_one_table,
JOIN_TAB **pos, SORT_POSITION **store_position,
table_map *allowed_tables,
bool stop_on_eq_ref)
{
THD *thd= join->thd;
POSITION *sort_position= join->next_sort_position;
SORT_POSITION *sort_end= *store_position;
JOIN_TAB *s;
table_map found_tables= 0 ;
bool found_eq_ref= 0 ;
DBUG_ENTER("get_plans_for_tables" );
table_map remaining_allowed_tables=
(join->emb_sjm_nest ?
(join->emb_sjm_nest->sj_inner_tables &
~join->const_table_map & remaining_tables):
remaining_tables);
s= *pos;
do
{
table_map real_table_bit= s->table->map;
if ((*allowed_tables & real_table_bit) &&
!(remaining_allowed_tables & s->dependent))
{
#ifdef DBUG_ASSERT_EXISTS
DBUG_ASSERT(!check_interleaving_with_nj(s));
restore_prev_nj_state(s); // Revert effect of check_... call
#endif
sort_end->join_tab= pos;
sort_end->position= sort_position;
bool hint_forces_jbuf=
hint_table_state(join->thd, s->table, BNL_HINT_ENUM, false );
bool disable_jbuf=
(join->thd->variables.join_cache_level == 0 ) && !hint_forces_jbuf;
Json_writer_object wrapper(thd);
/* Find the best access method from 's' to the current partial plan */
best_access_path(join, s, remaining_tables, join->positions, idx,
disable_jbuf, record_count,
sort_position, sort_position + 1 );
found_tables|= s->table->map;
sort_end++;
sort_position+= 2 ;
if (unlikely(stop_on_eq_ref) && sort_position[-2 ].type == JT_EQ_REF)
{
/* Found an eq_ref tables. Use this, ignoring the other tables */
found_eq_ref= 1 ;
if (found_tables == s->table->map)
break ; // First table
/* Store the found eq_ref table first in store_position */
sort_position-= 2 ;
*allowed_tables= s->table->map;
(*store_position)->join_tab= pos;
(*store_position)->position= sort_position;
(*store_position)++;
join->next_sort_position[0 ]= sort_position[0 ];
join->next_sort_position[1 ]= sort_position[1 ];
join->next_sort_position+= 2 ;
DBUG_RETURN(1 );
}
}
else
{
/* Verify that 'allowed_current_tables' was calculated correctly */
DBUG_ASSERT((remaining_tables & s->dependent) ||
!(remaining_tables & real_table_bit) ||
!(*allowed_tables & real_table_bit) ||
check_interleaving_with_nj(s));
}
} while ((s= *++pos));
*allowed_tables= found_tables;
*store_position= sort_end;
join->next_sort_position= sort_position;
DBUG_RETURN(found_eq_ref);
}
/*
@ brief
Check if it is potentially possible to short - cut the JOIN execution due to
ORDER BY . . . LIMIT clause
@ detail
It is possible when the join has " ORDER BY . . . LIMIT n " clause , and the
sort + limit operation is done right after the join operation ( there ' s no
grouping or DISTINCT in between ) .
Then we can potentially build a join plan that enumerates rows in the
ORDER BY order and so will be able to terminate as soon as it has produced
# limit rows .
Note that it is not a requirement that sort_by_table has an index that
matches ORDER BY . If it doesn ' t have one , the optimizer will pass
sort_by_table to filesort . Reading from sort_by_table won ' t use
short - cutting but the rest of the join will .
*/
static
bool join_limit_shortcut_is_applicable(const JOIN *join)
{
/*
Any post - join operation like GROUP BY or DISTINCT or window functions
means we cannot short - cut join execution
*/
if (!join->thd->variables.optimizer_join_limit_pref_ratio ||
!join->order ||
join->select_limit == HA_POS_ERROR ||
join->group_list ||
join->select_distinct ||
join->select_options & SELECT_BIG_RESULT ||
join->rollup.state != ROLLUP::STATE_NONE ||
join->select_lex->have_window_funcs() ||
join->select_lex->with_sum_func)
{
return false ;
}
/*
Cannot do short - cutting if
( 1 ) ORDER BY refers to more than one table or
( 2 ) the table it refers to cannot be first table in the join order
*/
if (!join->sort_by_table || // (1)
join->sort_by_table->reginfo.join_tab->dependent) // (2)
return false ;
Json_writer_object wrapper(join->thd);
Json_writer_object trace(join->thd, "join_limit_shortcut_is_applicable" );
trace.add("applicable" , 1 );
/* It looks like we can short-cut limit due to join */
return true ;
}
/*
@ brief
Check if we could use an index - based access method to produce rows
in the order for ORDER BY . . . LIMIT .
@ detail
This should do what test_if_skip_sort_order ( ) does . We can ' t use that
function directly , because :
1 . We ' re at the join optimization stage and have not done query plan
fix - ups done in get_best_combination ( ) and co .
2 . The code in test_if_skip_sort_order ( ) does modify query plan structures ,
for example it may change the table ' s quick select . This is done even if
it ' s called with no_changes = true parameter .
@ param access_method_changed OUT Whether the function changed the access
method to get rows in desired order .
@ param new_access_cost OUT if access method changed : its cost .
@ return
true - Can skip sorting
false - Cannot skip sorting
*/
bool test_if_skip_sort_order_early(JOIN *join,
bool *access_method_changed,
double *new_access_cost)
{
const POSITION *pos= &join->best_positions[join->const_tables];
TABLE *table= pos->table->table;
key_map usable_keys= table->keys_in_use_for_order_by;
*access_method_changed= false ;
// Step #1: Find indexes that produce the required ordering.
if (find_indexes_matching_order(join, table, join->order, &usable_keys))
return false ; // Cannot skip sorting
// Step #2: Check if the index we're using produces the needed ordering
uint ref_key;
if (pos->key)
{
// Mirror the (wrong) logic in test_if_skip_sort_order:
if (pos->spl_plan || pos->type == JT_REF_OR_NULL)
return false ; // Use filesort
ref_key= pos->key->key;
}
else
{
if (pos->table->quick)
{
if (pos->table->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE)
ref_key= pos->table->quick->index;
else
ref_key= MAX_KEY;
}
else
ref_key= MAX_KEY;
}
if (ref_key != MAX_KEY && usable_keys.is_set(ref_key))
{
return true ; // we're using an index that produces the required ordering.
}
/*
Step # 3 : check if we can switch to using an index that would produce the
ordering .
( But don ' t actually switch , this will be done by test_if_skip_sort_order )
*/
int best_key= -1 ;
uint UNINIT_VAR(best_key_parts);
uint saved_best_key_parts= 0 ;
int best_key_direction= 0 ;
JOIN_TAB *tab= pos->table;
ha_rows new_limit;
double new_read_time;
if (test_if_cheaper_ordering(/*in_join_optimizer */TRUE,
tab, join->order, table, usable_keys,
ref_key, join->select_limit,
&best_key, &best_key_direction,
&new_limit, &new_read_time,
&best_key_parts,
&saved_best_key_parts))
{
// Ok found a way to skip sorting
*access_method_changed= true ;
*new_access_cost= new_read_time;
return true ;
}
return false ;
}
/*
Compute the cost of join assuming we only need fraction of the output .
*/
double recompute_join_cost_with_limit(const JOIN *join, bool skip_sorting,
double *first_table_cost,
double fraction)
{
POSITION *pos= join->best_positions + join->const_tables;
/*
Generally , we assume that producing X % of output takes X % of the cost .
best_extension_by_limited_search ( ) subtracts COST_EPS from
join - > best_read , add it back .
( Note : before 11 . 0 , we subtracted COST_EPS here . In 11 . 0 + , there ' s no need
to do this )
*/
double partial_join_cost= join->best_read * fraction;
if (skip_sorting)
{
/*
First table produces rows in required order . Two options :
A . first_table_cost = NULL means we use whatever access method the join
optimizer has picked . Its cost was included in join - > best_read and
we ' ve already took a fraction of it .
B . first_table_cost ! = NULL means we will need to switch to another access
method , we have the cost to read rows to produce # LIMIT rows in join
output .
*/
if (first_table_cost)
{
/*
Subtract the remainder of the first table ' s cost we had in
join - > best_read .
( Before 11 . 0 , we also subtracted pos - > records_read / TIME_FOR_COMPARE .
In 11 . 0 + , that time is already included in pos - > read_time )
*/
partial_join_cost -= pos->read_time*fraction;
DBUG_ASSERT(partial_join_cost >= 0 .0 );
/* Add the cost of the new access method we've got: */
partial_join_cost= COST_ADD(partial_join_cost, *first_table_cost);
}
}
else
{
DBUG_ASSERT(!first_table_cost);
/*
Cannot skip sorting . We read the first table entirely , then sort it .
partial_join_cost includes pos - > read_time * fraction . Add to it
pos - > read_time * ( 1 - fraction ) so we have the cost to read the entire first
table . Do the same for costs of checking the WHERE .
*/
double extra_first_table_cost= pos->read_time * (1 .0 - fraction);
partial_join_cost= COST_ADD(partial_join_cost, extra_first_table_cost);
}
return partial_join_cost;
}
/*
@ brief
Finalize building the join order which allows it to short - cut the join
execution .
@ detail
This is called after we have produced a join order that allows short -
cutting .
Here , we decide if it is cheaper to use this one or the original join
order .
*/
POSITION *join_limit_shortcut_finalize_plan(JOIN *join, double *cost)
{
Json_writer_object wrapper(join->thd);
Json_writer_object trace(join->thd, "join_limit_shortcut_choice" );
double fraction= join->select_limit / join->join_record_count;
trace.add("limit_fraction" , fraction);
/* Check which fraction of join output we need */
if (fraction >= 1 .0 )
{
trace.add("skip_adjustment" , "no short-cutting" );
return NULL;
}
/*
Check if the first table ' s access method produces the required ordering .
Possible options :
1 . Yes : we can just take a fraction of the execution cost .
2 A No : change the access method to one that does produce the required
ordering , update the costs .
2 B No : Need to pass the first table to filesort ( ) .
*/
bool skip_sorting;
bool access_method_changed;
double new_access_cost;
{
Json_writer_array tmp(join->thd, "test_if_skip_sort_order_early" );
skip_sorting= test_if_skip_sort_order_early(join,
&access_method_changed,
&new_access_cost);
}
trace.add("can_skip_filesort" , skip_sorting);
double cost_with_shortcut=
recompute_join_cost_with_limit(join, skip_sorting,
access_method_changed ?
&new_access_cost : (double *)0 ,
fraction);
double risk_ratio=
(double )join->thd->variables.optimizer_join_limit_pref_ratio;
trace.add("full_join_cost" , join->best_read);
trace.add("risk_ratio" , risk_ratio);
trace.add("shortcut_join_cost" , cost_with_shortcut);
cost_with_shortcut *= risk_ratio;
trace.add("shortcut_cost_with_risk" , cost_with_shortcut);
if (cost_with_shortcut < join->best_read)
{
trace.add("use_shortcut_cost" , true );
POSITION *pos= (POSITION*)memdup_root(join->thd->mem_root,
join->best_positions,
sizeof (POSITION)*
(join->table_count + 1 ));
*cost= cost_with_shortcut;
return pos;
}
trace.add("use_shortcut_cost" , false );
return NULL;
}
/*
@ brief
If we ' re in Limit Optimization Mode , allow only join - > sort_by_table as
the first table in the join order
*/
static
bool join_limit_shortcut_limits_tables(const JOIN *join, uint idx, table_map *map)
{
if (join->limit_optimization_mode && idx == join->const_tables)
{
*map= join->sort_by_table->map;
return true ;
}
return false ;
}
/**
Find a good , possibly optimal , query execution plan ( QEP ) by a possibly
exhaustive search .
The procedure searches for the optimal ordering of the query tables in set
' remaining_tables ' of size N , and the corresponding optimal access paths to
each table . The choice of a table order and an access path for each table
constitutes a query execution plan ( QEP ) that fully specifies how to
execute the query .
The maximal size of the found plan is controlled by the parameter
' search_depth ' . When search_depth = = N , the resulting plan is complete and
can be used directly as a QEP . If search_depth < N , the found plan consists
of only some of the query tables . Such " partial " optimal plans are useful
only as input to query optimization procedures , and cannot be used directly
to execute a query .
The algorithm begins with an empty partial plan stored in ' join - > positions '
and a set of N tables - ' remaining_tables ' . Each step of the algorithm
evaluates the cost of the partial plan extended by all access plans for
each of the relations in ' remaining_tables ' , expands the current partial
plan with the access plan that results in lowest cost of the expanded
partial plan , and removes the corresponding relation from
' remaining_tables ' . The algorithm continues until it either constructs a
complete optimal plan , or constructs an optimal partial plan with size =
search_depth .
The final optimal plan is stored in ' join - > best_positions ' . The
corresponding cost of the optimal plan is in ' join - > best_read ' .
@ note
The procedure uses a recursive depth - first search where the depth of the
recursion ( and thus the exhaustiveness of the search ) is controlled by the
parameter ' search_depth ' .
@ note
The pseudocode below describes the algorithm of
' best_extension_by_limited_search ' . The worst - case complexity of this
algorithm is O ( N * N ^ search_depth / search_depth ) . When serch_depth > = N , then
the complexity of greedy_search is O ( N ! ) .
@ code
procedure best_extension_by_limited_search (
pplan in , // in, partial plan of tables-joined-so-far
pplan_cost , // in, cost of pplan
remaining_tables , // in, set of tables not referenced in pplan
best_plan_so_far , // in/out, best plan found so far
best_plan_so_far_cost , // in/out, cost of best_plan_so_far
search_depth ) // in, maximum size of the plans being considered
{
for each table T from remaining_tables
{
// Calculate the cost of using table T as above
cost = complex - series - of - calculations ;
// Add the cost to the cost so far.
pplan_cost + = cost ;
if ( pplan_cost > = best_plan_so_far_cost )
// pplan_cost already too great, stop search
continue ;
pplan = expand plan by best_access_method ;
remaining_tables = remaining_tables - table T ;
if ( remaining_tables is not an empty set
and
search_depth > 1 )
{
best_extension_by_limited_search ( pplan , pplan_cost ,
remaining_tables ,
best_plan_so_far ,
best_plan_so_far_cost ,
search_depth - 1 ) ;
}
else
{
best_plan_so_far_cost = pplan_cost ;
best_plan_so_far = pplan ;
}
}
}
@ endcode
@ note
When ' best_extension_by_limited_search ' is called for the first time ,
' join - > best_read ' must be set to the largest possible value ( e . g . DBL_MAX ) .
The actual implementation provides a way to optionally use pruning
heuristic to reduce the search space by skipping some partial plans .
@ note
The parameter ' search_depth ' provides control over the recursion
depth , and thus the size of the resulting optimal plan .
@ param join pointer to the structure providing all context info
for the query
@ param remaining_tables set of tables not included into the partial plan yet
@ param idx length of the partial QEP in ' join - > positions ' ;
since a depth - first search is used , also corresponds
to the current depth of the search tree ;
also an index in the array ' join - > best_ref ' ;
@ param record_count estimate for the number of records returned by the
best partial plan
@ param read_time the cost of the best partial plan
@ param search_depth maximum depth of the recursion and thus size of the
found optimal plan
( 0 < search_depth < = join - > tables + 1 ) .
( values : 0 = EXHAUSTIVE , 1 = PRUNE_BY_TIME_OR_ROWS )
@ param use_cond_selectivity specifies how the selectivity of the conditions
pushed to a table should be taken into account
@ retval
enum_best_search : : SEARCH_OK All fine
@ retval
enum_best_search : : SEARCH_FOUND_EDGE All remaining tables are edge tables
@ retval
enum_best_search : : SEARCH_ABORT Killed by user
@ retval
enum_best_search : : SEARCH_ERROR Fatal error
*/
static enum_best_search
best_extension_by_limited_search(JOIN *join,
table_map remaining_tables,
uint idx,
double record_count,
double read_time,
uint search_depth,
uint use_cond_selectivity,
table_map *processed_eq_ref_tables)
{
THD *thd= join->thd;
/*
' join ' is a partial plan with lower cost than the best plan so far ,
so continue expanding it further with the tables in ' remaining_tables ' .
*/
JOIN_TAB *s;
double best_record_count= DBL_MAX;
double best_read_time= DBL_MAX;
enum_best_search best_res;
uint tables_left= join->table_count - idx, found_tables;
uint accepted_tables __attribute__((unused));
table_map found_eq_ref_tables= 0 , used_eq_ref_table= 0 ;
table_map allowed_tables, allowed_current_tables;
SORT_POSITION *sort= (SORT_POSITION*) alloca(sizeof (SORT_POSITION)*tables_left);
SORT_POSITION *sort_end;
DBUG_ENTER("best_extension_by_limited_search" );
DBUG_EXECUTE_IF("show_explain_probe_best_ext_lim_search" ,
if (dbug_user_var_equals_int(thd,
"show_explain_probe_select_id" ,
join->select_lex->select_number))
dbug_serve_apcs(thd, 1 );
);
if (unlikely(thd->check_killed())) // Abort
DBUG_RETURN(SEARCH_ABORT);
DBUG_EXECUTE("opt" , print_plan(join, idx, record_count, read_time, read_time,
"part_plan" ););
status_var_increment(thd->status_var.optimizer_join_prefixes_check_calls);
if (join->emb_sjm_nest)
{
/*
If we are searching for the execution plan of a materialized semi - join nest
then allowed_tables contains bits only for the tables from this nest .
*/
allowed_tables= (join->emb_sjm_nest->sj_inner_tables & remaining_tables);
allowed_current_tables= join->get_allowed_nj_tables(idx) & remaining_tables;
}
else
{
/*
allowed_tables is used to check if there are tables left that can improve
a key search and to see if there are more tables to add in next iteration .
allowed_current_tables tells us which tables we can add to the current
plan at this stage .
*/
allowed_tables= remaining_tables;
allowed_current_tables= join->get_allowed_nj_tables(idx) & remaining_tables;
table_map sort_table;
if (join_limit_shortcut_limits_tables(join, idx, &sort_table))
allowed_current_tables= sort_table;
}
DBUG_ASSERT(allowed_tables & remaining_tables);
sort_end= sort;
{
Json_writer_object trace_one_table(thd);
JOIN_TAB **best_ref= join->best_ref + idx;
if (unlikely(thd->trace_started()))
trace_plan_prefix(&trace_one_table, join, idx, remaining_tables);
Json_writer_array arr(thd, "get_costs_for_tables" );
if (idx > join->const_tables && join->prune_level >= 2 &&
join->positions[idx-1 ].type == JT_EQ_REF &&
(join->eq_ref_tables & allowed_current_tables))
{
/* Previous table was an EQ REF table, only add other possible EQ_REF
tables to the chain , stop after first one is found .
*/
table_map table_map= join->eq_ref_tables & allowed_current_tables;
if (get_costs_for_tables(join, remaining_tables, idx, record_count,
&trace_one_table, best_ref, &sort_end,
&table_map, 1 ))
used_eq_ref_table= (*sort->join_tab)->table->map;
else
{
/* We didn't find another EQ_REF table, add remaining tables */
if ((table_map= allowed_current_tables & ~table_map))
get_costs_for_tables(join, remaining_tables, idx, record_count,
&trace_one_table, best_ref, &sort_end, &table_map,
0 );
}
}
else
{
table_map table_map= allowed_current_tables;
get_costs_for_tables(join, remaining_tables, idx, record_count,
&trace_one_table, best_ref, &sort_end, &table_map,
0 );
}
found_tables= (uint) (sort_end - sort);
DBUG_ASSERT(found_tables > 0 );
/*
Sort tables in ascending order of generated row combinations
*/
if (found_tables > 1 )
my_qsort(sort, found_tables, sizeof (SORT_POSITION), sort_positions);
}
DBUG_ASSERT(join->next_sort_position <=
join->sort_positions + join->sort_space);
accepted_tables= 0 ;
double min_rec_count= DBL_MAX;
double min_rec_count_read_time= DBL_MAX;
double min_cost= DBL_MAX;
double min_cost_record_count= DBL_MAX;
for (SORT_POSITION *pos= sort ; pos < sort_end ; pos++)
{
s= *pos->join_tab;
if (!(found_eq_ref_tables & s->table->map) &&
!check_interleaving_with_nj(s))
{
table_map real_table_bit= s->table->map;
double current_record_count, current_read_time, original_record_count;
double partial_join_cardinality;
POSITION *position= join->positions + idx, *loose_scan_pos;
double pushdown_cond_selectivity;
Json_writer_object trace_one_table(thd);
if (unlikely(thd->trace_started()))
{
trace_plan_prefix(&trace_one_table, join, idx, remaining_tables);
trace_one_table.add_table_name(s);
}
accepted_tables++;
*position= *pos->position; // Get stored result
loose_scan_pos= pos->position+1 ;
/* Compute the cost of the new plan extended with 's' */
current_record_count= COST_MULT(record_count, position->records_out);
current_read_time= COST_ADD(read_time, position->read_time);
if (unlikely(trace_one_table.trace_started()))
{
trace_one_table.
add("rows_for_plan" , current_record_count).
add("cost_for_plan" , current_read_time);
}
original_record_count= current_record_count;
optimize_semi_joins(join, remaining_tables, idx, ¤t_record_count,
¤t_read_time, loose_scan_pos);
if (position->sj_strategy != SJ_OPT_NONE)
{
/* Adjust records_out and current_record_count after semi join */
double ratio= current_record_count / original_record_count;
if (ratio < 1 .0 )
position->records_out*= ratio;
if (unlikely(trace_one_table.trace_started()))
{
trace_one_table.
add("sj_rows_out" , position->records_out).
add("sj_rows_for_plan" , current_record_count).
add("sj_filtered" , safe_filtered(position->records_out,
position->records_init));
}
}
/* Expand only partial plans with lower cost than the best QEP so far */
if (current_read_time + COST_EPS >= join->best_read)
{
DBUG_EXECUTE("opt" , print_plan(join, idx+1 ,
current_record_count,
read_time,
current_read_time,
"prune_by_cost" ););
trace_one_table
.add("pruned_by_cost" , true )
.add("current_cost" , current_read_time)
.add("best_cost" , join->best_read);
restore_prev_nj_state(s);
restore_prev_sj_state(remaining_tables, s, idx);
continue ;
}
/*
Prune some less promising partial plans . This heuristic may miss
the optimal QEPs , thus it results in a non - exhaustive search .
*/
if (join->prune_level >= 1 )
{
// Collect the members with min_cost and min_read_time.
bool min_rec_hit= false ;
bool min_cost_hit= false ;
if (join->extra_heuristic_pruning &&
(!(position->key_dependent & allowed_tables) ||
position->records_read < 2 .0 ))
{
if (current_record_count < min_rec_count)
{
min_rec_count= current_record_count;
min_rec_count_read_time= current_read_time;
min_rec_hit= true ;
}
if (current_read_time < min_cost)
{
min_cost_record_count= current_record_count;
min_cost= current_read_time;
min_cost_hit= true ;
}
}
if (best_record_count > current_record_count ||
best_read_time > current_read_time ||
(idx == join->const_tables && // 's' is the first table in the QEP
s->table == join->sort_by_table))
{
/*
Store the current record count and cost as the best
possible cost at this level if the following holds :
- It ' s the lowest record number and cost so far
- There is no remaining table that could improve index usage
or we found an EQ_REF or REF key with less than 2
matching records ( good enough ) .
*/
if (best_record_count >= current_record_count &&
best_read_time >= current_read_time &&
(!(position->key_dependent & join->allowed_tables) ||
position->records_read < 2 .0 ))
{
best_record_count= current_record_count;
best_read_time= current_read_time;
}
}
else
{
/*
Typically , we get here if :
best_record_count < current_record_count & &
best_read_time < current_read_time
That is , both record_count and read_time are worse than the best_
ones . This plan doesn ' t look promising , prune it away .
*/
DBUG_EXECUTE("opt" , print_plan(join, idx+1 ,
current_record_count,
read_time,
current_read_time,
"pruned_by_heuristic" ););
trace_one_table.add("pruned_by_heuristic" , true );
restore_prev_nj_state(s);
restore_prev_sj_state(remaining_tables, s, idx);
continue ;
}
const char * prune_reason= NULL;
if (!min_rec_hit &&
current_record_count >= min_rec_count &&
current_read_time >= min_rec_count_read_time)
prune_reason= "min_record_count" ;
if (!min_cost_hit &&
current_record_count >= min_cost_record_count &&
current_read_time >= min_cost)
prune_reason= "min_read_time" ;
if (prune_reason)
{
trace_one_table.add("pruned_by_heuristic" , prune_reason);
restore_prev_nj_state(s);
restore_prev_sj_state(remaining_tables, s, idx);
continue ;
}
}
pushdown_cond_selectivity= 1 .0 ;
/*
TODO : When a semi - join strategy is applied ( sj_strategy ! = SJ_OPT_NONE ) ,
we should account for selectivity from table_after_join_selectivity ( ) .
( Condition filtering is performed before the semi - join removes some
fanout so this might require moving the code around )
*/
if (use_cond_selectivity > 1 && position->sj_strategy == SJ_OPT_NONE)
{
pushdown_cond_selectivity=
table_after_join_selectivity(join, idx, s,
remaining_tables & ~real_table_bit,
&position->records_out);
if (unlikely(trace_one_table.trace_started()) &&
pushdown_cond_selectivity != 1 .0 )
trace_one_table.
add("pushdown_cond_selectivity" , pushdown_cond_selectivity).
add("filtered" , safe_filtered(position->records_out,
position->records_init)).
add("rows_out" , position->records_out);
}
join->positions[idx].cond_selectivity= pushdown_cond_selectivity;
partial_join_cardinality= record_count * position->records_out;
join->positions[idx].partial_join_cardinality= partial_join_cardinality;
if (unlikely(thd->trace_started()) && pushdown_cond_selectivity < 1 .0 &&
partial_join_cardinality < current_record_count)
trace_one_table
.add("selectivity" , pushdown_cond_selectivity)
.add("estimated_join_cardinality" , partial_join_cardinality);
if ((search_depth > 1 ) && (remaining_tables & ~real_table_bit) &
allowed_tables)
{
/* Recursively expand the current partial plan */
Json_writer_array trace_rest(thd, "rest_of_plan" );
swap_variables(JOIN_TAB*, join->best_ref[idx], *pos->join_tab);
best_res=
best_extension_by_limited_search(join,
remaining_tables &
~real_table_bit,
idx + 1 ,
partial_join_cardinality,
current_read_time,
search_depth - 1 ,
use_cond_selectivity,
&found_eq_ref_tables);
swap_variables(JOIN_TAB*, join->best_ref[idx], *pos->join_tab);
if ((int ) best_res < (int ) SEARCH_OK)
goto end; // Return best_res
if (best_res == SEARCH_FOUND_EDGE &&
check_if_edge_table(join->positions+ idx,
pushdown_cond_selectivity) !=
SEARCH_FOUND_EDGE)
best_res= SEARCH_OK;
}
else
{
/*
' join ' is either the best partial QEP with ' search_depth ' relations ,
or the best complete QEP so far , whichever is smaller .
*/
if (join->sort_by_table &&
join->sort_by_table !=
join->positions[join->const_tables].table->table)
{
/*
We may have to make a temp table , note that this is only a
heuristic since we cannot know for sure at this point if we
we are going to use addon fields or to have flush sorting to
disk . We also don ' t know the temporary table will be in memory
or disk .
The following calculation takes a middle ground where assume
we can sort the keys in memory but have to use a disk based
temporary table to retrieve the rows .
This cost is probably much bigger than it has to be . . .
*/
double sort_cost;
sort_cost= (get_qsort_sort_cost((ha_rows)current_record_count,0 ) +
current_record_count *
DISK_TEMPTABLE_LOOKUP_COST(thd));
trace_one_table.add("cost_for_sorting" , sort_cost);
current_read_time= COST_ADD(current_read_time, sort_cost);
}
if (current_read_time < join->best_read)
{
memcpy((uchar*) join->best_positions, (uchar*) join->positions,
sizeof (POSITION) * (idx + 1 ));
join->join_record_count= partial_join_cardinality;
join->best_read= current_read_time;
}
DBUG_EXECUTE("opt" , print_plan(join, idx+1 ,
current_record_count,
read_time,
current_read_time,
"full_plan" ););
best_res= check_if_edge_table(join->positions + idx,
pushdown_cond_selectivity);
}
restore_prev_nj_state(s);
restore_prev_sj_state(remaining_tables, s, idx);
if (best_res == SEARCH_FOUND_EDGE)
{
if (pos+1 < sort_end) // If not last table
trace_one_table.add("pruned_by_hanging_leaf" , true );
goto end;
}
}
}
DBUG_ASSERT(accepted_tables > 0 );
best_res= SEARCH_OK;
end:
join->next_sort_position-= found_tables*2 ;
if (used_eq_ref_table)
*processed_eq_ref_tables|= used_eq_ref_table | found_eq_ref_tables;
else
*processed_eq_ref_tables= 0 ;
DBUG_RETURN(best_res);
}
/**
Find how much space the previous read not const tables takes in cache .
*/
void JOIN_TAB::calc_used_field_length(bool max_fl)
{
uint null_fields,blobs,fields;
ulong rec_length;
Field **f_ptr,*field;
uint uneven_bit_fields;
MY_BITMAP *read_set= table->read_set;
uneven_bit_fields= null_fields= blobs= fields= rec_length=0 ;
for (f_ptr=table->field ; (field= *f_ptr) ; f_ptr++)
{
if (bitmap_is_set(read_set, field->field_index))
{
uint flags=field->flags;
fields++;
rec_length+=field->pack_length();
if (flags & BLOB_FLAG)
blobs++;
if (!(flags & NOT_NULL_FLAG))
null_fields++;
if (field->type() == MYSQL_TYPE_BIT &&
((Field_bit*)field)->bit_len)
uneven_bit_fields++;
}
}
if (null_fields || uneven_bit_fields)
rec_length+=(table->s->null_fields+7 )/8 ;
if (table->maybe_null)
rec_length+=sizeof (my_bool);
/* Take into account that DuplicateElimination may need to store rowid */
uint rowid_add_size= 0 ;
if (keep_current_rowid)
{
rowid_add_size= table->file->ref_length;
rec_length += rowid_add_size;
fields++;
}
if (max_fl)
{
// TODO: to improve this estimate for max expected length
if (blobs)
{
ulong blob_length= table->file->stats.mean_rec_length;
if (ULONG_MAX - rec_length > blob_length)
rec_length+= blob_length;
else
rec_length= ULONG_MAX;
}
max_used_fieldlength= rec_length;
}
else if (table->file->stats.mean_rec_length)
set_if_smaller(rec_length, table->file->stats.mean_rec_length + rowid_add_size);
used_fields=fields;
used_fieldlength=rec_length;
used_blobs=blobs;
used_null_fields= null_fields;
used_uneven_bit_fields= uneven_bit_fields;
}
/*
@ brief
Extract pushdown conditions for a table scan
@ details
This functions extracts pushdown conditions usable when this table is scanned .
The conditions are extracted either from WHERE or from ON expressions .
The conditions are attached to the field cache_select of this table .
@ note
Currently the extracted conditions are used only by BNL and BNLH join .
algorithms .
@ retval 0 on success
1 otherwise
*/
int JOIN_TAB::make_scan_filter()
{
COND *tmp;
DBUG_ENTER("make_scan_filter" );
Item *cond= is_inner_table_of_outer_join() ?
*get_first_inner_table()->on_expr_ref : join->conds;
if (cond)
{
if ((tmp= make_cond_for_table(join->thd, cond,
join->const_table_map | table->map,
table->map, -1 , FALSE , TRUE )))
{
DBUG_EXECUTE("where" ,print_where(tmp,"cache" , QT_ORDINARY););
if (!(cache_select=
(SQL_SELECT*) join->thd->memdup((uchar*) select,
sizeof (SQL_SELECT))))
DBUG_RETURN(1 );
cache_select->cond= tmp;
cache_select->read_tables=join->const_table_map;
}
else if (join->thd->is_error())
DBUG_RETURN(1 );
}
DBUG_RETURN(0 );
}
/**
@ brief
Check whether hash join algorithm can be used to join this table
@ details
This function finds out whether the ref items that have been chosen
by the planner to access this table can be used for hash join algorithms .
The answer depends on a certain property of the fields of the
joined tables on which the hash join key is built .
@ note
At present the function is supposed to be called only after the function
get_best_combination has been called .
@ retval TRUE it ' s possible to use hash join to join this table
@ retval FALSE otherwise
*/
bool JOIN_TAB::hash_join_is_possible()
{
if (type != JT_REF && type != JT_EQ_REF)
return FALSE ;
if (!is_ref_for_hash_join())
{
KEY *keyinfo= table->key_info + ref.key;
return keyinfo->key_part[0 ].field->hash_join_is_possible();
}
return TRUE ;
}
/**
@ brief
Check whether a KEYUSE can be really used for access this join table
@ param join Join structure with the best join order
for which the check is performed
@ param keyuse Evaluated KEYUSE structure
@ details
This function is supposed to be used after the best execution plan have been
already chosen and the JOIN_TAB array for the best join order been already set .
For a given KEYUSE to access this JOIN_TAB in the best execution plan the
function checks whether it really can be used . The function first performs
the check with access_from_tables_is_allowed ( ) . If it succeeds it checks
whether the keyuse - > val does not use some fields of a materialized semijoin
nest that cannot be used to build keys to access outer tables .
Such KEYUSEs exists for the query like this :
select * from ot
where ot . c in ( select it1 . c from it1 , it2 where it1 . c = f ( it2 . c ) )
Here we have two KEYUSEs to access table ot : with val = it1 . c and val = f ( it2 . c ) .
However if the subquery was materialized the second KEYUSE cannot be employed
to access ot .
@ retval true the given keyuse can be used for ref access of this JOIN_TAB
@ retval false otherwise
*/
bool JOIN_TAB::keyuse_is_valid_for_access_in_chosen_plan(JOIN *join,
KEYUSE *keyuse)
{
if (!access_from_tables_is_allowed(keyuse->used_tables,
join->sjm_lookup_tables))
return false ;
if (join->sjm_scan_tables & table->map)
return true ;
table_map keyuse_sjm_scan_tables= keyuse->used_tables &
join->sjm_scan_tables;
if (!keyuse_sjm_scan_tables)
return true ;
uint sjm_tab_nr= 0 ;
while (!(keyuse_sjm_scan_tables & table_map(1 ) << sjm_tab_nr))
sjm_tab_nr++;
JOIN_TAB *sjm_tab= join->map2table[sjm_tab_nr];
TABLE_LIST *emb_sj_nest= sjm_tab->emb_sj_nest;
if (!(emb_sj_nest->sj_mat_info && emb_sj_nest->sj_mat_info->is_used &&
emb_sj_nest->sj_mat_info->is_sj_scan))
return true ;
st_select_lex *sjm_sel= emb_sj_nest->sj_subq_pred->unit->first_select();
for (uint i= 0 ; i < sjm_sel->item_list.elements; i++)
{
DBUG_ASSERT(sjm_sel->ref_pointer_array[i]->real_item()->type() == Item::FIELD_ITEM);
if (keyuse->val->real_item()->type() == Item::FIELD_ITEM)
{
Field *field = ((Item_field*)sjm_sel->ref_pointer_array[i]->real_item())->field;
if (field->eq(((Item_field*)keyuse->val->real_item())->field))
return true ;
}
}
return false ;
}
static uint
cache_record_length(JOIN *join,uint idx)
{
uint length=0 ;
JOIN_TAB **pos,**end;
for (pos=join->best_ref+join->const_tables,end=join->best_ref+idx ;
pos != end ;
pos++)
{
JOIN_TAB *join_tab= *pos;
length+= join_tab->get_used_fieldlength();
}
return length;
}
/*
Estimate the number of engine ha_index_read_calls for EQ_REF tables
when taking into account the one - row - cache in join_read_always_key ( )
SYNOPSIS
@ param position All previous tables best_access_path ( ) information .
@ param idx Number of ( previous ) tables in positions .
@ param record_count Number of incoming record combinations
@ param found_ref Bitmap of tables that is used to construct the key
used with the index read .
@ return # The number of estimated calls that cannot be cached by the
the one - row - cache . In other words , number of expected
calls to engine ha_read_read_map ( ) .
Between 1 and record_count or 0 if record_count = = 0
DESCRIPTION
The one - row - cache gives a great benefit when there are multiple consecutive
calls to ha_index_read ( ) with the same key . In this case we can skip
calling the engine ( and in the future also skip to check the key
condition ) , which can notably increase the performance .
Assuming most of the rows are cached , there is no notable saving to be
made trying to calculate the total number of distinct key values that will
be used . The performance of a ha_index_read_call ( ) is about the same even
if we repeatedly read the same set of rows .
This code works by calculating the number of identical key sequences
found in the record stream .
The number of expected distinct calls can then be calculated as
records_count / sequences .
Some things to note :
- record_count = = PRODUCT ( records_out ) over all tables [ 0 . . . idx - 1 ]
- position - > prev_record_reads contains the number of identical
sequences found for previous EQ_REF tables .
Assume a join prefix of t1 , t2 , t3 , t4 and t4 is an EQ_REF table .
We have the following combinations that we have to consider :
= = = = = =
1 ) No JOIN_CACHE usage , tables depend only on one previous table
Row combinations are generated as :
- for all rows in t1
- for all rows in t2
- for all rows in t3
or
t1 . 1 , t2 . 1 , t3 . 1 , t1 . 1 , t2 . 1 , t3 . 2 , t1 . 1 , t2 . 1 , t3 . 3 . . . # Only t3 row changes
( until no more rows in t3 . , ie t3 . records_out times )
t1 . 1 , t2 . 2 , t3 . 1 , t1 . 1 , t2 . 2 , t3 . 2 , t1 . 1 , t2 . 2 , t3 . 3 . . . # t2 . 2 read
( above repeated until no more rows in t2 and t3 )
t1 . 2 , t2 . 1 , t3 . 1 , t1 . 2 , t2 . 1 , t3 . 2 , t1 . 2 , t2 . 1 , t3 . 3 . . . # t1 . 2 read
If t4 is an EQ_REF table that is depending of one of the
previous tables , the number of identical keys can be calculated
as the multiplication of records_out of the tables in between
the t4 and its first dependency .
Let ' s consider cases where t4 depends on different previous tables :
WHERE t4 . a = t3 . a
no caching as t3 can change for each row
engine_calls : record_count
WHERE t4 . a = t2 . a
t4 is not depending on t3 . The number of repeated rows are :
t1 . 1 , t2 . 1 , t3 . 1 to t1 . 1 , t2 . 1 , t3 . last # t3 . records_out rows
t1 . 1 , t2 . 2 , t3 . 1 to t1 . 1 , t2 . 2 , t3 . last # t3 . records_out rows
. . .
t1 . 2 , t2 . 1 , t3 . 1 to t1 . 2 , t2 . 1 , t3 . last
. . .
t1 . last , t2 . last . t3 . 1 to t1 . last , t2 . last . 1 , t3 . last
For each combination of t1 and t2 there are t3 . records_out repeated
rows with equal key value
engine_calls : record_count / t3 . records_out calls =
t1 . records_out * t2 . records_out
WHERE t4 . a = t1 . a
The repeated sequences :
t1 . 1 , t2 . 1 , t3 . 1 to t1 . 1 , t2 . last , t3 . last
t1 . 2 , t2 . 1 , t3 . 1 to t2 . 1 , t2 . last , t3 . last
repeated rows : t2 . records_out * t3 . records_out
engine_calls : record_count / repeated_rows = t1 . records_out
If t4 depends on a table that uses EQ_REF access , we can multiply that
table ' s repeated_rows with current table ' s repeated_rows to take that
into account .
= = = = =
2 ) Keys depending on multiple tables
In this case we have to stop searching after we find the first
table we depend upon .
We have to also disregard the number of repeated rows for the
found table . This can be seen from ( assuming tables t1 . . . t6 ) :
WHERE t6 . a = t4 . a and t6 . a = t3 . a and t4 . a = t2 . a
- Here t4 is not depending on t3 ( and thus there is a
t3 . records_out identical keys for t4 ) . However t6 key will
change for each t3 row and t6 cannot thus use
t3 . identical_keys
WHERE t4 . key_part1 = t1 . a and t4 . key_part2 = t3 . a
As t4 . key_part2 will change for every row , one - row - cache will not
be hit
WHERE t4 . key_part1 = t1 . a and t4 . key_part2 = t2 . a
t4 . key will change when t1 or t2 changes
This is the same case as above for WHERE t4 . a = t2 . a
engine_calls : record_count / t3 . records_out calls
= = = = =
3 ) JOIN_CACHE is used
If any table is using join_cache as this changes the row
combinations seen by following tables . Using join cache for a
table T # will have T # rows repeated for the next table as many
times there are combinations in the cache . The the cache will
re - read and the operations repeats ' refill - 1 ' number of times .
Table rows from table just before T # will come in ' random order ' ,
from the point of the next tables .
Assuming t3 is using a cache , t4 will see the rows coming in the
following order :
t1 . 1 , t2 . 1 , t3 . 1 , t1 . 1 , t2 . 2 , t3 . 1 , t1 . 1 , t2 . 3 , t3 . 1 . . .
( t3 . 1 repeated ' t2 . records_out ' times )
t1 . 2 , t2 . 1 , t3 . 1 , t1 . 2 , t2 . 2 , t3 . 1 , t1 . 2 , t2 . 3 , t3 . 1 . . .
( Next row in t1 used )
t1 . 1 , t2 . 1 , t3 . 2 , t1 . 1 , t2 . 2 , t3 . 2 , t1 . 1 , t2 . 3 , t3 . 2 . . .
( Restarting all t1 & t2 combinations for t3 . 2 )
WHERE t4 . a = t3 . a
- There is a repeated sequence of t3 . records_out rows for
each t1 , t2 row combination .
engine_calls = record_count / t3 . records_out
WHERE t4 . a = t2 . a
t2 changes for each row
engine_calls = record_count
WHERE t4 . a = t1 . a
repeated rows = t2 . records_out
engine_calls = record_count / t2 . records_out
A refill of the join cache will restart the row sequences
( we have ' refill ' more sequences ) , so we will have to do ' refill ' times
more engine read calls .
= = = = =
Expectations of the accuracy of the return value
- The value is always between 1 and record_count
- The returned value should almost always larger than the true number of
engine calls .
- Assuming that every row has different values for all other columns for
echo unique key value and record_count is accurate :
- If a table is depending on multiple tables , the return value may be
notable larger than real value .
- If there is no join cache the value should be exact .
- If there is a join cache , but no refills calculated or done then
the value should be exact .
- If there was more join_cache refills than was calculated , the value
may be slightly to low .
- If the number of refills is equal or less than was calculated the value
should be larger than the expected engine read calls . The more refills ,
the less exact the number will be .
*/
static double
prev_record_reads(const POSITION *position, uint idx, table_map found_ref,
double record_count, double *identical_keys)
{
double found= 1 .0 ;
const POSITION *pos_end= position - 1 ;
const POSITION *cur_pos= position + idx;
/* Safety against const tables */
if (unlikely(!found_ref))
goto end;
for (const POSITION *pos= cur_pos-1 ; pos != pos_end; pos--)
{
if (found_ref & pos->table->table->map)
{
/* Found a table we depend on */
found_ref= ~pos->table->table->map;
if (!found_ref)
{
/*
No more dependencies . We can use the cached values to improve things
a bit
*/
if (pos->type == JT_EQ_REF)
found= COST_MULT(found, pos->identical_keys);
else if (pos->use_join_buffer)
found= COST_MULT(found, pos->loops / pos->refills);
}
break ;
}
if (unlikely(pos->use_join_buffer))
{
/* Each refill can change the cached key */
found/= pos->refills;
}
else
{
/*
We are not depending on the current table .
There are ' records_out ' rows with identical rows
value for our depending tables .
*/
found= COST_MULT(found, pos->records_out);
}
}
/*
In most case found should < = record_count .
However if there was a reduction of rows ( records_out < 1 ) before
the referencing table then found could be > = record_count .
To get resonable numbers , we limit prev_record_read to be between
1 . 0 and record_count as we have to always do at least one read
anyway .
*/
end:
if (unlikely(found > record_count))
found= record_count;
if (unlikely(found <= 1 .0 ))
found= 1 .0 ;
*identical_keys= found;
return record_count / found;
}
/*
Enumerate join tabs in breadth - first fashion , including const tables .
*/
static JOIN_TAB *next_breadth_first_tab(JOIN_TAB *first_top_tab,
uint n_top_tabs_count, JOIN_TAB *tab)
{
/*
tab - > join = = NULL means that we ' re performing JOIN : : cleanup ( )
after a raised error : on EOM , or on an attempt to create a temporary table
with a column of a non allowed data type , such as SYS_REFCURSOR .
*/
DBUG_ASSERT(tab->join || current_thd->is_error());
if (tab->join)
n_top_tabs_count += tab->join->aggr_tables;
if (!tab->bush_root_tab)
{
/* We're at top level. Get the next top-level tab */
tab++;
if (tab < first_top_tab + n_top_tabs_count)
return tab;
/* No more top-level tabs. Switch to enumerating SJM nest children */
tab= first_top_tab;
}
else
{
/* We're inside of an SJM nest */
if (!tab->last_leaf_in_bush)
{
/* There's one more table in the nest, return it. */
return ++tab;
}
else
{
/*
There are no more tables in this nest . Get out of it and then we ' ll
proceed to the next nest .
*/
tab= tab->bush_root_tab + 1 ;
}
}
/*
Ok , " tab " points to a top - level table , and we need to find the next SJM
nest and enter it .
*/
for (; tab < first_top_tab + n_top_tabs_count; tab++)
{
if (tab->bush_children)
return tab->bush_children->start;
}
return NULL;
}
/*
Enumerate JOIN_TABs in " EXPLAIN order " . This order
- const tabs are included
- we enumerate " optimization tabs " .
-
*/
JOIN_TAB *first_explain_order_tab(JOIN* join)
{
JOIN_TAB* tab;
tab= join->join_tab;
if (!tab)
return NULL; /* Can happen when the tables were optimized away */
return (tab->bush_children) ? tab->bush_children->start : tab;
}
JOIN_TAB *next_explain_order_tab(JOIN* join, JOIN_TAB* tab)
{
/* If we're inside SJM nest and have reached its end, get out */
if (tab->last_leaf_in_bush)
return tab->bush_root_tab;
/* Move to next tab in the array we're traversing */
tab++;
if (tab == join->join_tab + join->top_join_tab_count)
return NULL; /* Outside SJM nest and reached EOF */
if (tab->bush_children)
return tab->bush_children->start;
return tab;
}
JOIN_TAB *first_top_level_tab(JOIN *join, enum enum_with_const_tables const_tbls)
{
JOIN_TAB *tab= join->join_tab;
if (const_tbls == WITHOUT_CONST_TABLES)
{
if (join->const_tables == join->table_count || !tab)
return NULL;
tab += join->const_tables;
}
return tab;
}
JOIN_TAB *next_top_level_tab(JOIN *join, JOIN_TAB *tab)
{
tab= next_breadth_first_tab(join->first_breadth_first_tab(),
join->top_join_tab_count, tab);
if (tab && tab->bush_root_tab)
tab= NULL;
return tab;
}
JOIN_TAB *first_linear_tab(JOIN *join,
enum enum_with_bush_roots include_bush_roots,
enum enum_with_const_tables const_tbls)
{
JOIN_TAB *first= join->join_tab;
if (!first)
return NULL;
if (const_tbls == WITHOUT_CONST_TABLES)
first+= join->const_tables;
if (first >= join->join_tab + join->top_join_tab_count)
return NULL; /* All are const tables */
if (first->bush_children && include_bush_roots == WITHOUT_BUSH_ROOTS)
{
/* This JOIN_TAB is a SJM nest; Start from first table in nest */
return first->bush_children->start;
}
return first;
}
/*
A helper function to loop over all join ' s join_tab in sequential fashion
DESCRIPTION
Depending on include_bush_roots parameter , JOIN_TABs that represent
SJM - scan / lookups are either returned or omitted .
SJM - Bush children are returned right after ( or in place of ) their container
join tab ( TODO : does anybody depend on this ? A : make_join_readinfo ( ) seems
to )
For example , if we have this structure :
ot1 - - ot2 - - sjm1 - - - - - - - - - - - - - - - - ot3 - . . .
|
+ - - it1 - - it2 - - it3
calls to next_linear_tab ( include_bush_roots = TRUE ) will return :
ot1 ot2 sjm1 it1 it2 it3 ot3 . . .
while calls to next_linear_tab ( include_bush_roots = FALSE ) will return :
ot1 ot2 it1 it2 it3 ot3 . . .
( note that sjm1 won ' t be returned ) .
*/
JOIN_TAB *next_linear_tab(JOIN* join, JOIN_TAB* tab,
enum enum_with_bush_roots include_bush_roots)
{
if (include_bush_roots == WITH_BUSH_ROOTS && tab->bush_children)
{
/* This JOIN_TAB is a SJM nest; Start from first table in nest */
return tab->bush_children->start;
}
DBUG_ASSERT(!tab->last_leaf_in_bush || tab->bush_root_tab);
if (tab->bush_root_tab) /* Are we inside an SJM nest */
{
/* Inside SJM nest */
if (!tab->last_leaf_in_bush)
return tab+1 ; /* Return next in nest */
/* Continue from the sjm on the top level */
tab= tab->bush_root_tab;
}
/* If no more JOIN_TAB's on the top level */
if (++tab >= join->join_tab + join->exec_join_tab_cnt() + join->aggr_tables)
return NULL;
if (include_bush_roots == WITHOUT_BUSH_ROOTS && tab->bush_children)
{
/* This JOIN_TAB is a SJM nest; Start from first table in nest */
tab= tab->bush_children->start;
}
return tab;
}
/*
Start to iterate over all join tables in bush - children - first order , excluding
the const tables ( see next_depth_first_tab ( ) comment for details )
*/
JOIN_TAB *first_depth_first_tab(JOIN* join)
{
JOIN_TAB* tab;
/* This means we're starting the enumeration */
if (join->const_tables == join->top_join_tab_count || !join->join_tab)
return NULL;
tab= join->join_tab + join->const_tables;
return (tab->bush_children) ? tab->bush_children->start : tab;
}
/*
A helper function to iterate over all join tables in bush - children - first order
DESCRIPTION
For example , for this join plan
ot1 - - ot2 - - sjm1 - - - - - - - - - - - - ot3 - . . .
|
|
it1 - - it2 - - it3
call to first_depth_first_tab ( ) will return ot1 , and subsequent calls to
next_depth_first_tab ( ) will return :
ot2 it1 it2 it3 sjm ot3 . . .
*/
JOIN_TAB *next_depth_first_tab(JOIN* join, JOIN_TAB* tab)
{
/* If we're inside SJM nest and have reached its end, get out */
if (tab->last_leaf_in_bush)
return tab->bush_root_tab;
/* Move to next tab in the array we're traversing */
tab++;
if (tab == join->join_tab +join->top_join_tab_count)
return NULL; /* Outside SJM nest and reached EOF */
if (tab->bush_children)
return tab->bush_children->start;
return tab;
}
bool JOIN::check_two_phase_optimization(THD *thd)
{
if (check_for_splittable_materialized())
return true ;
return false ;
}
bool JOIN::inject_cond_into_where(Item *injected_cond)
{
Item *where_item= injected_cond;
List<Item> *and_args= NULL;
if (conds && conds->type() == Item::COND_ITEM &&
((Item_cond*) conds)->functype() == Item_func::COND_AND_FUNC)
{
and_args= ((Item_cond*) conds)->argument_list();
if (cond_equal)
and_args->disjoin((List<Item> *) &cond_equal->current_level);
}
where_item= and_items(thd, conds, where_item);
if (where_item->fix_fields_if_needed(thd, 0 ))
return true ;
thd->change_item_tree(&select_lex->where, where_item);
select_lex->where->top_level_item();
conds= select_lex->where;
if (and_args && cond_equal)
{
and_args= ((Item_cond*) conds)->argument_list();
List_iterator<Item_equal> li(cond_equal->current_level);
Item_equal *elem;
while ((elem= li++))
{
and_args->push_back(elem, thd->mem_root);
}
}
return false ;
}
static Item * const null_ptr= NULL;
/*
Set up join struct according to the picked join order in
SYNOPSIS
get_best_combination ( )
join The join to process ( the picked join order is mainly in
join - > best_positions )
DESCRIPTION
Setup join structures according the picked join order
- finalize semi - join strategy choices ( see
fix_semijoin_strategies_for_picked_join_order )
- create join - > join_tab array and put there the JOIN_TABs in the join order
- create data structures describing ref access methods .
NOTE
In this function we switch from pre - join - optimization JOIN_TABs to
post - join - optimization JOIN_TABs . This is achieved by copying the entire
JOIN_TAB objects .
RETURN
FALSE OK
TRUE Out of memory
*/
bool JOIN::get_best_combination()
{
uint tablenr;
table_map used_tables;
JOIN_TAB *j;
KEYUSE *keyuse;
JOIN_TAB *sjm_nest_end= NULL;
JOIN_TAB *sjm_nest_root= NULL;
DBUG_ENTER("get_best_combination" );
/*
Additional plan nodes for postjoin tmp tables :
1 ? + // For GROUP BY
1 ? + // For DISTINCT
1 ? + // For aggregation functions aggregated in outer query
// when used with distinct
1 ? + // For ORDER BY
1 ? // buffer result
Up to 2 tmp tables are actually used , but it ' s hard to tell exact number
at this stage .
*/
uint aggr_tables= (group_list ? 1 : 0 ) +
(select_distinct ?
(tmp_table_param.using_outer_summary_function ? 2 : 1 ) : 0 ) +
(order ? 1 : 0 ) +
(select_options & (SELECT_BIG_RESULT | OPTION_BUFFER_RESULT) ? 1 : 0 ) ;
if (aggr_tables == 0 )
aggr_tables= 1 ; /* For group by pushdown */
if (select_lex->window_specs.elements)
aggr_tables++;
if (aggr_tables > 2 )
aggr_tables= 2 ;
full_join=0 ;
hash_join= FALSE ;
fix_semijoin_strategies_for_picked_join_order(this );
top_join_tab_count= get_number_of_tables_at_top_level(this );
#ifndef DBUG_OFF
dbug_join_tab_array_size= top_join_tab_count + aggr_tables;
#endif
/*
NOTE : The above computation of aggr_tables can produce wrong result because some
of the variables it uses may change their values after we leave this function .
Known examples :
- Dangerous : using_outer_summary_function = false at this point . Added
DBUG_ASSERT below to demonstrate . Can this cause us to allocate less
space than we would need ?
- Not dangerous : select_distinct can be true here but be assigned false
afterwards .
*/
aggr_tables= 2 ;
DBUG_ASSERT(!tmp_table_param.using_outer_summary_function);
if (!(join_tab= thd->alloc<JOIN_TAB>(top_join_tab_count + aggr_tables)))
DBUG_RETURN(TRUE );
if (inject_splitting_cond_for_all_tables_with_split_opt())
goto error;
JOIN_TAB_RANGE *root_range;
if (!(root_range= new (thd->mem_root) JOIN_TAB_RANGE))
goto error;
root_range->start= join_tab;
/* root_range->end will be set later */
join_tab_ranges.empty();
if (join_tab_ranges.push_back(root_range, thd->mem_root))
goto error;
for (j=join_tab, tablenr=0 ; tablenr < table_count ; tablenr++,j++)
{
TABLE *form;
POSITION *cur_pos= &best_positions[tablenr];
if (cur_pos->sj_strategy == SJ_OPT_MATERIALIZE ||
cur_pos->sj_strategy == SJ_OPT_MATERIALIZE_SCAN)
{
/*
Ok , we ' ve entered an SJ - Materialization semi - join ( note that this can ' t
be done recursively , semi - joins are not allowed to be nested ) .
1 . Put into main join order a JOIN_TAB that represents a lookup or scan
in the temptable .
*/
bzero((void *)j, sizeof (JOIN_TAB));
j->join= this ;
j->table= NULL; //temporary way to tell SJM tables from others.
j->ref.key = -1 ;
j->on_expr_ref= (Item**) &null_ptr;
/* The unique index is always in 'possible keys' in EXPLAIN */
j->keys= key_map(1 );
/*
2 . Proceed with processing SJM nest ' s join tabs , putting them into the
sub - order
*/
SJ_MATERIALIZATION_INFO *sjm= cur_pos->table->emb_sj_nest->sj_mat_info;
j->records_read= (sjm->is_sj_scan? sjm->rows : 1 .0 );
j->records_init= j->records_out= j->records_read;
j->records= (ha_rows) j->records_read;
j->cond_selectivity= 1 .0 ;
j->join_read_time= 0 .0 ; /* Not saved currently */
j->join_loops= 0 .0 ;
j->bush_children= JOIN_TAB_RANGE::create(thd, sjm->tables);
if (!j->bush_children)
goto error;
join_tab_ranges.push_back(j->bush_children, thd->mem_root);
sjm_nest_end= j->bush_children->end;
sjm_nest_root= j;
j= j->bush_children->start;
}
*j= *cur_pos->table;
j->bush_root_tab= sjm_nest_root;
form= table[tablenr]= j->table;
form->reginfo.join_tab=j;
DBUG_PRINT("info" ,("type: %d" , j->type));
if (j->type == JT_CONST)
goto loop_end; // Handled in make_join_stat..
j->loosescan_match_tab= NULL; //non-nulls will be set later
j->inside_loosescan_range= FALSE ;
j->ref.key = -1 ;
j->ref.key_parts=0 ;
if (j->type == JT_SYSTEM)
goto loop_end;
if (!(keyuse= cur_pos->key))
{
if (cur_pos->type == JT_NEXT) // Forced index
{
j->type= JT_NEXT;
j->index= cur_pos->forced_index;
}
else
j->type= JT_ALL;
if (cur_pos->use_join_buffer &&
tablenr != const_tables)
full_join= 1 ;
}
if ((j->type == JT_REF || j->type == JT_EQ_REF) &&
is_hash_join_key_no(j->ref.key))
hash_join= TRUE ;
j->range_rowid_filter_info=
cur_pos->range_rowid_filter_info;
/*
Save records_read in JOIN_TAB so that select_describe ( ) / etc don ' t have
to access join - > best_positions [ ] .
*/
j->records_init= cur_pos->records_init;
j->records_read= cur_pos->records_read;
j->records_out= cur_pos->records_out;
j->join_read_time= cur_pos->read_time;
j->join_loops= cur_pos->loops;
loop_end:
j->cond_selectivity= cur_pos->cond_selectivity;
DBUG_ASSERT(j->cond_selectivity <= 1 .0 );
crash_if_first_double_is_bigger(j->records_out,
j->records_init *
(j->range_rowid_filter_info ?
j->range_rowid_filter_info->selectivity :
1 .0 ));
map2table[j->table->tablenr]= j;
/* If we've reached the end of sjm nest, switch back to main sequence */
if (j + 1 == sjm_nest_end)
{
j->last_leaf_in_bush= TRUE ;
j= sjm_nest_root;
sjm_nest_root= NULL;
sjm_nest_end= NULL;
}
}
root_range->end= j;
used_tables= OUTER_REF_TABLE_BIT; // Outer row is already read
for (j=join_tab, tablenr=0 ; tablenr < table_count ; tablenr++,j++)
{
if (j->bush_children)
j= j->bush_children->start;
used_tables|= j->table->map;
if (j->type != JT_CONST && j->type != JT_SYSTEM)
{
if ((keyuse= best_positions[tablenr].key) &&
create_ref_for_key(this , j, keyuse, TRUE , used_tables))
goto error; // Something went wrong
}
if (j->last_leaf_in_bush)
j= j->bush_root_tab;
}
top_join_tab_count= (uint)(join_tab_ranges.head()->end -
join_tab_ranges.head()->start);
if (unlikely(thd->trace_started()))
print_final_join_order(this );
update_depend_map(this );
DBUG_RETURN(0 );
error:
/* join_tab was not correctly setup. Don't use it */
join_tab= 0 ;
DBUG_RETURN(1 );
}
/**
Create a descriptor of hash join key to access a given join table
@ param join join which the join table belongs to
@ param join_tab the join table to access
@ param org_keyuse beginning of the key uses to join this table
@ param used_tables bitmap of the previous tables
@ details
This function first finds key uses that can be utilized by the hash join
algorithm to join join_tab to the previous tables marked in the bitmap
used_tables . The tested key uses are taken from the array of all key uses
for ' join ' starting from the position org_keyuse . After all interesting key
uses have been found the function builds a descriptor of the corresponding
key that is used by the hash join algorithm would it be chosen to join
the table join_tab .
@ retval FALSE the descriptor for a hash join key is successfully created
@ retval TRUE otherwise
*/
static bool create_hj_key_for_table(JOIN *join, JOIN_TAB *join_tab,
KEYUSE *org_keyuse, table_map used_tables)
{
KEY *keyinfo;
KEY_PART_INFO *key_part_info;
KEYUSE *keyuse= org_keyuse;
uint key_parts= 0 ;
THD *thd= join->thd;
TABLE *table= join_tab->table;
bool first_keyuse= TRUE ;
DBUG_ENTER("create_hj_key_for_table" );
do
{
if (!(~used_tables & keyuse->used_tables) &&
join_tab->keyuse_is_valid_for_access_in_chosen_plan(join, keyuse) &&
are_tables_local(join_tab, keyuse->used_tables))
{
if (first_keyuse)
{
key_parts++;
}
else
{
KEYUSE *curr= org_keyuse;
for ( ; curr < keyuse; curr++)
{
if (curr->keypart == keyuse->keypart &&
!(~used_tables & curr->used_tables) &&
join_tab->keyuse_is_valid_for_access_in_chosen_plan(join,
curr) &&
are_tables_local(join_tab, curr->used_tables))
break ;
}
if (curr == keyuse)
key_parts++;
}
}
first_keyuse= FALSE ;
keyuse++;
} while (keyuse->table == table && keyuse->is_for_hash_join());
if (!key_parts)
DBUG_RETURN(TRUE );
/* This memory is allocated only once for the joined table join_tab */
if (!(keyinfo= thd->alloc<KEY>(1 )) ||
!(key_part_info = thd->alloc<KEY_PART_INFO>(key_parts)))
DBUG_RETURN(TRUE );
keyinfo->usable_key_parts= keyinfo->user_defined_key_parts = key_parts;
keyinfo->ext_key_parts= keyinfo->user_defined_key_parts;
keyinfo->key_part= key_part_info;
keyinfo->key_length=0 ;
keyinfo->algorithm= HA_KEY_ALG_UNDEF;
keyinfo->flags= HA_GENERATED_KEY;
keyinfo->is_statistics_from_stat_tables= FALSE ;
keyinfo->all_nulls_key_parts= 0 ;
keyinfo->stat_storage_length= 0 ;
keyinfo->name.str= "$hj" ;
keyinfo->name.length= 3 ;
keyinfo->rec_per_key= thd->calloc<ulong>(key_parts);
if (!keyinfo->rec_per_key)
DBUG_RETURN(TRUE );
keyinfo->key_part= key_part_info;
first_keyuse= TRUE ;
keyuse= org_keyuse;
do
{
if (!(~used_tables & keyuse->used_tables) &&
join_tab->keyuse_is_valid_for_access_in_chosen_plan(join, keyuse) &&
are_tables_local(join_tab, keyuse->used_tables))
{
bool add_key_part= TRUE ;
if (!first_keyuse)
{
for (KEYUSE *curr= org_keyuse; curr < keyuse; curr++)
{
if (curr->keypart == keyuse->keypart &&
!(~used_tables & curr->used_tables) &&
join_tab->keyuse_is_valid_for_access_in_chosen_plan(join,
curr) &&
are_tables_local(join_tab, curr->used_tables))
{
keyuse->keypart= NO_KEYPART;
add_key_part= FALSE ;
break ;
}
}
}
if (add_key_part)
{
Field *field= table->field[keyuse->keypart];
uint fieldnr= keyuse->keypart+1 ;
table->create_key_part_by_field(key_part_info, field, fieldnr);
keyinfo->key_length += key_part_info->store_length;
key_part_info++;
}
}
first_keyuse= FALSE ;
keyuse++;
} while (keyuse->table == table && keyuse->is_for_hash_join());
keyinfo->ext_key_parts= keyinfo->user_defined_key_parts;
keyinfo->ext_key_flags= keyinfo->flags;
keyinfo->ext_key_part_map= 0 ;
join_tab->hj_key= keyinfo;
DBUG_RETURN(FALSE );
}
/*
Check if a set of tables specified by used_tables can be accessed when
we ' re doing scan on join_tab jtab .
*/
static bool are_tables_local(JOIN_TAB *jtab, table_map used_tables)
{
if (jtab->bush_root_tab)
{
/*
jtab is inside execution join nest . We may not refer to outside tables ,
except the const tables .
*/
table_map local_tables= jtab->emb_sj_nest->nested_join->used_tables |
jtab->join->const_table_map |
OUTER_REF_TABLE_BIT;
return !MY_TEST(used_tables & ~local_tables);
}
/*
If we got here then jtab is at top level .
- all other tables at top level are accessible ,
- tables in join nests are accessible too , because all their columns that
are needed at top level will be unpacked when scanning the
materialization table .
*/
return TRUE ;
}
static bool create_ref_for_key(JOIN *join, JOIN_TAB *j,
KEYUSE *org_keyuse, bool allow_full_scan,
table_map used_tables)
{
uint keyparts, length, key;
TABLE *table;
KEY *keyinfo;
KEYUSE *keyuse= org_keyuse;
bool ftkey= (keyuse->keypart == FT_KEYPART);
THD *thd= join->thd;
DBUG_ENTER("create_ref_for_key" );
/* Use best key from find_best */
table= j->table;
key= keyuse->key;
if (!is_hash_join_key_no(key))
keyinfo= table->key_info+key;
else
{
if (create_hj_key_for_table(join, j, org_keyuse, used_tables))
DBUG_RETURN(TRUE );
keyinfo= j->hj_key;
}
if (ftkey)
{
Item_func_match *ifm=(Item_func_match *)keyuse->val;
length=0 ;
keyparts=1 ;
ifm->join_key=1 ;
}
else
{
keyparts=length=0 ;
uint found_part_ref_or_null= 0 ;
/*
Calculate length for the used key
Stop if there is a missing key part or when we find second key_part
with KEY_OPTIMIZE_REF_OR_NULL
*/
do
{
if (!(~used_tables & keyuse->used_tables) &&
(!keyuse->validity_ref || *keyuse->validity_ref) &&
j->keyuse_is_valid_for_access_in_chosen_plan(join, keyuse))
{
if (are_tables_local(j, keyuse->val->used_tables()))
{
if ((is_hash_join_key_no(key) && keyuse->keypart != NO_KEYPART) ||
(!is_hash_join_key_no(key) && keyparts == keyuse->keypart &&
!(found_part_ref_or_null & keyuse->optimize)))
{
length+= keyinfo->key_part[keyparts].store_length;
keyparts++;
found_part_ref_or_null|= keyuse->optimize & ~KEY_OPTIMIZE_EQ;
}
}
}
keyuse++;
} while (keyuse->table == table && keyuse->key == key);
if (!keyparts && allow_full_scan)
{
/* It's a LooseIndexScan strategy scanning whole index */
j->type= JT_ALL; // TODO: Check if this should be JT_NEXT
j->index= key;
DBUG_RETURN(FALSE );
}
DBUG_ASSERT(length > 0 );
DBUG_ASSERT(keyparts != 0 );
} /* not ftkey */
/* set up fieldref */
j->ref.key_parts= keyparts;
j->ref.key_length= length;
j->ref.key= (int ) key;
if (!(j->ref.key_buff= thd->calloc<uchar>(ALIGN_SIZE(length)*2 )) ||
!(j->ref.key_copy= thd->alloc<store_key*>(keyparts+1 )) ||
!(j->ref.items= thd->alloc<Item*>(keyparts)) ||
!(j->ref.cond_guards= thd->alloc<bool *>(keyparts)))
{
DBUG_RETURN(TRUE );
}
j->ref.key_buff2=j->ref.key_buff+ALIGN_SIZE(length);
j->ref.key_err=1 ;
j->ref.has_record= FALSE ;
j->ref.null_rejecting= 0 ;
j->ref.disable_cache= FALSE ;
j->ref.null_ref_part= NO_REF_PART;
j->ref.const_ref_part_map= 0 ;
j->ref.uses_splitting= FALSE ;
keyuse=org_keyuse;
store_key **ref_key= j->ref.key_copy;
uchar *key_buff=j->ref.key_buff, *null_ref_key= 0 ;
uint null_ref_part= NO_REF_PART;
bool keyuse_uses_no_tables= TRUE ;
uint not_null_keyparts= 0 ;
if (ftkey)
{
j->ref.items[0 ]=((Item_func*)(keyuse->val))->key_item();
/* Predicates pushed down into subquery can't be used FT access */
j->ref.cond_guards[0 ]= NULL;
if (keyuse->used_tables)
DBUG_RETURN(TRUE ); // not supported yet. SerG
j->type=JT_FT;
}
else
{
uint i;
for (i=0 ; i < keyparts ; keyuse++,i++)
{
while (((~used_tables) & keyuse->used_tables) ||
(keyuse->validity_ref && !(*keyuse->validity_ref)) ||
!j->keyuse_is_valid_for_access_in_chosen_plan(join, keyuse) ||
keyuse->keypart == NO_KEYPART ||
(keyuse->keypart !=
(is_hash_join_key_no(key) ?
keyinfo->key_part[i].field->field_index : i)) ||
!are_tables_local(j, keyuse->val->used_tables()))
keyuse++; /* Skip other parts */
uint maybe_null= MY_TEST(keyinfo->key_part[i].null_bit);
j->ref.items[i]=keyuse->val; // Save for cond removal
j->ref.cond_guards[i]= keyuse->cond_guard;
if (!keyuse->val->maybe_null() || keyuse->null_rejecting)
not_null_keyparts++;
/*
Set ref . null_rejecting to true only if we are going to inject a
" keyuse - > val IS NOT NULL " predicate .
*/
Item *real= (keyuse->val)->real_item();
if (keyuse->null_rejecting && (real->type() == Item::FIELD_ITEM) &&
((Item_field*)real)->field->maybe_null())
j->ref.null_rejecting|= (key_part_map)1 << i;
keyuse_uses_no_tables= keyuse_uses_no_tables && !keyuse->used_tables;
j->ref.uses_splitting |= (keyuse->validity_ref != NULL);
/*
We don ' t want to compute heavy expressions in EXPLAIN , an example would
select * from t1 where t1 . key = ( select thats very heavy ) ;
( select thats very heavy ) = > is a constant here
eg : ( select avg ( order_cost ) from orders ) = > constant but expensive
*/
if (!keyuse->val->used_tables() && !thd->lex->describe)
{ // Compare against constant
store_key_item tmp(thd,
keyinfo->key_part[i].field,
key_buff + maybe_null,
maybe_null ? key_buff : 0 ,
keyinfo->key_part[i].length,
keyuse->val,
FALSE );
if (unlikely(thd->is_error()))
DBUG_RETURN(TRUE );
tmp.copy(thd);
j->ref.const_ref_part_map |= key_part_map(1 ) << i ;
}
else
{
*ref_key++= get_store_key(thd,
keyuse,join->const_table_map,
&keyinfo->key_part[i],
key_buff, maybe_null);
if (!keyuse->val->used_tables())
j->ref.const_ref_part_map |= key_part_map(1 ) << i ;
}
/*
Remember if we are going to use REF_OR_NULL
But only if field _ really_ can be null i . e . we force JT_REF
instead of JT_REF_OR_NULL in case if field can ' t be null
*/
if ((keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL) && maybe_null)
{
null_ref_key= key_buff;
null_ref_part= i;
}
key_buff+= keyinfo->key_part[i].store_length;
}
} /* not ftkey */
*ref_key=0 ; // end_marker
if (j->type == JT_FT)
DBUG_RETURN(0 );
ulong key_flags= j->table->actual_key_flags(keyinfo);
if (j->type == JT_CONST)
j->table->const_table= 1 ;
else if (!((keyparts == keyinfo->user_defined_key_parts &&
(
(key_flags & (HA_NOSAME | HA_NULL_PART_KEY)) == HA_NOSAME ||
/* Unique key and all keyparts are NULL rejecting */
((key_flags & HA_NOSAME) && keyparts == not_null_keyparts)
)) ||
/* true only for extended keys */
(MY_TEST(key_flags & HA_EXT_NOSAME) &&
keyparts == keyinfo->ext_key_parts) ) ||
null_ref_key)
{
/* Must read with repeat */
j->type= null_ref_key ? JT_REF_OR_NULL : JT_REF;
j->ref.null_ref_key= null_ref_key;
j->ref.null_ref_part= null_ref_part;
}
else if (keyuse_uses_no_tables)
{
/*
This happen if we are using a constant expression in the ON part
of an LEFT JOIN .
SELECT * FROM a LEFT JOIN b ON b . key = 30
Here we should not mark the table as a ' const ' as a field may
have a ' normal ' value or a NULL value .
*/
j->type=JT_CONST;
}
else
j->type=JT_EQ_REF;
if (j->type == JT_EQ_REF)
j->read_record.unlock_row= join_read_key_unlock_row;
else if (j->type == JT_CONST)
j->read_record.unlock_row= join_const_unlock_row;
else
j->read_record.unlock_row= rr_unlock_row;
DBUG_RETURN(0 );
}
static store_key *
get_store_key(THD *thd, KEYUSE *keyuse, table_map used_tables,
KEY_PART_INFO *key_part, uchar *key_buff, uint maybe_null)
{
if (!((~used_tables) & keyuse->used_tables)) // if const item
{
return new store_key_const_item(thd,
key_part->field,
key_buff + maybe_null,
maybe_null ? key_buff : 0 ,
key_part->length,
keyuse->val);
}
else if (keyuse->val->type() == Item::FIELD_ITEM ||
(keyuse->val->type() == Item::REF_ITEM &&
((((Item_ref*)keyuse->val)->ref_type() == Item_ref::OUTER_REF &&
(*(Item_ref**)((Item_ref*)keyuse->val)->ref)->ref_type() ==
Item_ref::DIRECT_REF) ||
((Item_ref*)keyuse->val)->ref_type() == Item_ref::VIEW_REF) &&
keyuse->val->real_item()->type() == Item::FIELD_ITEM))
return new store_key_field(thd,
key_part->field,
key_buff + maybe_null,
maybe_null ? key_buff : 0 ,
key_part->length,
((Item_field*) keyuse->val->real_item())->field,
keyuse->val->real_item()->full_name());
return new store_key_item(thd,
key_part->field,
key_buff + maybe_null,
maybe_null ? key_buff : 0 ,
key_part->length,
keyuse->val, FALSE );
}
inline void add_cond_and_fix(THD *thd, Item **e1, Item *e2)
{
if (*e1)
{
if (!e2)
return ;
Item *res;
if ((res= new (thd->mem_root) Item_cond_and(thd, *e1, e2)))
{
res->fix_fields(thd, 0 );
res->update_used_tables();
*e1= res;
}
}
else
*e1= e2;
}
/**
Add to join_tab - > select_cond [ i ] " table . field IS NOT NULL " conditions
we ' ve inferred from ref / eq_ref access performed .
This function is a part of " Early NULL - values filtering for ref access "
optimization .
Example of this optimization :
For query SELECT * FROM t1 , t2 WHERE t2 . key = t1 . field @ n
and plan " any - access ( t1 ) , ref ( t2 . key = t1 . field ) " @ n
add " t1 . field IS NOT NULL " to t1 ' s table condition . @ n
Description of the optimization :
We look through equalities chosen to perform ref / eq_ref access ,
pick equalities that have form " tbl . part_of_key = othertbl . field "
( where othertbl is a non - const table and othertbl . field may be NULL )
and add them to conditions on corresponding tables ( othertbl in this
example ) .
Exception from that is the case when referred_tab - > join ! = join .
I . e . don ' t add NOT NULL constraints from any embedded subquery .
Consider this query :
@ code
SELECT A . f2 FROM t1 LEFT JOIN t2 A ON A . f2 = f1
WHERE A . f3 = ( SELECT MIN ( f3 ) FROM t2 C WHERE A . f4 = C . f4 ) OR A . f3 IS NULL ;
@ endocde
Here condition A . f3 IS NOT NULL is going to be added to the WHERE
condition of the embedding query .
Another example :
SELECT * FROM t10 , t11 WHERE ( t10 . a < 10 OR t10 . a IS NULL )
AND t11 . b < = > t10 . b AND ( t11 . a = ( SELECT MAX ( a ) FROM t12
WHERE t12 . b = t10 . a ) ) ;
Here condition t10 . a IS NOT NULL is going to be added .
In both cases addition of NOT NULL condition will erroneously reject
some rows of the result set .
referred_tab - > join ! = join constraint would disallow such additions .
This optimization doesn ' t affect the choices that ref , range , or join
optimizer make . This was intentional because this was added after 4 . 1
was GA .
Implementation overview
1 . update_ref_and_keys ( ) accumulates info about null - rejecting
predicates in KEY_FIELD : : null_rejecting
1 . 1 add_key_part saves these to KEYUSE .
2 . create_ref_for_key copies them to TABLE_REF .
3 . add_not_null_conds adds " x IS NOT NULL " to join_tab - > select_cond of
appropriate JOIN_TAB members .
*/
static void add_not_null_conds(JOIN *join)
{
JOIN_TAB *tab;
DBUG_ENTER("add_not_null_conds" );
for (tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES);
tab;
tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS))
{
if (tab->type == JT_REF || tab->type == JT_EQ_REF ||
tab->type == JT_REF_OR_NULL)
{
for (uint keypart= 0 ; keypart < tab->ref.key_parts; keypart++)
{
if (tab->ref.null_rejecting & ((key_part_map)1 << keypart))
{
Item *item= tab->ref.items[keypart];
Item *notnull;
Item *real= item->real_item();
if (real->can_eval_in_optimize() && real->type() != Item::FIELD_ITEM)
{
/*
It could be constant instead of field after constant
propagation .
*/
continue ;
}
DBUG_ASSERT(real->type() == Item::FIELD_ITEM);
Item_field *not_null_item= (Item_field*)real;
JOIN_TAB *referred_tab= not_null_item->field->table->reginfo.join_tab;
/*
For UPDATE queries such as :
UPDATE t1 SET t1 . f2 = ( SELECT MAX ( t2 . f4 ) FROM t2 WHERE t2 . f3 = t1 . f1 ) ;
not_null_item is the t1 . f1 , but it ' s referred_tab is 0 .
*/
if (!(notnull= new (join->thd->mem_root)
Item_func_isnotnull(join->thd, item)))
DBUG_VOID_RETURN;
/*
We need to do full fix_fields ( ) call here in order to have correct
notnull - > const_item ( ) . This is needed e . g . by test_quick_select
when it is called from make_join_select after this function is
called .
*/
if (notnull->fix_fields(join->thd, ¬null))
DBUG_VOID_RETURN;
DBUG_EXECUTE("where" ,print_where(notnull,
(referred_tab ?
referred_tab->table->alias.c_ptr() :
"outer_ref_cond" ),
QT_ORDINARY););
if (!tab->first_inner)
{
COND *new_cond= (referred_tab && referred_tab->join == join) ?
referred_tab->select_cond :
join->outer_ref_cond;
add_cond_and_fix(join->thd, &new_cond, notnull);
if (referred_tab && referred_tab->join == join)
referred_tab->set_select_cond(new_cond, __LINE__);
else
join->outer_ref_cond= new_cond;
}
else
add_cond_and_fix(join->thd, tab->first_inner->on_expr_ref, notnull);
}
}
}
}
DBUG_VOID_RETURN;
}
/**
Build a predicate guarded by match variables for embedding outer joins .
The function recursively adds guards for predicate cond
assending from tab to the first inner table next embedding
nested outer join and so on until it reaches root_tab
( root_tab can be 0 ) .
In other words :
add_found_match_trig_cond ( tab - > first_inner_tab , y , 0 ) is the way one should
wrap parts of WHERE . The idea is that the part of WHERE should be only
evaluated after we ' ve finished figuring out whether outer joins .
^ ^ ^ is the above correct ?
@ param tab the first inner table for most nested outer join
@ param cond the predicate to be guarded ( must be set )
@ param root_tab the first inner table to stop
@ return
- pointer to the guarded predicate , if success
- 0 , otherwise
*/
static COND*
add_found_match_trig_cond(THD *thd, JOIN_TAB *tab, COND *cond,
JOIN_TAB *root_tab)
{
COND *tmp;
DBUG_ASSERT(cond != 0 );
if (tab == root_tab)
return cond;
if ((tmp= add_found_match_trig_cond(thd, tab->first_upper, cond, root_tab)))
tmp= new (thd->mem_root) Item_func_trig_cond(thd, tmp, &tab->found);
if (tmp)
{
tmp->quick_fix_field();
tmp->update_used_tables();
}
return tmp;
}
bool TABLE_LIST::is_active_sjm()
{
return sj_mat_info && sj_mat_info->is_used;
}
/**
Fill in outer join related info for the execution plan structure .
For each outer join operation left after simplification of the
original query the function set up the following pointers in the linear
structure join - > join_tab representing the selected execution plan .
The first inner table t0 for the operation is set to refer to the last
inner table tk through the field t0 - > last_inner .
Any inner table ti for the operation are set to refer to the first
inner table ti - > first_inner .
The first inner table t0 for the operation is set to refer to the
first inner table of the embedding outer join operation , if there is any ,
through the field t0 - > first_upper .
The on expression for the outer join operation is attached to the
corresponding first inner table through the field t0 - > on_expr_ref .
Here ti are structures of the JOIN_TAB type .
In other words , for each join tab , set
- first_inner
- last_inner
- first_upper
- on_expr_ref , cond_equal
EXAMPLE . For the query :
@ code
SELECT * FROM t1
LEFT JOIN
( t2 , t3 LEFT JOIN t4 ON t3 . a = t4 . a )
ON ( t1 . a = t2 . a AND t1 . b = t3 . b )
WHERE t1 . c > 5 ,
@ endcode
given the execution plan with the table order t1 , t2 , t3 , t4
is selected , the following references will be set ;
t4 - > last_inner = [ t4 ] , t4 - > first_inner = [ t4 ] , t4 - > first_upper = [ t2 ]
t2 - > last_inner = [ t4 ] , t2 - > first_inner = t3 - > first_inner = [ t2 ] ,
on expression ( t1 . a = t2 . a AND t1 . b = t3 . b ) will be attached to
* t2 - > on_expr_ref , while t3 . a = t4 . a will be attached to * t4 - > on_expr_ref .
@ param join reference to the info fully describing the query
@ note
The function assumes that the simplification procedure has been
already applied to the join query ( see simplify_joins ) .
This function can be called only after the execution plan
has been chosen .
*/
static bool
make_outerjoin_info(JOIN *join)
{
DBUG_ENTER("make_outerjoin_info" );
/*
Create temp . tables for merged SJ - Materialization nests . We need to do
this now , because further code relies on tab - > table and
tab - > table - > pos_in_table_list being set .
*/
JOIN_TAB *tab;
for (tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES);
tab;
tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS))
{
if (tab->bush_children)
{
if (setup_sj_materialization_part1(tab))
DBUG_RETURN(TRUE );
tab->table->reginfo.join_tab= tab;
}
}
for (tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES);
tab;
tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS))
{
TABLE *table= tab->table;
TABLE_LIST *tbl= table->pos_in_table_list;
TABLE_LIST *embedding= tbl->embedding;
if (tbl->outer_join & (JOIN_TYPE_LEFT | JOIN_TYPE_RIGHT))
{
/*
Table tab is the only one inner table for outer join .
( Like table t4 for the table reference t3 LEFT JOIN t4 ON t3 . a = t4 . a
is in the query above . )
*/
tab->last_inner= tab->first_inner= tab;
tab->on_expr_ref= &tbl->on_expr;
tab->cond_equal= tbl->cond_equal;
if (embedding && !embedding->is_active_sjm())
tab->first_upper= embedding->nested_join->first_nested;
}
else if (!embedding)
tab->table->reginfo.not_exists_optimize= 0 ;
for ( ; embedding ; embedding= embedding->embedding)
{
if (embedding->is_active_sjm())
{
/*
We ' re trying to walk out of an SJ - Materialization nest .
Don ' t do this .
*/
break ;
}
/* Ignore sj-nests: */
if (!(embedding->on_expr && embedding->outer_join))
{
tab->table->reginfo.not_exists_optimize= 0 ;
continue ;
}
NESTED_JOIN *nested_join= embedding->nested_join;
if (!nested_join->counter)
{
/*
Table tab is the first inner table for nested_join .
Save reference to it in the nested join structure .
*/
nested_join->first_nested= tab;
tab->on_expr_ref= &embedding->on_expr;
tab->cond_equal= tbl->cond_equal;
if (embedding->embedding)
tab->first_upper= embedding->embedding->nested_join->first_nested;
}
if (!tab->first_inner)
tab->first_inner= nested_join->first_nested;
if (++nested_join->counter < nested_join->n_tables)
break ;
/* Table tab is the last inner table for nested join. */
nested_join->first_nested->last_inner= tab;
}
}
DBUG_RETURN(FALSE );
}
/*
@ brief
Build a temporary join prefix condition for JOIN_TABs up to the last tab
@ param ret OUT the condition is returned here
@ return
false OK
true Out of memory
@ detail
Walk through the join prefix ( from the first table to the last_tab ) and
build a condition :
join_tab_1_cond AND join_tab_2_cond AND . . . AND last_tab_conds
The condition is only intended to be used by the range optimizer , so :
- it is not normalized ( can have Item_cond_and inside another
Item_cond_and )
- it does not include join - > exec_const_cond and other similar conditions .
*/
bool build_tmp_join_prefix_cond(JOIN *join, JOIN_TAB *last_tab, Item **ret)
{
THD *const thd= join->thd;
Item_cond_and *all_conds= NULL;
Item *res= NULL;
// Pick the ON-expression. Use the same logic as in get_sargable_cond():
if (last_tab->on_expr_ref)
res= *last_tab->on_expr_ref;
else if (last_tab->table->pos_in_table_list &&
last_tab->table->pos_in_table_list->embedding &&
!last_tab->table->pos_in_table_list->embedding->sj_on_expr)
{
res= last_tab->table->pos_in_table_list->embedding->on_expr;
}
for (JOIN_TAB *tab= first_depth_first_tab(join);
tab;
tab= next_depth_first_tab(join, tab))
{
if (tab->select_cond)
{
if (!res)
res= tab->select_cond;
else
{
if (!all_conds)
{
if (!(all_conds= new (thd->mem_root)Item_cond_and(thd, res,
tab->select_cond)))
return true ;
res= all_conds;
}
else
all_conds->add(tab->select_cond, thd->mem_root);
}
}
if (tab == last_tab)
break ;
}
*ret= all_conds? all_conds: res;
return false ;
}
static bool
make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
{
THD *thd= join->thd;
DBUG_ENTER("make_join_select" );
if (select)
{
Json_writer_object trace_wrapper(thd);
Json_writer_object trace_conditions(thd, "attaching_conditions_to_tables" );
Json_writer_array trace_attached_comp(thd,
"attached_conditions_computation" );
add_not_null_conds(join);
table_map used_tables;
/*
Step # 1 : Extract constant condition
- Extract and check the constant part of the WHERE
- Extract constant parts of ON expressions from outer
joins and attach them appropriately .
*/
if (cond) /* Because of QUICK_GROUP_MIN_MAX_SELECT */
{ /* there may be a select without a cond. */
if (join->table_count > 1 )
cond->update_used_tables(); // Tablenr may have changed
/*
Extract expressions that depend on constant tables
1 . Const part of the join ' s WHERE clause can be checked immediately
and if it is not satisfied then the join has empty result
2 . Constant parts of outer joins ' ON expressions must be attached
there inside the triggers .
*/
{ // Check const tables
Item* const_cond= NULL;
const_cond= make_cond_for_table(thd, cond,
join->const_table_map,
(table_map) 0 , -1 , FALSE , FALSE );
if (!const_cond && thd->is_error())
DBUG_RETURN(1 );
/* Add conditions added by add_not_null_conds(). */
for (uint i= 0 ; i < join->const_tables ; i++)
add_cond_and_fix(thd, &const_cond,
join->join_tab[i].select_cond);
DBUG_EXECUTE("where" ,print_where(const_cond,"constants" ,
QT_ORDINARY););
if (const_cond)
{
Json_writer_object trace_const_cond(thd);
trace_const_cond.add("condition_on_constant_tables" , const_cond);
if (const_cond->can_eval_in_optimize())
{
bool const_cond_result;
{
Json_writer_array a(thd, "computing_condition" );
const_cond_result= const_cond->val_bool() != 0 ;
}
if (!const_cond_result)
{
DBUG_PRINT("info" ,("Found impossible WHERE condition" ));
if (unlikely(trace_const_cond.trace_started()))
trace_const_cond.
add("evalualted" , "true" ).
add("found" , "impossible where" );
join->exec_const_cond= NULL;
DBUG_RETURN(1 );
}
}
else
{
trace_const_cond.add("evaluated" , "false" )
.add("cause" , "expensive cond" );
}
join->exec_const_cond= const_cond;
}
if (join->table_count != join->const_tables)
{
COND *outer_ref_cond= make_cond_for_table(thd, cond,
join->const_table_map |
OUTER_REF_TABLE_BIT,
OUTER_REF_TABLE_BIT,
-1 , FALSE , FALSE );
if (outer_ref_cond)
{
add_cond_and_fix(thd, &outer_ref_cond, join->outer_ref_cond);
join->outer_ref_cond= outer_ref_cond;
Json_writer_object trace(thd);
trace.add("outer_ref_cond" , outer_ref_cond);
}
else if (thd->is_error())
DBUG_RETURN(1 );
}
else
{
COND *pseudo_bits_cond=
make_cond_for_table(thd, cond,
join->const_table_map |
PSEUDO_TABLE_BITS,
PSEUDO_TABLE_BITS,
-1 , FALSE , FALSE );
if (pseudo_bits_cond)
{
add_cond_and_fix(thd, &pseudo_bits_cond,
join->pseudo_bits_cond);
join->pseudo_bits_cond= pseudo_bits_cond;
Json_writer_object trace(thd);
trace.add("pseudo_bits_cond" , pseudo_bits_cond);
}
else if (thd->is_error())
DBUG_RETURN(1 );
}
}
}
/*
Step # 2 : Extract WHERE / ON parts
*/
uint i;
for (i= join->top_join_tab_count - 1 ; i >= join->const_tables; i--)
{
if (!join->join_tab[i].bush_children)
break ;
}
uint last_top_base_tab_idx= i;
table_map save_used_tables= 0 ;
used_tables=((select->const_tables=join->const_table_map) |
OUTER_REF_TABLE_BIT | RAND_TABLE_BIT);
JOIN_TAB *tab;
table_map current_map;
i= join->const_tables;
for (tab= first_depth_first_tab(join); tab;
tab= next_depth_first_tab(join, tab))
{
bool is_hj;
/*
first_inner is the X in queries like :
SELECT * FROM t1 LEFT OUTER JOIN ( t2 JOIN t3 ) ON X
*/
JOIN_TAB *first_inner_tab= tab->first_inner;
COND *tmp;
if (!tab->bush_children)
current_map= tab->table->map;
else
current_map= tab->bush_children->start->emb_sj_nest->sj_inner_tables;
/*
Tables that are within SJ - Materialization nests cannot have their
conditions referring to preceding non - const tables .
- If we ' re looking at the first SJM table , reset used_tables
to refer to only allowed tables
*/
if (tab->emb_sj_nest && tab->emb_sj_nest->sj_mat_info &&
tab->emb_sj_nest->sj_mat_info->is_used &&
!(used_tables & tab->emb_sj_nest->sj_inner_tables))
{
save_used_tables= used_tables;
used_tables= join->const_table_map | OUTER_REF_TABLE_BIT |
RAND_TABLE_BIT;
}
used_tables|=current_map;
/*
Change from using ref access to using quick select on the same index
if the quick select uses more key parts .
There are two cases .
A . ref access is ref ( const ) . quick select was also constructed using
equality restrictions that ref used , and so it will scan a subset of
rows that ref access scans .
Example : suppose the index is INDEX ( kp1 , kp2 ) and the WHERE has :
kp1 = ' foo ' and kp2 < = 10
here , ref access will use kp1 = ' foo ' and quick select will use
( foo ) < = ( kp1 , kp2 ) < = ( foo , 10 )
B . ref access is not constant . In this case , quick select was
constructed from some other restriction and in general will scan
totally different set of rows ( it maybe larger or smaller ) .
Example : for INDEX ( kp1 , kp2 ) and the WHERE :
kp1 < = ' foo ' and kp1 = prev_table . col and kp2 < = 10
the ref access will use kp1 = prev_table . col , while quick select will
use ( - inf ) < ( kp1 , kp2 ) < = ( ' foo ' , 10 ) .
Because of the above , we perform the rewrite ONLY when ref is
ref ( const ) .
*/
if (tab->type == JT_REF && tab->quick &&
(((uint) tab->ref.key == tab->quick->index &&
tab->ref.key_length < tab->quick->max_used_key_length) ||
(!is_hash_join_key_no(tab->ref.key) &&
tab->table->intersect_keys.is_set(tab->ref.key))) &&
tab->ref.const_ref_part_map == // (ref-is-const)
make_prev_keypart_map(tab->ref.key_parts)) // (ref-is-const)
{
/* Range uses longer key; Use this instead of ref on key */
if (unlikely(thd->trace_started()))
{
Json_writer_object ref_to_range(thd);
ref_to_range.
add("ref_to_range" , true ).
add("cause" , "range uses longer key" );
}
tab->type= JT_RANGE;
tab->use_quick=1 ;
if (is_index_merge(tab->quick->get_type()))
tab->clear_range_rowid_filter();
tab->ref.key= -1 ;
tab->ref.key_parts=0 ; // Don't use ref key.
join->best_positions[i].records_read= rows2double(tab->quick->records);
/*
We will use join cache here : prevent sorting of the first
table only and sort at the end .
*/
if (i != join->const_tables &&
join->table_count > join->const_tables + 1 &&
join->best_positions[i].use_join_buffer)
join->full_join= 1 ;
}
tmp= NULL;
if (cond)
{
if (tab->bush_children)
{
// Reached the materialization tab
tmp= make_cond_after_sjm(thd, cond, cond, save_used_tables,
used_tables, /*inside_or_clause=*/FALSE);
used_tables= save_used_tables | used_tables;
save_used_tables= 0 ;
}
else
{
tmp= make_cond_for_table(thd, cond, used_tables, current_map, i,
FALSE , FALSE );
if (!tmp && thd->is_error())
DBUG_RETURN(1 );
if (tab == join->join_tab + last_top_base_tab_idx)
{
/*
This pushes conjunctive conditions of WHERE condition such that :
- their used_tables ( ) contain RAND_TABLE_BIT
- the conditions does not refer to any fields
( such like rand ( ) > 0 . 5 )
*/
table_map rand_table_bit= (table_map) RAND_TABLE_BIT;
COND *rand_cond= make_cond_for_table(thd, cond, used_tables,
rand_table_bit, -1 ,
FALSE , FALSE );
if (rand_cond)
add_cond_and_fix(thd, &tmp, rand_cond);
else if (thd->is_error())
DBUG_RETURN(1 );
}
}
/* Add conditions added by add_not_null_conds(). */
if (tab->select_cond)
add_cond_and_fix(thd, &tmp, tab->select_cond);
}
uint max_jcl= join->max_allowed_join_cache_level;
bool is_hash_allowed= join->allowed_join_cache_types &
JOIN_CACHE_HASHED_BIT;
bool is_bnlh_enabled= ((max_jcl == 3 || max_jcl == 4 ) &&
is_hash_allowed) ||
hint_table_state(thd, tab->table, BNL_HINT_ENUM, false );
bool is_bkah_enabled= (max_jcl > 4 && is_hash_allowed) ||
hint_table_state(thd, tab->table, BKA_HINT_ENUM, false );
is_hj= (tab->type == JT_REF || tab->type == JT_EQ_REF) &&
(is_bnlh_enabled ||
(is_bkah_enabled && is_hash_join_key_no(tab->ref.key))) &&
(!tab->emb_sj_nest ||
join->allowed_semijoin_with_cache) &&
(!(tab->table->map & join->outer_join) ||
join->allowed_outer_join_with_cache);
if (cond && !tmp && tab->quick)
{ // Outer join
if ((tab->type != JT_ALL && tab->type != JT_RANGE) && !is_hj)
{
/*
Don ' t use the quick method
We come here in the case where we have ' key = constant ' and
the test is removed by make_cond_for_table ( )
*/
delete tab->quick;
tab->quick= 0 ;
}
else
{
/*
Hack to handle the case where we only refer to a table
in the ON part of an OUTER JOIN . In this case we want the code
below to check if we should use ' quick ' instead .
*/
DBUG_PRINT("info" , ("Item_int" ));
tmp= (Item*) Item_true;
}
}
if (tmp || !cond || tab->type == JT_REF || tab->type == JT_REF_OR_NULL ||
tab->type == JT_EQ_REF || first_inner_tab)
{
DBUG_EXECUTE("where" ,print_where(tmp,
tab->table ?
tab->table->alias.c_ptr() :"sjm-nest" ,
QT_ORDINARY););
SQL_SELECT *sel= tab->select= ((SQL_SELECT*)
thd->memdup((uchar*) select,
sizeof (*select)));
if (!sel)
DBUG_RETURN(1 ); // End of memory
/*
If tab is an inner table of an outer join operation ,
add a match guard to the pushed down predicate .
The guard will turn the predicate on only after
the first match for outer tables is encountered .
*/
if (cond && tmp)
{
/*
Because of QUICK_GROUP_MIN_MAX_SELECT there may be a select without
a cond , so neutralize the hack above .
*/
COND *tmp_cond;
if (!(tmp_cond= add_found_match_trig_cond(thd, first_inner_tab, tmp,
0 )))
DBUG_RETURN(1 );
sel->cond= tmp_cond;
tab->set_select_cond(tmp_cond, __LINE__);
/* Push condition to storage engine if this is enabled
and the condition is not guarded */
if (tab->table)
{
tab->table->file->pushed_cond= NULL;
if ((tab->table->file->ha_table_flags() &
HA_CAN_TABLE_CONDITION_PUSHDOWN) &&
!first_inner_tab)
{
Json_writer_object wrap(thd);
Json_writer_object trace_cp(thd, "table_condition_pushdown" );
trace_cp.add_table_name(tab->table);
COND *push_cond=
make_cond_for_table(thd, tmp_cond, current_map, current_map,
-1 , FALSE , FALSE );
if (push_cond)
{
trace_cp.add("push_cond" , push_cond);
/* Push condition to handler */
if (!tab->table->file->cond_push(push_cond))
tab->table->file->pushed_cond= push_cond;
}
else if (thd->is_error())
DBUG_RETURN(1 );
}
}
}
else
{
sel->cond= NULL;
tab->set_select_cond(NULL, __LINE__);
}
sel->head=tab->table;
DBUG_EXECUTE("where" ,
print_where(tmp,
tab->table ? tab->table->alias.c_ptr() :
"(sjm-nest)" ,
QT_ORDINARY););
if (tab->quick)
{
/* Use quick key read if it's a constant and it's not used
with key reading */
if ((tab->needed_reg.is_clear_all() && tab->type != JT_EQ_REF &&
tab->type != JT_FT &&
((tab->type != JT_CONST && tab->type != JT_REF) ||
(uint) tab->ref.key == tab->quick->index)) || is_hj)
{
DBUG_ASSERT(tab->quick->is_valid());
sel->quick=tab->quick; // Use value from get_quick_...
sel->quick_keys.clear_all();
sel->needed_reg.clear_all();
if (is_hj && tab->rowid_filter)
tab->clear_range_rowid_filter();
}
else
{
delete tab->quick;
}
tab->quick=0 ;
}
uint ref_key= (sel->head ?
(uint) sel->head->reginfo.join_tab->ref.key+1 :
0 );
if (i == join->const_tables && ref_key)
{
if (!tab->const_keys.is_clear_all() &&
tab->table->reginfo.impossible_range)
DBUG_RETURN(1 );
}
else if ((tab->type == JT_ALL || tab->type == JT_NEXT))
{
if (!tab->const_keys.is_clear_all() &&
tab->table->reginfo.impossible_range)
DBUG_RETURN(1 ); // Impossible range
/*
We plan to scan all rows either with table or index scan
Check again if we should use an index .
There are two cases :
1 ) There could be an index usage the refers to a previous
table that we didn ' t consider before , but could be consider
now as a " last resort " . For example
SELECT * from t1 , t2 where t1 . a between t2 . a and t2 . b ;
2 ) If the current table is the first non const table
and there is a limit it still possibly beneficial
to use the index even if the index range is big as
we can stop when we ' ve found limit rows .
( 1 ) - Don ' t switch the used index if we are using semi - join
LooseScan on this table . Using different index will not
produce the desired ordering and de - duplication .
*/
if (!tab->table->is_filled_at_execution() &&
!tab->loosescan_match_tab && // (1)
((cond && (!tab->keys.is_subset(tab->const_keys) &&
i > join->const_tables)) ||
(!tab->const_keys.is_clear_all() && i == join->const_tables &&
join->unit->lim.get_select_limit() <
join->best_positions[i].records_read &&
!(join->select_options & OPTION_FOUND_ROWS))))
{
/* Join with outer join condition */
COND *orig_cond=sel->cond;
if (build_tmp_join_prefix_cond(join, tab, &sel->cond))
return true ;
/*
To be removed in 11 . 0 + :
Caution : we can reach this point with quick = NULL . Below , we ' ll
use tab - > keys and not tab - > const_keys like
get_quick_record_count ( ) did . If we have constructed a
group - min - max quick select , make sure we ' re able to construct it
again
*/
if (sel->quick && sel->quick->get_type() ==
QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
{
tab->keys.set_bit(sel->quick->index);
}
/*
We can ' t call sel - > cond - > fix_fields ,
as it will break tab - > on_expr if it ' s AND condition
( fix_fields currently removes extra AND / OR levels ) .
Yet attributes of the just built condition are not needed .
Thus we call sel - > cond - > quick_fix_field for safety .
*/
if (sel->cond && !sel->cond->fixed())
sel->cond->quick_fix_field();
quick_select_return res;
if ((res= sel->test_quick_select(thd, tab->keys,
((used_tables & ~ current_map) |
OUTER_REF_TABLE_BIT),
(join->select_options &
OPTION_FOUND_ROWS ?
HA_POS_ERROR :
join->unit->lim.get_select_limit()),
0 ,
FALSE , FALSE , FALSE ,
Item_func::BITMAP_ALL)) ==
SQL_SELECT::IMPOSSIBLE_RANGE)
{
/*
Before reporting " Impossible WHERE " for the whole query
we have to check isn ' t it only " impossible ON " instead
*/
sel->cond=orig_cond;
if (!*tab->on_expr_ref ||
(res= sel->test_quick_select(thd, tab->keys,
used_tables & ~ current_map,
(join->select_options &
OPTION_FOUND_ROWS ?
HA_POS_ERROR :
join->unit->lim.get_select_limit()),
0 , FALSE , FALSE , FALSE ,
Item_func::BITMAP_NONE)) ==
SQL_SELECT::IMPOSSIBLE_RANGE)
DBUG_RETURN(1 ); // Impossible WHERE
}
else
sel->cond=orig_cond;
if (res == SQL_SELECT::ERROR)
DBUG_RETURN(1 ); /* Some error in one of test_quick_select calls */
/* Fix for EXPLAIN */
if (sel->quick)
{
join->best_positions[i].records_read=
(double ) sel->quick->records;
set_if_smaller(join->best_positions[i].records_out,
rows2double(sel->head->opt_range_condition_rows));
}
else
{
/*
sel - > head - > opt_range_condition_rows may have been
updated to a smaller number than before by a call to
test_quick_select . This can happen even if the range
optimizer decided to not use the range ( sel - > quick was
not set ) .
*/
set_if_smaller(join->best_positions[i].records_out,
rows2double(sel->head->opt_range_condition_rows));
}
}
else
{
sel->needed_reg=tab->needed_reg;
}
sel->quick_keys= tab->table->opt_range_keys;
if (!sel->quick_keys.is_subset(tab->checked_keys) ||
!sel->needed_reg.is_subset(tab->checked_keys))
{
handler *file= tab->table->file;
/*
" Range checked for each record " is a " last resort " access method
that should only be used when the other option is a cross - product
join .
We use the following condition ( it ' s approximate ) :
1 . There are potential keys for ( sel - > needed_reg )
2 . There were no possible ways to construct a quick select , or
the quick select would be more expensive than the full table
scan .
*/
tab->use_quick= (!sel->needed_reg.is_clear_all() &&
(sel->quick_keys.is_clear_all() ||
(sel->quick &&
sel->quick->read_time >
file->cost(file->ha_scan_and_compare_time(tab->table->file-> stats.records))))) ?
2 : 1 ;
sel->read_tables= used_tables & ~current_map;
sel->quick_keys.clear_all();
}
if (i != join->const_tables && tab->use_quick != 2 &&
!tab->first_inner)
{ /* Read with cache */
/*
TODO : the execution also gets here when we will not be using
join buffer . Review these cases and perhaps , remove this call .
( The final decision whether to use join buffer is made in
check_join_cache_usage , so we should only call make_scan_filter ( )
there , too ) .
*/
if (tab->make_scan_filter())
DBUG_RETURN(1 );
}
}
}
/*
Push down conditions from all ON expressions .
Each of these conditions are guarded by a variable
that turns if off just before null complemented row for
outer joins is formed . Thus , the condition from an
' on expression ' are guaranteed not to be checked for
the null complemented row .
*/
/*
First push down constant conditions from ON expressions .
- Each pushed - down condition is wrapped into trigger which is
enabled only for non - NULL - complemented record
- The condition is attached to the first_inner_table .
With regards to join nests :
- if we start at top level , don ' t walk into nests
- if we start inside a nest , stay within that nest .
*/
JOIN_TAB *start_from= tab->bush_root_tab?
tab->bush_root_tab->bush_children->start :
join->join_tab + join->const_tables;
JOIN_TAB *end_with= tab->bush_root_tab?
tab->bush_root_tab->bush_children->end :
join->join_tab + join->top_join_tab_count;
for (JOIN_TAB *join_tab= start_from;
join_tab != end_with;
join_tab++)
{
if (*join_tab->on_expr_ref)
{
JOIN_TAB *cond_tab= join_tab->first_inner;
COND *tmp_cond= make_cond_for_table(thd, *join_tab->on_expr_ref,
join->const_table_map,
(table_map) 0 , -1 , FALSE , FALSE );
if (!tmp_cond)
{
if (!thd->is_error())
continue ;
DBUG_RETURN(1 );
}
tmp_cond= new (thd->mem_root) Item_func_trig_cond(thd, tmp_cond,
&cond_tab->not_null_compl);
if (!tmp_cond)
DBUG_RETURN(1 );
tmp_cond->quick_fix_field();
cond_tab->select_cond= !cond_tab->select_cond ? tmp_cond :
new (thd->mem_root) Item_cond_and(thd, cond_tab->select_cond,
tmp_cond);
if (!cond_tab->select_cond)
DBUG_RETURN(1 );
cond_tab->select_cond->quick_fix_field();
cond_tab->select_cond->update_used_tables();
if (cond_tab->select)
cond_tab->select->cond= cond_tab->select_cond;
}
}
/* Push down non-constant conditions from ON expressions */
JOIN_TAB *last_tab= tab;
/*
while we ' re inside of an outer join and last_tab is
the last of its tables . . .
*/
while (first_inner_tab && first_inner_tab->last_inner == last_tab)
{
/*
Table tab is the last inner table of an outer join .
An on expression is always attached to it .
*/
COND *on_expr= *first_inner_tab->on_expr_ref;
table_map used_tables2= (join->const_table_map |
OUTER_REF_TABLE_BIT | RAND_TABLE_BIT);
start_from= tab->bush_root_tab?
tab->bush_root_tab->bush_children->start :
join->join_tab + join->const_tables;
for (JOIN_TAB *inner_tab= start_from;
inner_tab <= last_tab;
inner_tab++)
{
DBUG_ASSERT(inner_tab->table);
current_map= inner_tab->table->map;
used_tables2|= current_map;
/*
psergey : have put the - 1 below . It ' s bad , will need to fix it .
*/
COND *tmp_cond= make_cond_for_table(thd, on_expr, used_tables2,
current_map,
/*(inner_tab - first_tab)*/ -1,
FALSE , FALSE );
if (!tmp_cond && thd->is_error())
DBUG_RETURN(1 );
if (tab == last_tab)
{
/*
This pushes conjunctive conditions of ON expression of an outer
join such that :
- their used_tables ( ) contain RAND_TABLE_BIT
- the conditions does not refer to any fields
( such like rand ( ) > 0 . 5 )
*/
table_map rand_table_bit= (table_map) RAND_TABLE_BIT;
COND *rand_cond= make_cond_for_table(thd, on_expr, used_tables2,
rand_table_bit, -1 ,
FALSE , FALSE );
if (rand_cond)
add_cond_and_fix(thd, &tmp_cond, rand_cond);
else if (thd->is_error())
DBUG_RETURN(1 );
}
bool is_sjm_lookup_tab= FALSE ;
if (inner_tab->bush_children)
{
/*
' inner_tab ' is an SJ - Materialization tab , i . e . we have a join
order like this :
ot1 sjm_tab LEFT JOIN ot2 ot3
^ ^
' tab ' - + + - - - left join we ' re adding triggers for
LEFT JOIN ' s ON expression may not have references to subquery
columns . The subquery was in the WHERE clause , so IN - equality
is in the WHERE clause , also .
However , equality propagation code may have propagated the
IN - equality into ON expression , and we may get things like
subquery_inner_table = const
in the ON expression . We must not check such conditions during
SJM - lookup , because 1 ) subquery_inner_table has no valid current
row ( materialization temp . table has it instead ) , and 2 ) they
would be true anyway .
*/
SJ_MATERIALIZATION_INFO *sjm=
inner_tab->bush_children->start->emb_sj_nest->sj_mat_info;
if (sjm->is_used && !sjm->is_sj_scan)
is_sjm_lookup_tab= TRUE ;
}
if (inner_tab == first_inner_tab && inner_tab->on_precond &&
!is_sjm_lookup_tab)
add_cond_and_fix(thd, &tmp_cond, inner_tab->on_precond);
if (tmp_cond && !is_sjm_lookup_tab)
{
JOIN_TAB *cond_tab= (inner_tab < first_inner_tab ?
first_inner_tab : inner_tab);
Item **sel_cond_ref= (inner_tab < first_inner_tab ?
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 in Prozent C=94 H=86 G=89
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.4.924Bemerkung:
(vorverarbeitet am 2026-10-08)
¤
*Bot Zugriff